6d9b36d592
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>
612 lines
23 KiB
JavaScript
612 lines
23 KiB
JavaScript
/**
|
|
* 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);
|
|
});
|
|
});
|