01f49ab6d2
Le due griglie fornite dal cliente (1-18 settembre, dal 21 in poi) sono trascritte come dato e ridisegnate nello stile del sito: si leggono da telefono, si stampano in PDF e per correggere una cella non serve rifare uno screenshot. Quale griglia compare lo decide la data, come per le edizioni della promo: quella in vigore, oppure - se nessuna lo e' ancora - la prossima, dichiarando da quando entra. Serve perche' il link esiste da subito e ad agosto un "nessun calendario" non direbbe niente a chi vuole gli orari di settembre. Il popup e' un <dialog> nativo: fondale, Esc e trappola del focus senza librerie. "Scarica in PDF" stampa la sola griglia, gia' impaginata in orizzontale dal CSS di stampa. Le discipline hanno una barra colorata a sinistra oltre al fondo, cosi' restano distinguibili anche quando il browser stampa senza sfondi. Quattordici test sul dato: confini al secondo in ora italiana, fasce orarie uniche e in ordine, nessuna lezione in un giorno di chiusura, e il buco vero fra il 19 e il 20 settembre resta dichiarato invece che nascosto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { 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 sono coperti da nessuno dei due fogli del cliente. Il buco è vero e
|
|
// resta dichiarato qui: se un giorno verrà colmato, questo test lo dirà.
|
|
it('nei due giorni scoperti (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('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);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|