289 lines
11 KiB
JavaScript
289 lines
11 KiB
JavaScript
/**
|
|
* 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)
|
|
})
|
|
})
|