/** * agendaMappers.spec.js * * Testa as funções de mapeamento de dados da agenda: * - mapAgendaEventosToCalendarEvents * - mapAgendaEventosToClinicResourceEvents * - buildNextSessions * - minutesToDuration * - tituloFallback * - calcDefaultSlotDuration * - buildWeeklyBreakBackgroundEvents */ import { describe, it, expect } from 'vitest'; import { mapAgendaEventosToCalendarEvents, mapAgendaEventosToClinicResourceEvents, buildNextSessions, minutesToDuration, tituloFallback, calcDefaultSlotDuration, buildWeeklyBreakBackgroundEvents } from '../agendaMappers.js'; // ─── fixtures ───────────────────────────────────────────────────────────────── function evento(overrides = {}) { return { id: 'ev-1', titulo: 'Sessão Teste', tipo: 'sessao', status: 'agendado', inicio_em: '2026-03-10T09:00:00', fim_em: '2026-03-10T10:00:00', owner_id: 'owner-1', tenant_id: 'tenant-1', patient_id: 'patient-1', modalidade: 'presencial', ...overrides }; } // ─── mapAgendaEventosToCalendarEvents ───────────────────────────────────────── describe('mapAgendaEventosToCalendarEvents', () => { it('mapeia um evento simples para o shape do FullCalendar', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento()]); expect(ev.id).toBe('ev-1'); expect(ev.start).toBe('2026-03-10T09:00:00'); expect(ev.end).toBe('2026-03-10T10:00:00'); expect(ev.extendedProps.tipo).toBe('sessao'); expect(ev.extendedProps.status).toBe('agendado'); }); it('filtra rows null/undefined', () => { const result = mapAgendaEventosToCalendarEvents([null, undefined, evento()]); expect(result.length).toBe(1); }); it('retorna array vazio para input vazio', () => { expect(mapAgendaEventosToCalendarEvents([])).toEqual([]); expect(mapAgendaEventosToCalendarEvents(null)).toEqual([]); }); it('inclui ícone ✓ no título para status realizado', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ status: 'realizado' })]); expect(ev.title).toContain('✓'); }); it('inclui ícone ✗ no título para status faltou', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ status: 'faltou' })]); expect(ev.title).toContain('✗'); }); it('inclui ícone ∅ no título para status cancelado', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ status: 'cancelado' })]); expect(ev.title).toContain('∅'); }); it('inclui ícone ↺ no título para status remarcado', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ status: 'remarcado' })]); expect(ev.title).toContain('↺'); }); it('inclui ícone ↻ para ocorrências de série', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ recurrence_id: 'rule-1', is_occurrence: true })]); expect(ev.title).toContain('↻'); }); it('aplica cor de fundo para status faltou', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ status: 'faltou' })]); expect(ev.backgroundColor).toBe('#ef4444'); }); it('aplica cor de fundo para status cancelado', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ status: 'cancelado' })]); expect(ev.backgroundColor).toBe('#f97316'); }); it('aplica cor de fundo para status remarcado', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ status: 'remarcado' })]); expect(ev.backgroundColor).toBe('#a855f7'); }); it('usa titulo_custom quando disponível', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ titulo_custom: 'Personalizado' })]); expect(ev.title).toContain('Personalizado'); }); it('usa nome do paciente via patients join quando titulo ausente', () => { const [ev] = mapAgendaEventosToCalendarEvents([ evento({ titulo: null, titulo_custom: null, patients: { nome_completo: 'João Silva', avatar_url: null } }) ]); expect(ev.title).toContain('João Silva'); }); it('mapeia patient_id corretamente', () => { const [ev] = mapAgendaEventosToCalendarEvents([evento({ patient_id: 'p-123' })]); expect(ev.extendedProps.patient_id).toBe('p-123'); expect(ev.extendedProps.paciente_id).toBe('p-123'); // alias }); it('mapeia recurrence_id e original_date', () => { const [ev] = mapAgendaEventosToCalendarEvents([ evento({ recurrence_id: 'rule-abc', original_date: '2026-03-10' }) ]); expect(ev.extendedProps.recurrence_id).toBe('rule-abc'); expect(ev.extendedProps.original_date).toBe('2026-03-10'); }); it('mapeia exception_type', () => { const [ev] = mapAgendaEventosToCalendarEvents([ evento({ exception_type: 'patient_missed', status: 'faltou' }) ]); expect(ev.extendedProps.exception_type).toBe('patient_missed'); expect(ev.extendedProps.is_exception).toBe(true); }); }); // ─── mapAgendaEventosToClinicResourceEvents ─────────────────────────────────── describe('mapAgendaEventosToClinicResourceEvents', () => { it('adiciona resourceId baseado em owner_id', () => { const [ev] = mapAgendaEventosToClinicResourceEvents([evento({ owner_id: 'owner-99' })]); expect(ev.resourceId).toBe('owner-99'); }); it('usa terapeuta_id como fallback para resourceId', () => { const [ev] = mapAgendaEventosToClinicResourceEvents([evento({ owner_id: null, terapeuta_id: 'tera-1' })]); expect(ev.resourceId).toBe('tera-1'); }); }); // ─── buildNextSessions ──────────────────────────────────────────────────────── describe('buildNextSessions', () => { it('filtra sessões no passado', () => { const now = new Date('2026-03-10T12:00:00'); const rows = [evento({ id: 'past', fim_em: '2026-03-09T10:00:00' }), evento({ id: 'future', fim_em: '2026-03-11T10:00:00' })]; const result = buildNextSessions(rows, now); expect(result.length).toBe(1); expect(result[0].id).toBe('future'); }); it('inclui sessão cujo fim_em é agora (mesmo ms)', () => { const now = new Date('2026-03-10T10:00:00'); const rows = [evento({ fim_em: '2026-03-10T10:00:00' })]; const result = buildNextSessions(rows, now); expect(result.length).toBe(1); }); it('limita a 6 sessões', () => { const now = new Date('2026-01-01'); const rows = Array.from({ length: 10 }, (_, i) => evento({ id: `ev-${i}`, fim_em: `2026-03-${String(i + 10).padStart(2, '0')}T10:00:00` }) ); const result = buildNextSessions(rows, now); expect(result.length).toBe(6); }); it('retorna shape correto', () => { const now = new Date('2026-01-01'); const [s] = buildNextSessions([evento()], now); expect(s).toMatchObject({ id: 'ev-1', title: 'Sessão Teste', startISO: '2026-03-10T09:00:00', endISO: '2026-03-10T10:00:00', tipo: 'sessao', status: 'agendado' }); }); it('mapeia pacienteId de patient_id', () => { const now = new Date('2026-01-01'); const [s] = buildNextSessions([evento({ patient_id: 'p-999' })], now); expect(s.pacienteId).toBe('p-999'); }); }); // ─── minutesToDuration ──────────────────────────────────────────────────────── describe('minutesToDuration', () => { it('30 minutos → 00:30:00', () => expect(minutesToDuration(30)).toBe('00:30:00')); it('60 minutos → 01:00:00', () => expect(minutesToDuration(60)).toBe('01:00:00')); it('90 minutos → 01:30:00', () => expect(minutesToDuration(90)).toBe('01:30:00')); it('0 minutos → 00:00:00', () => expect(minutesToDuration(0)).toBe('00:00:00')); }); // ─── tituloFallback ─────────────────────────────────────────────────────────── describe('tituloFallback', () => { it('sessao → Sessão', () => expect(tituloFallback('sessao')).toBe('Sessão')); it('bloqueio → Bloqueio', () => expect(tituloFallback('bloqueio')).toBe('Bloqueio')); it('pessoal → Pessoal', () => expect(tituloFallback('pessoal')).toBe('Pessoal')); it('clinica → Clínica', () => expect(tituloFallback('clinica')).toBe('Clínica')); it('desconhecido → Compromisso', () => expect(tituloFallback('outro')).toBe('Compromisso')); it('null → Compromisso', () => expect(tituloFallback(null)).toBe('Compromisso')); }); // ─── calcDefaultSlotDuration ────────────────────────────────────────────────── describe('calcDefaultSlotDuration', () => { it('usa granularidade custom quando ativa', () => { const s = { usar_granularidade_custom: true, granularidade_min: 15 }; expect(calcDefaultSlotDuration(s)).toBe('00:15:00'); }); it('usa admin_slot_visual_minutos como fallback', () => { const s = { admin_slot_visual_minutos: 20 }; expect(calcDefaultSlotDuration(s)).toBe('00:20:00'); }); it('usa 30 min como padrão quando nenhuma configuração', () => { expect(calcDefaultSlotDuration({})).toBe('00:30:00'); expect(calcDefaultSlotDuration(null)).toBe('00:30:00'); }); }); // ─── buildWeeklyBreakBackgroundEvents ──────────────────────────────────────── describe('buildWeeklyBreakBackgroundEvents', () => { it('retorna vazio para input vazio', () => { const result = buildWeeklyBreakBackgroundEvents([], new Date('2026-03-01'), new Date('2026-03-08')); expect(result).toEqual([]); }); it('gera eventos de background para pausa no dia correto', () => { const pausas = [{ weekday: 1, start: '12:00', end: '13:00', label: 'Almoço' }]; // segunda const result = buildWeeklyBreakBackgroundEvents( pausas, new Date(2026, 2, 1), // dom new Date(2026, 2, 8) // dom ); expect(result.length).toBe(1); expect(result[0].display).toBe('background'); expect(result[0].start).toContain('2026-03-02'); // segunda expect(result[0].extendedProps.label).toBe('Almoço'); }); it('gera uma pausa por semana quando range cobre 2 semanas', () => { const pausas = [{ weekday: 1, start: '12:00', end: '13:00' }]; // toda segunda const result = buildWeeklyBreakBackgroundEvents( pausas, new Date(2026, 2, 1), // dom 01/03 new Date(2026, 2, 15) // dom 15/03 ); expect(result.length).toBe(2); // seg 02 e seg 09 }); it('não gera para dias diferentes', () => { const pausas = [{ weekday: 5, start: '12:00', end: '13:00' }]; // sexta const result = buildWeeklyBreakBackgroundEvents( pausas, new Date(2026, 2, 2), // seg new Date(2026, 2, 5) // qui ); expect(result.length).toBe(0); }); });