/** * 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 3 opções (todos_sem_excecao removido da UI em 2026-05-12)', () => { const { composer } = setup(); expect(composer.editScopeOptions.value).toHaveLength(3); expect(composer.editScopeOptions.value.map((o) => o.value)).toEqual(['somente_este', 'este_e_seguintes', 'todos']); }); 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:00–14: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]'); }); });