Files
InsanityLab-web/apps/sito/tests/promo-edizioni.test.ts
T
Adriano Dal Pastro 33091be802 Merge remote-tracking branch 'origin/feat/split-apps' into merge-split
# Conflicts:
#	apps/sito/compose.yaml
2026-09-04 10:09:29 +00:00

113 lines
5.7 KiB
TypeScript

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');
expect(edizioneAttiva(d('2026-09-10T12:00:00+02:00'))?.id).toBe('settembre-2026');
});
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-25T12: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'))?.id).toBe('settembre-2026');
expect(edizioneAttiva(d('2026-09-19T23:59:59+02:00'))?.id).toBe('settembre-2026');
expect(edizioneAttiva(d('2026-09-20T00: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');
expect(edizioneAttiva(d('2026-09-10T12:00:00+02:00'), rovescio)?.id).toBe('settembre-2026');
});
});
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`);
if (c.priceOld) base.push(`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);
}
});
});