import { describe, it, expect } from 'vitest'; import { avvisi, avvisiAttivi, calendari, calendarioDaMostrare, DISCIPLINE, GIORNI, type Giorno } from '../src/data/calendari'; const d = (iso: string) => new Date(iso); describe('calendarioDaMostrare', () => { it('in vigore: mostra quello del periodo', () => { expect(calendarioDaMostrare(d('2026-09-05T10:00:00+02:00'))).toMatchObject({ cal: { id: 'settembre-1-18' }, futuro: false, }); expect(calendarioDaMostrare(d('2026-09-25T10:00:00+02:00'))).toMatchObject({ cal: { id: 'settembre-21-in-poi' }, futuro: false, }); // `to: null` = a tempo indeterminato: fra un anno vale ancora quello. expect(calendarioDaMostrare(d('2027-03-01T10:00:00+02:00'))).toMatchObject({ cal: { id: 'settembre-21-in-poi' }, futuro: false, }); }); // Il link esiste prima che il primo orario cominci: ad agosto deve mostrare gli orari di // settembre dichiarando che entrano dopo, non un "nessun calendario" che non dice niente. it('prima dell\'inizio: mostra il prossimo, marcato come futuro', () => { expect(calendarioDaMostrare(d('2026-08-17T10:00:00+02:00'))).toMatchObject({ cal: { id: 'settembre-1-18' }, futuro: true, }); }); // 19 e 20 settembre non stanno in nessuna griglia perché quei due giorni non hanno class: // il 19 c'è la presentazione della nuova apertura, il 20 si sta chiusi (Adriano, 17/08). // La griglia mostrata è quindi quella del 21, marcata futura, e l'informazione sui due // giorni arriva dagli avvisi qui sotto. it('nei due giorni fuori griglia (19-20 settembre) mostra il prossimo', () => { expect(calendarioDaMostrare(d('2026-09-19T10:00:00+02:00'))).toMatchObject({ cal: { id: 'settembre-21-in-poi' }, futuro: true, }); expect(calendarioDaMostrare(d('2026-09-20T23:00:00+02:00'))).toMatchObject({ cal: { id: 'settembre-21-in-poi' }, futuro: true, }); }); it('confini al secondo, in ora italiana', () => { expect(calendarioDaMostrare(d('2026-09-18T23:59:59+02:00'))?.cal.id).toBe('settembre-1-18'); expect(calendarioDaMostrare(d('2026-09-21T00:00:00+02:00'))).toMatchObject({ cal: { id: 'settembre-21-in-poi' }, futuro: false, }); }); }); describe('giornate fuori griglia', () => { it('la presentazione e la chiusura si vedono prima di arrivarci', () => { const oggi = avvisiAttivi(d('2026-08-17T12:00:00+02:00')).map((a) => a.id); expect(oggi).toEqual(['presentazione-19-settembre', 'chiusura-20-settembre']); }); // Un avviso passato è peggio di nessun avviso: dice che il centro è chiuso un giorno che è // già trascorso, e chi legge non sa più se valga per quest'anno o per il prossimo. it('ogni avviso sparisce da solo quando è passato', () => { expect(avvisiAttivi(d('2026-09-19T23:59:59+02:00')).map((a) => a.id)) .toEqual(['presentazione-19-settembre', 'chiusura-20-settembre']); expect(avvisiAttivi(d('2026-09-20T00:00:00+02:00')).map((a) => a.id)) .toEqual(['chiusura-20-settembre']); expect(avvisiAttivi(d('2026-09-21T00:00:00+02:00'))).toEqual([]); }); it('ogni avviso ha una scadenza leggibile e un testo', () => { for (const a of avvisi) { expect(Number.isNaN(Date.parse(a.to)), `${a.id}: scadenza illeggibile`).toBe(false); if (a.from !== null) expect(Date.parse(a.to)).toBeGreaterThanOrEqual(Date.parse(a.from)); expect(a.quando.length).toBeGreaterThan(0); expect(a.testo.length).toBeGreaterThan(0); } }); }); describe('coerenza dei calendari trascritti', () => { for (const c of calendari) { describe(c.id, () => { it('la finestra è leggibile e ha un verso', () => { expect(Number.isNaN(Date.parse(c.from))).toBe(false); if (c.to !== null) { expect(Number.isNaN(Date.parse(c.to))).toBe(false); expect(Date.parse(c.to)).toBeGreaterThan(Date.parse(c.from)); } }); it('le fasce orarie sono uniche e in ordine crescente', () => { const ore = c.righe.map((r) => r.ora); expect(new Set(ore).size, 'fascia ripetuta').toBe(ore.length); const minuti = ore.map((o) => { const [h, m] = o.split('.'); return Number(h) * 60 + Number(m); }); expect(minuti.every((v, i) => i === 0 || v > minuti[i - 1]), `fasce fuori ordine: ${ore.join(' ')}`).toBe(true); }); it('ogni cella sta su un giorno esistente e porta una disciplina nota', () => { for (const r of c.righe) { for (const [giorno, disciplina] of Object.entries(r.celle)) { expect(GIORNI, `${r.ora}: giorno ${giorno} inesistente`).toContain(giorno as Giorno); expect(DISCIPLINE, `${r.ora} ${giorno}: disciplina ${disciplina} sconosciuta`).toContain(disciplina!); } } }); // Una lezione in un giorno di chiusura è la contraddizione che nessuno nota leggendo la // griglia: la colonna è barrata e la cella ci finisce dentro lo stesso. it('nessuna lezione cade in un giorno di chiusura', () => { for (const r of c.righe) { for (const g of c.chiusi) { expect(r.celle[g], `${r.ora}: lezione di ${g}, che è chiuso`).toBeUndefined(); } } }); it('le note dei giorni si riferiscono a giorni aperti', () => { for (const g of Object.keys(c.noteGiorno ?? {}) as Giorno[]) { expect(GIORNI).toContain(g); expect(c.chiusi, `nota su ${g}, che è dichiarato chiuso`).not.toContain(g); } }); }); } });