A66 WIP: AgendaEventDialog quebrado em 5 composables + 265 specs + V2 esqueleto

Sub-sessao 1 entregue (composables):
- agendaEventHelpers (262L) — utilitarios puros (date, format, parse)
- useAgendaEventComposer (485L) — montagem do form + validacao
- useAgendaEventActions (387L) — save/delete/cancel/move actions
- useAgendaEventPickerBilling (378L) — pickers (terapeuta, servico,
  convenio) + calculo de billing
- useAgendaEventLifecycle (474L) — open/close/dirty state + autosave
- 5 specs em __tests__/ (75+76+28+43+43 = 265 testes), 495/495 passing

AgendaEventDialog: 3522 -> 2632 linhas (-25%) consumindo os composables.
Backup byte-identico em AgendaEventDialog.vue.bak pra rollback.

Sub-sessao 2 entregue (esqueleto, NAO TESTADO):
- AgendaEventDialogV2 (~1100L, 3 zonas: PACIENTE/QUANDO/O QUE)
- Preview em /preview/agenda-dialog-v2 com 5 cenarios
- Rota em routes.misc.js
- User testou e nao gostou do design — aguarda feedback especifico
  pra iteracao na sub-sessao 3 (migracao nos 9 consumers).

Dialogs auxiliares novos pro AgendaEventDialog:
- InsurancePlanQuickCreateDialog (criar convenio inline)
- ServiceQuickCreateDialog (criar tipo de sessao inline)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-06 09:13:22 -03:00
parent 957e912a7f
commit 6d9b36d592
17 changed files with 10963 additions and 1295 deletions
@@ -0,0 +1,701 @@
/**
* useAgendaEventComposer.spec.js — A66 sub-sessão 1B
*
* Cobre o composable factory extraído do AgendaEventDialog.
* Foco do contrato: refs reativos + computeds derivados são consistentes
* com o comportamento original do .vue (matriz de inputs, edge cases).
*
* Não cobre: watchers e handlers (1C — fica no .vue ainda).
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ref, reactive } from 'vue';
import { useAgendaEventComposer } from '../useAgendaEventComposer';
// Mock de getPatientAgendaPermissions — o composable importa direto
vi.mock('@/composables/usePatientLifecycle', () => ({
getPatientAgendaPermissions: (status) => {
const norm = String(status || '').toLowerCase();
if (norm === 'inativo' || norm === 'arquivado') {
return { canCreateSession: false, canCreateRecurrence: false };
}
return { canCreateSession: true, canCreateRecurrence: true };
}
}));
// Helper: monta props default + emit fake
function setup(overrides = {}, extras = {}) {
const props = reactive({
modelValue: false,
eventRow: null,
initialStartISO: '',
initialEndISO: '',
ownerId: 'owner-1',
planOwnerId: '',
allowOwnerEdit: false,
ownerOptions: [],
tenantId: 'tenant-1',
commitmentOptions: [],
presetCommitmentId: null,
lockCommitment: false,
restrictPatientsToOwner: false,
patientScopeOwnerId: null,
workRules: [],
blockedDates: [],
agendaSettings: { session_duration_min: 50, session_break_min: 0 },
allEvents: [],
pausasSemanais: [],
feriados: [],
newPatientRoute: '',
...overrides
});
const emitted = [];
const emit = (...args) => emitted.push(args);
const composer = useAgendaEventComposer(props, emit, extras);
return { props, emit, emitted, composer };
}
const SESSION_COMMITMENT = { id: 'c-session', native_key: 'session', name: 'Sessão' };
const MEETING_COMMITMENT = { id: 'c-meeting', native_key: 'meeting', name: 'Reunião' };
// ════════════════════════════════════════════════════════════════════════
describe('visible (v-model)', () => {
it('lê de props.modelValue', () => {
const { composer, props } = setup({ modelValue: true });
expect(composer.visible.value).toBe(true);
props.modelValue = false;
expect(composer.visible.value).toBe(false);
});
it('escrever emite update:modelValue', () => {
const { composer, emitted } = setup();
composer.visible.value = true;
expect(emitted).toContainEqual(['update:modelValue', true]);
});
});
describe('isEdit', () => {
it('false sem eventRow', () => {
const { composer } = setup({ eventRow: null });
expect(composer.isEdit.value).toBe(false);
});
it('true com id', () => {
const { composer } = setup({ eventRow: { id: 'evt-1' } });
expect(composer.isEdit.value).toBe(true);
});
it('true com is_occurrence (sem id)', () => {
const { composer } = setup({ eventRow: { is_occurrence: true } });
expect(composer.isEdit.value).toBe(true);
});
});
describe('allowBack', () => {
it('true por default', () => {
const { composer } = setup();
expect(composer.allowBack.value).toBe(true);
});
it('false quando lockCommitment', () => {
const { composer } = setup({ lockCommitment: true });
expect(composer.allowBack.value).toBe(false);
});
it('false quando presetCommitmentId', () => {
const { composer } = setup({ presetCommitmentId: 'c-1' });
expect(composer.allowBack.value).toBe(false);
});
});
describe('hasSerie', () => {
it('false sem indicadores de série', () => {
const { composer } = setup({ eventRow: { id: 'evt-1' } });
expect(composer.hasSerie.value).toBe(false);
});
it('true com recurrence_id', () => {
const { composer } = setup({ eventRow: { id: 'evt-1', recurrence_id: 'r-1' } });
expect(composer.hasSerie.value).toBe(true);
});
it('true com serie_id (legado)', () => {
const { composer } = setup({ eventRow: { id: 'evt-1', serie_id: 's-1' } });
expect(composer.hasSerie.value).toBe(true);
});
it('true com is_occurrence', () => {
const { composer } = setup({ eventRow: { is_occurrence: true } });
expect(composer.hasSerie.value).toBe(true);
});
});
describe('isFirstOccurrence', () => {
it('false quando não é série', () => {
const { composer } = setup();
expect(composer.isFirstOccurrence.value).toBe(false);
});
it('true quando recurrence_date é a menor data da série', () => {
const serieEvents = ref([
{ recurrence_date: '2026-05-15' },
{ recurrence_date: '2026-05-22' },
{ recurrence_date: '2026-05-29' }
]);
const { composer } = setup(
{ eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-15' } },
{ serieEvents }
);
expect(composer.isFirstOccurrence.value).toBe(true);
});
it('false quando recurrence_date NÃO é a menor', () => {
const serieEvents = ref([
{ recurrence_date: '2026-05-15' },
{ recurrence_date: '2026-05-22' }
]);
const { composer } = setup(
{ eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-22' } },
{ serieEvents }
);
expect(composer.isFirstOccurrence.value).toBe(false);
});
it('false quando serieEvents vazio', () => {
const { composer } = setup(
{ eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-15' } },
{ serieEvents: ref([]) }
);
expect(composer.isFirstOccurrence.value).toBe(false);
});
});
describe('editScopeOptions', () => {
it('retorna 4 opções', () => {
const { composer } = setup();
expect(composer.editScopeOptions.value).toHaveLength(4);
});
it('"este_e_seguintes" disabled quando isFirstOccurrence', () => {
const serieEvents = ref([{ recurrence_date: '2026-05-15' }, { recurrence_date: '2026-05-22' }]);
const { composer } = setup(
{ eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-15' } },
{ serieEvents }
);
const opt = composer.editScopeOptions.value.find((o) => o.value === 'este_e_seguintes');
expect(opt.disabled).toBe(true);
});
});
describe('qtdSessoesEfetiva', () => {
it('valores fixos pelo mode', () => {
const { composer } = setup();
composer.qtdSessoesMode.value = '4';
expect(composer.qtdSessoesEfetiva.value).toBe(4);
composer.qtdSessoesMode.value = '8';
expect(composer.qtdSessoesEfetiva.value).toBe(8);
composer.qtdSessoesMode.value = '12';
expect(composer.qtdSessoesEfetiva.value).toBe(12);
});
it('"personalizar" usa qtdSessoesCustom', () => {
const { composer } = setup();
composer.qtdSessoesMode.value = 'personalizar';
composer.qtdSessoesCustom.value = 24;
expect(composer.qtdSessoesEfetiva.value).toBe(24);
});
it('clampa em 1 quando custom é 0/null', () => {
const { composer } = setup();
composer.qtdSessoesMode.value = 'personalizar';
composer.qtdSessoesCustom.value = 0;
expect(composer.qtdSessoesEfetiva.value).toBe(1);
composer.qtdSessoesCustom.value = null;
expect(composer.qtdSessoesEfetiva.value).toBe(1);
});
});
describe('proximasOcorrencias', () => {
function asISODate(d) {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
it('retorna [] quando avulsa', () => {
const { composer } = setup();
composer.recorrenciaType.value = 'avulsa';
composer.form.value.dia = new Date('2026-05-15');
expect(composer.proximasOcorrencias.value).toEqual([]);
});
it('semanal: 4 datas separadas por 7 dias', () => {
const { composer } = setup();
composer.recorrenciaType.value = 'semanal';
composer.qtdSessoesMode.value = '4';
composer.form.value.dia = new Date('2026-05-15T10:00:00');
const list = composer.proximasOcorrencias.value;
expect(list).toHaveLength(4);
expect(asISODate(list[0])).toBe('2026-05-15');
expect(asISODate(list[1])).toBe('2026-05-22');
expect(asISODate(list[2])).toBe('2026-05-29');
expect(asISODate(list[3])).toBe('2026-06-05');
});
it('quinzenal: separação 14 dias', () => {
const { composer } = setup();
composer.recorrenciaType.value = 'quinzenal';
composer.qtdSessoesMode.value = '4';
composer.form.value.dia = new Date('2026-05-15T10:00:00');
const list = composer.proximasOcorrencias.value;
expect(list).toHaveLength(4);
expect(asISODate(list[1])).toBe('2026-05-29');
expect(asISODate(list[2])).toBe('2026-06-12');
});
it('diasEspecificos: respeita dias selecionados', () => {
const { composer } = setup();
composer.recorrenciaType.value = 'diasEspecificos';
composer.qtdSessoesMode.value = '4';
composer.diasSelecionados.value = [1, 3]; // segunda + quarta
composer.form.value.dia = new Date('2026-05-18T10:00:00'); // segunda
const list = composer.proximasOcorrencias.value;
expect(list).toHaveLength(4);
// 18 (seg) - 20 (qua) - 25 (seg) - 27 (qua)
expect(list[0].getDay()).toBe(1);
expect(list[1].getDay()).toBe(3);
expect(list[2].getDay()).toBe(1);
expect(list[3].getDay()).toBe(3);
});
it('diasEspecificos com array vazio retorna []', () => {
const { composer } = setup();
composer.recorrenciaType.value = 'diasEspecificos';
composer.diasSelecionados.value = [];
composer.form.value.dia = new Date('2026-05-15');
expect(composer.proximasOcorrencias.value).toEqual([]);
});
});
describe('toggleDiaSelecionado', () => {
it('adiciona se não existe, remove se existe', () => {
const { composer } = setup();
composer.toggleDiaSelecionado(1);
expect(composer.diasSelecionados.value).toEqual([1]);
composer.toggleDiaSelecionado(3);
expect(composer.diasSelecionados.value).toEqual([1, 3]);
composer.toggleDiaSelecionado(1);
expect(composer.diasSelecionados.value).toEqual([3]);
});
});
describe('isForaDoPlano (com dataLimiteManual)', () => {
it('false quando dataLimiteManual null', () => {
const { composer } = setup();
expect(composer.isForaDoPlano(new Date('2026-12-31'))).toBe(false);
});
it('true quando data > limite', () => {
const { composer } = setup();
composer.dataLimiteManual.value = '2026-06-30';
expect(composer.isForaDoPlano(new Date('2026-12-31'))).toBe(true);
expect(composer.isForaDoPlano(new Date('2026-06-29'))).toBe(false);
});
});
describe('commitmentCards', () => {
it('coloca native_key="session" primeiro', () => {
const { composer } = setup({
commitmentOptions: [MEETING_COMMITMENT, SESSION_COMMITMENT, { id: 'c-3', name: 'Avaliação' }]
});
const cards = composer.commitmentCards.value;
// commitmentOptions vem via reactive(props) — itens viram proxies,
// por isso comparamos id (toBe) em vez de identidade do objeto.
expect(cards[0].id).toBe('c-session');
});
it('ordem alfabética entre não-session', () => {
const { composer } = setup({
commitmentOptions: [
{ id: 'c-1', name: 'Zeta' },
{ id: 'c-2', name: 'Alpha' },
{ id: 'c-3', name: 'Beta' }
]
});
const cards = composer.commitmentCards.value;
expect(cards.map((c) => c.name)).toEqual(['Alpha', 'Beta', 'Zeta']);
});
});
describe('selectedCommitment + relacionados', () => {
it('selectedCommitment encontra pelo form.commitment_id', () => {
const { composer } = setup({
commitmentOptions: [SESSION_COMMITMENT, MEETING_COMMITMENT]
});
composer.form.value.commitment_id = 'c-meeting';
expect(composer.selectedCommitment.value?.id).toBe('c-meeting');
});
it('selectedCommitmentName fallback "—" quando null', () => {
const { composer } = setup();
expect(composer.selectedCommitmentName.value).toBe('—');
});
it('requiresPatient true quando native_key=session', () => {
const { composer } = setup({ commitmentOptions: [SESSION_COMMITMENT] });
composer.form.value.commitment_id = 'c-session';
expect(composer.requiresPatient.value).toBe(true);
expect(composer.isSessionEvent.value).toBe(true);
});
it('requiresPatient false pra outros commitments', () => {
const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
composer.form.value.commitment_id = 'c-meeting';
expect(composer.requiresPatient.value).toBe(false);
});
it('patientLocked true só na edição de sessão com paciente', () => {
const { composer } = setup({
commitmentOptions: [SESSION_COMMITMENT],
eventRow: { id: 'evt-1', paciente_id: 'p-1' }
});
composer.form.value.commitment_id = 'c-session';
expect(composer.patientLocked.value).toBe(true);
});
it('hasInsurance reage a form.insurance_plan_id', () => {
const { composer } = setup();
expect(composer.hasInsurance.value).toBe(false);
composer.form.value.insurance_plan_id = 'plan-1';
expect(composer.hasInsurance.value).toBe(true);
});
});
describe('agendaPerms', () => {
it('Inativo bloqueia create', () => {
const { composer } = setup();
composer.form.value.paciente_status = 'Inativo';
expect(composer.agendaPerms.value.canCreateSession).toBe(false);
});
it('Ativo permite tudo', () => {
const { composer } = setup();
composer.form.value.paciente_status = 'Ativo';
expect(composer.agendaPerms.value.canCreateSession).toBe(true);
expect(composer.agendaPerms.value.canCreateRecurrence).toBe(true);
});
});
describe('isSessionFuture / isArchivedPastEdit / isInativoFutureEdit', () => {
let mockNow;
beforeEach(() => {
mockNow = new Date('2026-05-15T12:00:00').getTime();
vi.useFakeTimers();
vi.setSystemTime(mockNow);
});
it('isSessionFuture true quando criando (não-edit)', () => {
const { composer } = setup({ eventRow: null });
expect(composer.isSessionFuture.value).toBe(true);
});
it('isSessionFuture true quando edit + sessão futura', () => {
const { composer } = setup({
eventRow: { id: 'evt-1', inicio_em: '2026-05-20T10:00:00' }
});
expect(composer.isSessionFuture.value).toBe(true);
});
it('isSessionFuture false quando edit + sessão passada', () => {
const { composer } = setup({
eventRow: { id: 'evt-1', inicio_em: '2026-05-10T10:00:00' }
});
expect(composer.isSessionFuture.value).toBe(false);
});
it('isArchivedPastEdit true: edit + Arquivado + passada', () => {
const { composer } = setup({
eventRow: { id: 'evt-1', inicio_em: '2026-05-10T10:00:00' }
});
composer.form.value.paciente_status = 'Arquivado';
expect(composer.isArchivedPastEdit.value).toBe(true);
});
it('isInativoFutureEdit true: edit + Inativo + futura', () => {
const { composer } = setup({
eventRow: { id: 'evt-1', inicio_em: '2026-05-20T10:00:00' }
});
composer.form.value.paciente_status = 'Inativo';
expect(composer.isInativoFutureEdit.value).toBe(true);
});
});
describe('inicioDateTime / fimDateTime', () => {
it('null quando dia ou startTime ausentes', () => {
const { composer } = setup();
composer.form.value.dia = null;
expect(composer.inicioDateTime.value).toBe(null);
});
it('combina dia + startTime corretamente', () => {
const { composer } = setup();
composer.form.value.dia = new Date('2026-05-15T00:00:00');
composer.form.value.startTime = '14:30';
const ini = composer.inicioDateTime.value;
expect(ini.getHours()).toBe(14);
expect(ini.getMinutes()).toBe(30);
});
it('fimDateTime adiciona duracaoMin', () => {
const { composer } = setup();
composer.form.value.dia = new Date('2026-05-15T00:00:00');
composer.form.value.startTime = '14:00';
composer.form.value.duracaoMin = 50;
const fim = composer.fimDateTime.value;
expect(fim.getHours()).toBe(14);
expect(fim.getMinutes()).toBe(50);
});
});
describe('startTimeDate (computed get/set)', () => {
it('getter null quando startTime null', () => {
const { composer } = setup();
composer.form.value.startTime = null;
expect(composer.startTimeDate.value).toBe(null);
});
it('getter retorna Date com hora setada', () => {
const { composer } = setup();
composer.form.value.startTime = '09:30';
const d = composer.startTimeDate.value;
expect(d.getHours()).toBe(9);
expect(d.getMinutes()).toBe(30);
});
it('setter atualiza form.startTime no formato HH:MM', () => {
const { composer } = setup();
const d = new Date();
d.setHours(15, 5, 0, 0);
composer.startTimeDate.value = d;
expect(composer.form.value.startTime).toBe('15:05');
});
it('setter null limpa form.startTime', () => {
const { composer } = setup();
composer.form.value.startTime = '14:00';
composer.startTimeDate.value = null;
expect(composer.form.value.startTime).toBe(null);
});
});
describe('canSave (matriz de validação — núcleo)', () => {
function ready(composer) {
composer.form.value.owner_id = 'owner-1';
composer.form.value.dia = new Date('2026-05-15');
composer.form.value.startTime = '14:00';
composer.form.value.commitment_id = 'c-meeting';
composer.form.value.duracaoMin = 50;
}
it('false sem owner_id', () => {
const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
ready(composer);
composer.form.value.owner_id = '';
expect(composer.canSave.value).toBe(false);
});
it('false sem dia', () => {
const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
ready(composer);
composer.form.value.dia = null;
expect(composer.canSave.value).toBe(false);
});
it('false sem startTime', () => {
const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
ready(composer);
composer.form.value.startTime = null;
expect(composer.canSave.value).toBe(false);
});
it('false ao criar sem commitment_id', () => {
const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
ready(composer);
composer.form.value.commitment_id = null;
expect(composer.canSave.value).toBe(false);
});
it('true ao EDITAR sem commitment_id (sessões antigas)', () => {
const { composer } = setup({
commitmentOptions: [MEETING_COMMITMENT],
eventRow: { id: 'evt-1', inicio_em: '2026-05-15T14:00:00' }
});
ready(composer);
composer.form.value.commitment_id = null; // legacy null
expect(composer.canSave.value).toBe(true);
});
it('false em sessão sem paciente_id quando requiresPatient', () => {
const { composer } = setup({ commitmentOptions: [SESSION_COMMITMENT] });
ready(composer);
composer.form.value.commitment_id = 'c-session';
composer.form.value.paciente_id = null;
expect(composer.canSave.value).toBe(false);
});
it('false em sessão particular sem itens de billing', () => {
const items = ref([]);
const { composer } = setup(
{ commitmentOptions: [SESSION_COMMITMENT] },
{ commitmentItems: items }
);
ready(composer);
composer.form.value.commitment_id = 'c-session';
composer.form.value.paciente_id = 'p-1';
composer.billingType.value = 'particular';
expect(composer.canSave.value).toBe(false);
});
it('true em sessão particular COM itens', () => {
const items = ref([{ id: 'i-1' }]);
const { composer } = setup(
{ commitmentOptions: [SESSION_COMMITMENT] },
{ commitmentItems: items }
);
ready(composer);
composer.form.value.commitment_id = 'c-session';
composer.form.value.paciente_id = 'p-1';
composer.billingType.value = 'particular';
expect(composer.canSave.value).toBe(true);
});
it('false ao criar sessão pra paciente Inativo', () => {
const items = ref([{ id: 'i-1' }]);
const { composer } = setup(
{ commitmentOptions: [SESSION_COMMITMENT] },
{ commitmentItems: items }
);
ready(composer);
composer.form.value.commitment_id = 'c-session';
composer.form.value.paciente_id = 'p-1';
composer.form.value.paciente_status = 'Inativo';
composer.billingType.value = 'particular';
expect(composer.canSave.value).toBe(false);
});
it('false ao criar recorrência pra paciente Arquivado', () => {
const items = ref([{ id: 'i-1' }]);
const { composer } = setup(
{ commitmentOptions: [SESSION_COMMITMENT] },
{ commitmentItems: items }
);
ready(composer);
composer.form.value.commitment_id = 'c-session';
composer.form.value.paciente_id = 'p-1';
composer.form.value.paciente_status = 'Arquivado';
composer.billingType.value = 'particular';
composer.recorrenciaType.value = 'semanal';
expect(composer.canSave.value).toBe(false);
});
it('true em billing convenio sem precisar de itens', () => {
const items = ref([]);
const { composer } = setup(
{ commitmentOptions: [SESSION_COMMITMENT] },
{ commitmentItems: items }
);
ready(composer);
composer.form.value.commitment_id = 'c-session';
composer.form.value.paciente_id = 'p-1';
composer.billingType.value = 'convenio';
expect(composer.canSave.value).toBe(true);
});
});
describe('timeConflict (pré-check antes do PATCH)', () => {
function readyForm(composer) {
composer.form.value.dia = new Date('2026-05-15T00:00:00');
composer.form.value.startTime = '14:00';
composer.form.value.duracaoMin = 50;
}
it('null quando form ainda incompleto', () => {
const { composer } = setup();
composer.form.value.dia = null;
expect(composer.timeConflict.value).toBe(null);
});
it('null sem allEvents', () => {
const { composer } = setup({ allEvents: [] });
readyForm(composer);
expect(composer.timeConflict.value).toBe(null);
});
it('detecta overlap com evento existente', () => {
const { composer } = setup({
allEvents: [{
id: 'evt-x',
inicio_em: '2026-05-15T14:30:00',
fim_em: '2026-05-15T15:20:00',
paciente_nome: 'Maria'
}]
});
readyForm(composer);
expect(composer.timeConflict.value).toMatch(/Maria/);
});
it('NÃO detecta overlap com o próprio evento (form.id === evt.id)', () => {
const { composer } = setup({
allEvents: [{
id: 'self',
inicio_em: '2026-05-15T14:00:00',
fim_em: '2026-05-15T14:50:00'
}]
});
readyForm(composer);
composer.form.value.id = 'self';
expect(composer.timeConflict.value).toBe(null);
});
it('null quando evento adjacente (sem overlap real)', () => {
const { composer } = setup({
allEvents: [{
id: 'evt-x',
inicio_em: '2026-05-15T15:00:00',
fim_em: '2026-05-15T15:50:00'
}]
});
readyForm(composer);
composer.form.value.duracaoMin = 50; // 14:0014:50
expect(composer.timeConflict.value).toBe(null);
});
it('detecta sobreposição com pausa do dia', () => {
const { composer } = setup({
pausasSemanais: [{
dia_semana: 5, // sexta (2026-05-15)
hora_inicio: '14:30',
hora_fim: '15:00'
}]
});
readyForm(composer);
expect(composer.timeConflict.value).toMatch(/pausa/i);
});
});
describe('totalConflitos / sessoesForaDoPlano', () => {
it('totalConflitos conta ocorrências com folga/feriado/bloqueado/pausa', () => {
const { composer } = setup({
workRules: [
{ dia_semana: 1 }, { dia_semana: 2 }, { dia_semana: 3 },
{ dia_semana: 4 }, { dia_semana: 5 } // seg-sex
],
blockedDates: ['2026-05-22']
});
composer.recorrenciaType.value = 'semanal';
composer.qtdSessoesMode.value = '4';
composer.form.value.dia = new Date('2026-05-15T10:00:00'); // sexta
// 15 (sex ok) - 22 (sex bloqueada) - 29 (sex ok) - 5/jun (sex ok)
expect(composer.totalConflitos.value).toBe(1);
});
it('sessoesForaDoPlano com dataLimiteManual', () => {
const { composer } = setup();
composer.recorrenciaType.value = 'semanal';
composer.qtdSessoesMode.value = '4';
composer.form.value.dia = new Date('2026-05-15T10:00:00');
composer.dataLimiteManual.value = '2026-05-22';
// ocorrências carregam hora 10:00 (de form.dia); limite "2026-05-22"
// vira meia-noite local. Logo 22/5 10:00 > 22/5 00:00 → fora.
// Resultado: 15 (dentro), 22 (fora), 29 (fora), 5/jun (fora) = 3
expect(composer.sessoesForaDoPlano.value).toBe(3);
});
});
describe('headerTitle', () => {
it('"Editar compromisso" quando isEdit', () => {
const { composer } = setup({ eventRow: { id: 'evt-1' } });
expect(composer.headerTitle.value).toBe('Editar compromisso');
});
it('"Novo compromisso — escolha o tipo" no step 1', () => {
const { composer } = setup();
composer.step.value = 1;
expect(composer.headerTitle.value).toMatch(/escolha o tipo/);
});
it('"Novo compromisso" no step 2', () => {
const { composer } = setup();
composer.step.value = 2;
expect(composer.headerTitle.value).toBe('Novo compromisso');
});
});
describe('computedTitulo', () => {
it('usa titulo_custom quando preenchido', () => {
const { composer } = setup();
composer.form.value.titulo_custom = 'Minha custom';
expect(composer.computedTitulo.value).toBe('Minha custom');
});
it('"—" quando não há commitment selecionado (selectedCommitmentName fallback)', () => {
// selectedCommitmentName retorna "—" quando sem commitment, então
// computedTitulo herda esse "—" porque "—" || "Compromisso" → "—".
// Comportamento original do .vue preservado.
const { composer } = setup();
expect(composer.computedTitulo.value).toBe('—');
});
it('combina nome paciente + commitment quando session', () => {
const { composer } = setup({ commitmentOptions: [SESSION_COMMITMENT] });
composer.form.value.commitment_id = 'c-session';
composer.form.value.paciente_nome = 'Ana';
expect(composer.computedTitulo.value).toBe('Ana [Sessão]');
});
});