import { describe, it, expect } from 'vitest'; import { edizioni, edizioneAttiva, tagEdizione, type Edizione } from '../src/data/promo-edizioni'; import { seedByTag } from '../src/data/content-seed'; const d = (iso: string) => new Date(iso); describe('edizioneAttiva', () => { it('sceglie l\'edizione la cui finestra contiene l\'istante', () => { expect(edizioneAttiva(d('2026-08-15T12:00:00+02:00'))?.id).toBe('agosto-2026'); expect(edizioneAttiva(d('2026-08-25T12:00:00+02:00'))?.id).toBe('bundle-apertura'); }); it('fuori da ogni finestra non c\'è edizione attiva', () => { expect(edizioneAttiva(d('2026-07-15T12:00:00+02:00'))).toBeNull(); expect(edizioneAttiva(d('2026-09-10T12:00:00+02:00'))).toBeNull(); }); // Il passaggio è il momento in cui il sito cambia da solo: va provato al secondo, e in ora // italiana. Il server gira in UTC, quindi un confronto fatto sull'orologio locale della // macchina anticiperebbe (o ritarderebbe) lo scambio di due ore. it('scambia le edizioni al secondo giusto, in ora italiana', () => { expect(edizioneAttiva(d('2026-08-19T23:59:59+02:00'))?.id).toBe('agosto-2026'); expect(edizioneAttiva(d('2026-08-20T00:00:00+02:00'))?.id).toBe('bundle-apertura'); expect(edizioneAttiva(d('2026-09-04T23:59:59+02:00'))?.id).toBe('bundle-apertura'); expect(edizioneAttiva(d('2026-09-05T00:00:00+02:00'))).toBeNull(); }); it('l\'ordine dell\'elenco non cambia il risultato', () => { const rovescio = [...edizioni].reverse(); expect(edizioneAttiva(d('2026-08-15T12:00:00+02:00'), rovescio)?.id).toBe('agosto-2026'); expect(edizioneAttiva(d('2026-08-25T12:00:00+02:00'), rovescio)?.id).toBe('bundle-apertura'); }); }); describe('coerenza delle edizioni configurate', () => { const ordinate = [...edizioni].sort((a, b) => Date.parse(a.from) - Date.parse(b.from)); it('ogni finestra è valida e ha un verso', () => { for (const e of edizioni) { expect(Number.isNaN(Date.parse(e.from)), `${e.id}: from illeggibile`).toBe(false); expect(Number.isNaN(Date.parse(e.to)), `${e.id}: to illeggibile`).toBe(false); expect(Date.parse(e.to), `${e.id}: finisce prima di cominciare`).toBeGreaterThan(Date.parse(e.from)); } }); // Due promozioni insieme sul sito è il difetto peggiore: prezzi diversi per la stessa cosa, // e nessuno sa quale vale. Nessun buco, invece, evita il giorno in cui la pagina non offre // niente senza che nessuno se ne accorga: se un buco è voluto va scritto qui, non subìto. it('le finestre non si sovrappongono e non lasciano buchi', () => { for (let i = 1; i < ordinate.length; i++) { const prima = ordinate[i - 1]; const dopo = ordinate[i]; const stacco = Date.parse(dopo.from) - Date.parse(prima.to); expect(stacco, `${prima.id} e ${dopo.id} si sovrappongono`).toBeGreaterThan(0); expect(stacco, `fra ${prima.id} e ${dopo.id} c'è un buco`).toBeLessThanOrEqual(1000); } }); // Il countdown è la promessa fatta al visitatore. Se dice una data e lo scambio ne usa // un'altra, il sito si contraddice da solo: o cambia mentre il contatore corre ancora, o // resta lì a zero. Sono lo stesso numero e devono restare lo stesso numero. it('il countdown scade quando finisce l\'edizione', () => { for (const e of edizioni) { const deadline = seedByTag.get(tagEdizione(e, 'countdown.deadline'))?.value; expect(deadline, `${e.id}: manca il tag della deadline`).toBeTruthy(); expect(Date.parse(deadline!), `${e.id}: countdown e fine edizione non coincidono`) .toBe(Date.parse(e.to)); } }); }); describe('i testi di ogni edizione esistono', () => { // Un tag che manca non rompe la pagina: `resolve` restituisce stringa vuota e logga un warn // che nessuno legge. In vetrina diventa una card senza prezzo, o un titolo sparito. Meglio // scoprirlo qui che dal cliente. const richiesti = (e: Edizione): string[] => { const base = ['hero.badge', 'hero.title1', 'hero.accent', 'hero.title2', 'hero.desc', 'hero.cta', 'buy-label', 'validity-note', 'faq.title', 'countdown.label', 'countdown.deadline', 'countdown.unit.days', 'countdown.unit.hours', 'countdown.unit.minutes', 'countdown.unit.seconds']; if (e.launch) base.push('launch.badge', 'launch.title', 'launch.accent', 'launch.desc', 'why5.text'); for (const c of e.cards) { base.push(`card.${c.id}.eyebrow`, `card.${c.id}.title`, `card.${c.id}.price`, `card.${c.id}.price-old`); if (c.featured) base.push(`card.${c.id}.badge`); if (c.unit) base.push(`card.${c.id}.unit`); if (c.question) base.push(`card.${c.id}.question`); if (c.desc) base.push(`card.${c.id}.desc`); if (c.note) base.push(`card.${c.id}.note-label`, `card.${c.id}.note`); for (let i = 1; i <= c.features; i++) base.push(`card.${c.id}.feature.${i}`); } for (const n of e.faq) base.push(`faq.${n}.q`, `faq.${n}.a`); return base.map((s) => tagEdizione(e, s)); }; for (const e of edizioni) { it(`${e.id}: tutti i tag sono nel seed`, () => { const mancanti = richiesti(e).filter((tag) => !seedByTag.has(tag)); expect(mancanti, `tag mancanti: ${mancanti.join(', ')}`).toEqual([]); }); } it('lo stato "nessuna promozione" ha i suoi testi', () => { for (const tag of ['promo.chiusa.title', 'promo.chiusa.desc', 'promo.chiusa.cta']) { expect(seedByTag.has(tag), `manca ${tag}`).toBe(true); } }); });