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:
@@ -0,0 +1,611 @@
|
||||
/**
|
||||
* useAgendaEventPickerBilling.spec.js — A66 sub-sessão 1C-ii-a
|
||||
*
|
||||
* Cobre handlers de patient picker + billing items + 2 watchers
|
||||
* (form.commitment_id, form.insurance_plan_id).
|
||||
*
|
||||
* Mock estratégia: monta um composer fake + actions fake com refs/computeds
|
||||
* mínimos. Mock supabase.from('patients') pra loadPatients.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// ── Mocks ─────────────────────────────────────────────────────
|
||||
let _supabaseSelectArgs = null;
|
||||
const _patientsResult = { data: [], error: null };
|
||||
function makeQ() {
|
||||
// O loadPatients faz: from().select().order().limit() e DEPOIS adiciona
|
||||
// .eq() condicionalmente, finalizando com `await q`. Pra suportar isso,
|
||||
// o mock retorna o mesmo `q` em todos os métodos da chain e implementa
|
||||
// `then` (thenable) pra ser awaitable.
|
||||
const q = {
|
||||
select: (...args) => {
|
||||
_supabaseSelectArgs = args;
|
||||
return q;
|
||||
},
|
||||
eq: () => q,
|
||||
order: () => q,
|
||||
limit: () => q,
|
||||
then: (resolve) => resolve(_patientsResult)
|
||||
};
|
||||
return q;
|
||||
}
|
||||
vi.mock('@/lib/supabase/client', () => ({
|
||||
supabase: {
|
||||
from: () => makeQ()
|
||||
}
|
||||
}));
|
||||
|
||||
function resetPatientsResult({ data = [], error = null } = {}) {
|
||||
_patientsResult.data = data;
|
||||
_patientsResult.error = error;
|
||||
}
|
||||
|
||||
const { useAgendaEventPickerBilling } = await import('../useAgendaEventPickerBilling.js');
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
function makeComposer(overrides = {}) {
|
||||
const form = ref({
|
||||
commitment_id: null,
|
||||
paciente_id: null,
|
||||
paciente_nome: '',
|
||||
paciente_avatar: '',
|
||||
extra_fields: {},
|
||||
price: null,
|
||||
insurance_plan_id: null,
|
||||
insurance_plan_service_id: null,
|
||||
insurance_value: null,
|
||||
insurance_guide_number: null,
|
||||
duracaoMin: 50,
|
||||
...(overrides.formExtra || {})
|
||||
});
|
||||
return {
|
||||
form,
|
||||
billingType: ref('particular'),
|
||||
isEdit: ref(false),
|
||||
visible: ref(true),
|
||||
step: ref(1),
|
||||
requiresPatient: ref(true),
|
||||
allowBack: ref(true),
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
function makeActions() {
|
||||
return {
|
||||
_restoringConvenio: ref(false),
|
||||
samePatientConflict: ref(null)
|
||||
};
|
||||
}
|
||||
|
||||
function setup(overrides = {}, propsOverrides = {}) {
|
||||
_supabaseSelectArgs = null;
|
||||
// NÃO resetamos _patientsResult aqui pra permitir que o teste pré-popule.
|
||||
// Cada `it` chama resetPatientsResult() explicitamente quando precisa.
|
||||
|
||||
const composer = overrides.composer ?? makeComposer();
|
||||
const actions = overrides.actions ?? makeActions();
|
||||
const commitmentItems = overrides.commitmentItems ?? ref([]);
|
||||
const servicePickerSel = overrides.servicePickerSel ?? ref(null);
|
||||
const selectedPlanService = overrides.selectedPlanService ?? ref(null);
|
||||
const services = overrides.services ?? ref([]);
|
||||
const loadServices = vi.fn().mockResolvedValue();
|
||||
const getDefaultPrice = overrides.getDefaultPrice ?? vi.fn(() => null);
|
||||
const planServices = overrides.planServices ?? computed(() => []);
|
||||
const loadActiveDiscount = overrides.loadActiveDiscount ?? vi.fn().mockResolvedValue(null);
|
||||
const _csLoadItems = overrides._csLoadItems ?? vi.fn().mockResolvedValue([]);
|
||||
const _csLoadItemsOrTemplate = overrides._csLoadItemsOrTemplate ?? vi.fn().mockResolvedValue([]);
|
||||
const isDynamic = overrides.isDynamic ?? computed(() => false);
|
||||
const props = {
|
||||
ownerId: 'owner-1',
|
||||
tenantId: 'tenant-1',
|
||||
eventRow: null,
|
||||
agendaSettings: { session_duration_min: 50 },
|
||||
restrictPatientsToOwner: false,
|
||||
patientScopeOwnerId: null,
|
||||
newPatientRoute: '',
|
||||
...propsOverrides
|
||||
};
|
||||
|
||||
const result = useAgendaEventPickerBilling({
|
||||
composer,
|
||||
actions,
|
||||
commitmentItems,
|
||||
servicePickerSel,
|
||||
selectedPlanService,
|
||||
services,
|
||||
loadServices,
|
||||
getDefaultPrice,
|
||||
planServices,
|
||||
loadActiveDiscount,
|
||||
_csLoadItems,
|
||||
_csLoadItemsOrTemplate,
|
||||
isDynamic,
|
||||
props
|
||||
});
|
||||
|
||||
return {
|
||||
composer,
|
||||
actions,
|
||||
commitmentItems,
|
||||
servicePickerSel,
|
||||
selectedPlanService,
|
||||
services,
|
||||
loadServices,
|
||||
getDefaultPrice,
|
||||
planServices,
|
||||
loadActiveDiscount,
|
||||
_csLoadItems,
|
||||
_csLoadItemsOrTemplate,
|
||||
isDynamic,
|
||||
props,
|
||||
...result
|
||||
};
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
describe('addItem', () => {
|
||||
it('no-op sem service.id', async () => {
|
||||
const { commitmentItems, addItem } = setup();
|
||||
await addItem(null);
|
||||
await addItem({});
|
||||
expect(commitmentItems.value).toEqual([]);
|
||||
});
|
||||
|
||||
it('adiciona item novo com final_price calculado', async () => {
|
||||
const { commitmentItems, addItem } = setup();
|
||||
await addItem({ id: 's-1', name: 'Sessão', price: 100 });
|
||||
expect(commitmentItems.value).toHaveLength(1);
|
||||
expect(commitmentItems.value[0]).toMatchObject({
|
||||
service_id: 's-1',
|
||||
service_name: 'Sessão',
|
||||
quantity: 1,
|
||||
unit_price: 100,
|
||||
discount_pct: 0,
|
||||
discount_flat: 0,
|
||||
final_price: 100
|
||||
});
|
||||
});
|
||||
|
||||
it('incrementa quantity quando service_id já existe', async () => {
|
||||
const { commitmentItems, addItem } = setup();
|
||||
await addItem({ id: 's-1', name: 'Sessão', price: 100 });
|
||||
await addItem({ id: 's-1', name: 'Sessão', price: 100 });
|
||||
await addItem({ id: 's-1', name: 'Sessão', price: 100 });
|
||||
expect(commitmentItems.value).toHaveLength(1);
|
||||
expect(commitmentItems.value[0].quantity).toBe(3);
|
||||
expect(commitmentItems.value[0].final_price).toBe(300);
|
||||
});
|
||||
|
||||
it('aplica desconto ativo do paciente', async () => {
|
||||
const composer = makeComposer({ formExtra: { paciente_id: 'p-1' } });
|
||||
const loadActiveDiscount = vi.fn().mockResolvedValue({ discount_pct: 10, discount_flat: 5 });
|
||||
const { commitmentItems, addItem } = setup({ composer, loadActiveDiscount });
|
||||
await addItem({ id: 's-1', name: 'X', price: 100 });
|
||||
expect(loadActiveDiscount).toHaveBeenCalledWith('owner-1', 'p-1');
|
||||
// 100 - 10% = 90 - 5 = 85
|
||||
expect(commitmentItems.value[0].final_price).toBe(85);
|
||||
});
|
||||
|
||||
it('sem patient_id NÃO chama loadActiveDiscount', async () => {
|
||||
const { addItem, loadActiveDiscount } = setup();
|
||||
await addItem({ id: 's-1', name: 'X', price: 100 });
|
||||
expect(loadActiveDiscount).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeItem', () => {
|
||||
it('remove item por índice', () => {
|
||||
const items = ref([
|
||||
{ service_id: 'a', quantity: 1, final_price: 100 },
|
||||
{ service_id: 'b', quantity: 1, final_price: 200 }
|
||||
]);
|
||||
const { removeItem, commitmentItems } = setup({ commitmentItems: items });
|
||||
removeItem(0);
|
||||
expect(commitmentItems.value).toHaveLength(1);
|
||||
expect(commitmentItems.value[0].service_id).toBe('b');
|
||||
});
|
||||
|
||||
it('lista vazia em modo dynamic restaura duração padrão', () => {
|
||||
const composer = makeComposer({ formExtra: { duracaoMin: 30 } });
|
||||
const isDynamic = computed(() => true);
|
||||
const items = ref([{ service_id: 'a', quantity: 1, final_price: 100 }]);
|
||||
const { removeItem } = setup({ composer, isDynamic, commitmentItems: items });
|
||||
removeItem(0);
|
||||
expect(composer.form.value.duracaoMin).toBe(50); // session_duration_min default
|
||||
});
|
||||
|
||||
it('NÃO restaura duração se !isDynamic', () => {
|
||||
const composer = makeComposer({ formExtra: { duracaoMin: 30 } });
|
||||
const items = ref([{ service_id: 'a', quantity: 1, final_price: 100 }]);
|
||||
const { removeItem } = setup({ composer, commitmentItems: items });
|
||||
removeItem(0);
|
||||
expect(composer.form.value.duracaoMin).toBe(30);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onItemChange', () => {
|
||||
it('recalcula final_price baseado em quantity/discounts', () => {
|
||||
const { onItemChange } = setup();
|
||||
const item = { unit_price: 100, quantity: 2, discount_pct: 10, discount_flat: 0, final_price: 0 };
|
||||
onItemChange(item);
|
||||
expect(item.final_price).toBe(180); // 200 - 10% = 180
|
||||
});
|
||||
});
|
||||
|
||||
describe('onProcedureSelect', () => {
|
||||
it('seta plan_service_id e atualiza insurance_value', () => {
|
||||
const planServices = computed(() => [{ id: 'ps-1', value: 250.5 }]);
|
||||
const composer = makeComposer();
|
||||
const { onProcedureSelect } = setup({ composer, planServices });
|
||||
onProcedureSelect('ps-1');
|
||||
expect(composer.form.value.insurance_plan_service_id).toBe('ps-1');
|
||||
expect(composer.form.value.insurance_value).toBe(250.5);
|
||||
});
|
||||
|
||||
it('null limpa insurance_value', () => {
|
||||
const composer = makeComposer({ formExtra: { insurance_value: 100 } });
|
||||
const { onProcedureSelect } = setup({ composer });
|
||||
onProcedureSelect(null);
|
||||
expect(composer.form.value.insurance_plan_service_id).toBe(null);
|
||||
expect(composer.form.value.insurance_value).toBe(null);
|
||||
});
|
||||
|
||||
it('id desconhecido limpa insurance_value', () => {
|
||||
const planServices = computed(() => [{ id: 'ps-1', value: 250 }]);
|
||||
const composer = makeComposer({ formExtra: { insurance_value: 100 } });
|
||||
const { onProcedureSelect } = setup({ composer, planServices });
|
||||
onProcedureSelect('ps-x');
|
||||
expect(composer.form.value.insurance_plan_service_id).toBe('ps-x');
|
||||
expect(composer.form.value.insurance_value).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectCommitment', () => {
|
||||
it('seta commitment_id, reseta extra_fields, vai pra step 2', () => {
|
||||
const composer = makeComposer();
|
||||
const { selectCommitment } = setup({ composer });
|
||||
selectCommitment({ id: 'c-1', name: 'X', fields: [{ key: 'idade' }, { key: 'peso' }] });
|
||||
expect(composer.form.value.commitment_id).toBe('c-1');
|
||||
expect(composer.form.value.extra_fields).toEqual({ idade: '', peso: '' });
|
||||
expect(composer.step.value).toBe(2);
|
||||
});
|
||||
|
||||
it('no-op sem id', () => {
|
||||
const composer = makeComposer();
|
||||
const { selectCommitment } = setup({ composer });
|
||||
composer.step.value = 1;
|
||||
selectCommitment(null);
|
||||
selectCommitment({});
|
||||
expect(composer.step.value).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('goBack', () => {
|
||||
it('volta pra step 1 e limpa commitment + paciente', () => {
|
||||
const composer = makeComposer({
|
||||
formExtra: { commitment_id: 'c-1', paciente_id: 'p-1', paciente_nome: 'Ana' }
|
||||
});
|
||||
composer.step.value = 2;
|
||||
const { goBack } = setup({ composer });
|
||||
goBack();
|
||||
expect(composer.step.value).toBe(1);
|
||||
expect(composer.form.value.commitment_id).toBe(null);
|
||||
expect(composer.form.value.paciente_id).toBe(null);
|
||||
expect(composer.form.value.paciente_nome).toBe('');
|
||||
});
|
||||
|
||||
it('no-op em edição', () => {
|
||||
const composer = makeComposer();
|
||||
composer.isEdit.value = true;
|
||||
composer.step.value = 2;
|
||||
const { goBack } = setup({ composer });
|
||||
goBack();
|
||||
expect(composer.step.value).toBe(2);
|
||||
});
|
||||
|
||||
it('no-op com !allowBack', () => {
|
||||
const composer = makeComposer();
|
||||
composer.allowBack.value = false;
|
||||
composer.step.value = 2;
|
||||
const { goBack } = setup({ composer });
|
||||
goBack();
|
||||
expect(composer.step.value).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectPaciente / clearPaciente', () => {
|
||||
it('selectPaciente preenche form e fecha picker', () => {
|
||||
const composer = makeComposer();
|
||||
const { selectPaciente, pacientePickerOpen } = setup({ composer });
|
||||
pacientePickerOpen.value = true;
|
||||
selectPaciente({ id: 'p-1', nome: 'Ana', avatar_url: 'url' });
|
||||
expect(composer.form.value.paciente_id).toBe('p-1');
|
||||
expect(composer.form.value.paciente_nome).toBe('Ana');
|
||||
expect(composer.form.value.paciente_avatar).toBe('url');
|
||||
expect(pacientePickerOpen.value).toBe(false);
|
||||
});
|
||||
|
||||
it('selectPaciente no-op sem id', () => {
|
||||
const composer = makeComposer();
|
||||
const { selectPaciente } = setup({ composer });
|
||||
selectPaciente(null);
|
||||
selectPaciente({});
|
||||
expect(composer.form.value.paciente_id).toBe(null);
|
||||
});
|
||||
|
||||
it('clearPaciente limpa form + samePatientConflict', () => {
|
||||
const composer = makeComposer({
|
||||
formExtra: { paciente_id: 'p-1', paciente_nome: 'Ana', paciente_avatar: 'url' }
|
||||
});
|
||||
const actions = makeActions();
|
||||
actions.samePatientConflict.value = { id: 'evt-x' };
|
||||
const { clearPaciente } = setup({ composer, actions });
|
||||
clearPaciente();
|
||||
expect(composer.form.value.paciente_id).toBe(null);
|
||||
expect(composer.form.value.paciente_nome).toBe('');
|
||||
expect(actions.samePatientConflict.value).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openPacientePicker', () => {
|
||||
it('abre picker e dispara loadPatients', () => {
|
||||
const composer = makeComposer();
|
||||
composer.requiresPatient.value = true;
|
||||
const { openPacientePicker, pacientePickerOpen } = setup({ composer });
|
||||
openPacientePicker();
|
||||
expect(pacientePickerOpen.value).toBe(true);
|
||||
});
|
||||
|
||||
it('no-op quando NÃO requiresPatient', () => {
|
||||
const composer = makeComposer();
|
||||
composer.requiresPatient.value = false;
|
||||
const { openPacientePicker, pacientePickerOpen } = setup({ composer });
|
||||
openPacientePicker();
|
||||
expect(pacientePickerOpen.value).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearPatientsCache', () => {
|
||||
it('zera patients, search e error', () => {
|
||||
const { clearPatientsCache, patients, pacienteSearch, pacientesError } = setup();
|
||||
patients.value = [{ id: 'p-1' }];
|
||||
pacienteSearch.value = 'foo';
|
||||
pacientesError.value = 'err';
|
||||
clearPatientsCache();
|
||||
expect(patients.value).toEqual([]);
|
||||
expect(pacienteSearch.value).toBe('');
|
||||
expect(pacientesError.value).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadPatients', () => {
|
||||
it('faz fetch e mapeia resultado', async () => {
|
||||
const { loadPatients, patients } = setup();
|
||||
resetPatientsResult({
|
||||
data: [{ id: 'p-1', nome_completo: 'Ana', email_principal: 'a@x', telefone: '11', status: 'Ativo', avatar_url: 'u' }]
|
||||
});
|
||||
await loadPatients(true);
|
||||
expect(patients.value).toHaveLength(1);
|
||||
expect(patients.value[0]).toMatchObject({
|
||||
id: 'p-1',
|
||||
nome: 'Ana',
|
||||
email: 'a@x',
|
||||
telefone: '11'
|
||||
});
|
||||
});
|
||||
|
||||
it('skip quando já tem cache e !force', async () => {
|
||||
const { loadPatients, patients } = setup();
|
||||
patients.value = [{ id: 'cached' }];
|
||||
await loadPatients(false);
|
||||
expect(patients.value).toEqual([{ id: 'cached' }]);
|
||||
});
|
||||
|
||||
it('error → setta pacientesError e zera lista', async () => {
|
||||
const { loadPatients, patients, pacientesError } = setup();
|
||||
resetPatientsResult({ data: null, error: new Error('boom') });
|
||||
await loadPatients(true);
|
||||
expect(patients.value).toEqual([]);
|
||||
expect(pacientesError.value).toBe('boom');
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyDefaultPrice', () => {
|
||||
it('skip em billingType=particular', () => {
|
||||
const composer = makeComposer();
|
||||
composer.billingType.value = 'particular';
|
||||
const getDefaultPrice = vi.fn(() => 100);
|
||||
const { applyDefaultPrice } = setup({ composer, getDefaultPrice });
|
||||
applyDefaultPrice();
|
||||
expect(composer.form.value.price).toBe(null);
|
||||
});
|
||||
|
||||
it('skip em edição', () => {
|
||||
const composer = makeComposer();
|
||||
composer.billingType.value = 'gratuito';
|
||||
composer.isEdit.value = true;
|
||||
const getDefaultPrice = vi.fn(() => 100);
|
||||
const { applyDefaultPrice } = setup({ composer, getDefaultPrice });
|
||||
applyDefaultPrice();
|
||||
expect(composer.form.value.price).toBe(null);
|
||||
});
|
||||
|
||||
it('aplica em criação + billingType != particular', () => {
|
||||
const composer = makeComposer();
|
||||
composer.billingType.value = 'gratuito';
|
||||
const getDefaultPrice = vi.fn(() => 75);
|
||||
const { applyDefaultPrice } = setup({ composer, getDefaultPrice });
|
||||
applyDefaultPrice();
|
||||
expect(composer.form.value.price).toBe(75);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Watcher: form.commitment_id (auto-fill price)', () => {
|
||||
it('dispara em criação + visível: ensureServices + applyDefaultPrice', async () => {
|
||||
const composer = makeComposer();
|
||||
// applyDefaultPrice skip se billingType=particular (default); usar 'gratuito'
|
||||
composer.billingType.value = 'gratuito';
|
||||
const getDefaultPrice = vi.fn(() => 80);
|
||||
const { loadServices, composer: c } = setup({ composer, getDefaultPrice });
|
||||
c.form.value.commitment_id = 'c-1';
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(loadServices).toHaveBeenCalled();
|
||||
expect(c.form.value.price).toBe(80);
|
||||
});
|
||||
|
||||
it('NÃO dispara em edição', async () => {
|
||||
const composer = makeComposer();
|
||||
composer.isEdit.value = true;
|
||||
const { loadServices } = setup({ composer });
|
||||
composer.form.value.commitment_id = 'c-1';
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(loadServices).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('NÃO dispara quando dialog fechado (!visible)', async () => {
|
||||
const composer = makeComposer();
|
||||
composer.visible.value = false;
|
||||
const { loadServices } = setup({ composer });
|
||||
composer.form.value.commitment_id = 'c-1';
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(loadServices).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Watcher: form.insurance_plan_id', () => {
|
||||
it('seleciona convênio: limpa items + servicePickerSel', async () => {
|
||||
const composer = makeComposer();
|
||||
const items = ref([{ service_id: 'a' }]);
|
||||
const sps = ref('svc-x');
|
||||
const { commitmentItems, servicePickerSel } = setup({
|
||||
composer,
|
||||
commitmentItems: items,
|
||||
servicePickerSel: sps
|
||||
});
|
||||
composer.form.value.insurance_plan_id = 'plan-1';
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(commitmentItems.value).toEqual([]);
|
||||
expect(servicePickerSel.value).toBe(null);
|
||||
});
|
||||
|
||||
it('desmarca convênio: limpa insurance_value e guide', async () => {
|
||||
const composer = makeComposer({
|
||||
formExtra: { insurance_value: 100, insurance_guide_number: 'X' }
|
||||
});
|
||||
const { composer: c } = setup({ composer });
|
||||
c.form.value.insurance_plan_id = 'plan-1';
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
c.form.value.insurance_plan_id = null;
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(c.form.value.insurance_value).toBe(null);
|
||||
expect(c.form.value.insurance_guide_number).toBe(null);
|
||||
});
|
||||
|
||||
it('ignorado quando _restoringConvenio', async () => {
|
||||
const composer = makeComposer();
|
||||
const actions = makeActions();
|
||||
actions._restoringConvenio.value = true;
|
||||
const items = ref([{ service_id: 'a' }]);
|
||||
const { commitmentItems } = setup({
|
||||
composer,
|
||||
actions,
|
||||
commitmentItems: items
|
||||
});
|
||||
composer.form.value.insurance_plan_id = 'plan-1';
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
// Restoração ativa: não toca em commitmentItems
|
||||
expect(commitmentItems.value).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_loadCommitmentItemsForEvent', () => {
|
||||
it('sem eventId nem ruleId: limpa items, billingType=particular', async () => {
|
||||
const composer = makeComposer();
|
||||
const items = ref([{ service_id: 'a' }]);
|
||||
const { _loadCommitmentItemsForEvent, commitmentItems } = setup({
|
||||
composer,
|
||||
commitmentItems: items
|
||||
});
|
||||
await _loadCommitmentItemsForEvent(null);
|
||||
expect(commitmentItems.value).toEqual([]);
|
||||
expect(composer.billingType.value).toBe('particular');
|
||||
});
|
||||
|
||||
it('eventRow com insurance_plan_id: aplica convenio', async () => {
|
||||
const composer = makeComposer();
|
||||
const props = {
|
||||
ownerId: 'owner-1',
|
||||
tenantId: 't-1',
|
||||
agendaSettings: { session_duration_min: 50 },
|
||||
eventRow: {
|
||||
insurance_plan_id: 'plan-1',
|
||||
insurance_guide_number: 'G-1',
|
||||
insurance_value: 200,
|
||||
insurance_plan_service_id: 'ps-1'
|
||||
}
|
||||
};
|
||||
const { _loadCommitmentItemsForEvent } = setup({ composer }, props);
|
||||
await _loadCommitmentItemsForEvent('evt-1');
|
||||
// Espera nextTick interno
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
expect(composer.billingType.value).toBe('convenio');
|
||||
expect(composer.form.value.insurance_plan_id).toBe('plan-1');
|
||||
expect(composer.form.value.insurance_value).toBe(200);
|
||||
});
|
||||
|
||||
it('com items carregados: billingType=particular', async () => {
|
||||
const composer = makeComposer();
|
||||
const _csLoadItems = vi.fn().mockResolvedValue([{ service_id: 's-1', final_price: 100 }]);
|
||||
const { _loadCommitmentItemsForEvent, commitmentItems } = setup({
|
||||
composer,
|
||||
_csLoadItems
|
||||
});
|
||||
await _loadCommitmentItemsForEvent('evt-1');
|
||||
expect(commitmentItems.value).toHaveLength(1);
|
||||
expect(composer.billingType.value).toBe('particular');
|
||||
});
|
||||
|
||||
it('sem items: billingType=gratuito', async () => {
|
||||
const composer = makeComposer();
|
||||
const _csLoadItems = vi.fn().mockResolvedValue([]);
|
||||
const { _loadCommitmentItemsForEvent } = setup({ composer, _csLoadItems });
|
||||
await _loadCommitmentItemsForEvent('evt-1');
|
||||
expect(composer.billingType.value).toBe('gratuito');
|
||||
});
|
||||
|
||||
it('error: items=[], billingType=gratuito', async () => {
|
||||
const composer = makeComposer();
|
||||
const _csLoadItems = vi.fn().mockRejectedValue(new Error('boom'));
|
||||
const { _loadCommitmentItemsForEvent, commitmentItems } = setup({
|
||||
composer,
|
||||
_csLoadItems
|
||||
});
|
||||
await _loadCommitmentItemsForEvent('evt-1');
|
||||
expect(commitmentItems.value).toEqual([]);
|
||||
expect(composer.billingType.value).toBe('gratuito');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureServicesLoaded', () => {
|
||||
it('carrega só uma vez (gate)', async () => {
|
||||
const { ensureServicesLoaded, loadServices } = setup();
|
||||
await ensureServicesLoaded();
|
||||
await ensureServicesLoaded();
|
||||
await ensureServicesLoaded();
|
||||
expect(loadServices).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('skip sem ownerId', async () => {
|
||||
const { ensureServicesLoaded, loadServices } = setup({}, { ownerId: '' });
|
||||
await ensureServicesLoaded();
|
||||
expect(loadServices).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resetServicesGate permite re-load', async () => {
|
||||
const { ensureServicesLoaded, resetServicesGate, loadServices } = setup();
|
||||
await ensureServicesLoaded();
|
||||
resetServicesGate();
|
||||
await ensureServicesLoaded();
|
||||
expect(loadServices).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user