f914d0699a
Gli Opening Bundles scadono il 04/09 alle 23:59:59 e dopo di loro non c'era nessuna edizione: dal 5 il sito avrebbe mostrato "Nessuna promozione in corso" mentre le promo erano vive. Adriano ha consegnato le sei creative il 03/09. Edizione `settembre-2026` (5 -> 19 settembre, chiude il giorno dell'inaugurazione): tornano i pacchetti da 5 incontri, con in piu' il Check-up avanzato, che e' una valutazione singola e per questo dice "1 valutazione" invece di "5 incontri". Prezzi 250/150/130/80/70. Due cose che la struttura non prevedeva: - **Nessun prezzo barrato.** Le creative danno il solo prezzo promo, e i listini pieni di agosto (300/220/140/100) sono numeri che nessuno ha riconfermato - riportarli accanto ai nuovi sarebbe stato inventarli. `price-old` diventa opzionale nella CardSpec (`priceOld`) invece di essere un tag vuoto: cosi' la spec dichiara che quella card non ne ha, e il test lo pretende solo dove c'e'. Agosto e i bundle sono marcati `priceOld: true`, quindi restano sotto controllo come prima. - **Cinque card non stanno in una griglia da due o da tre** senza lasciare un buco in fondo. `.cards-5` va a sei colonne con span due, cosi' le ultime due si spostano al centro; a due card per riga sotto i 1000px, a una sotto i 700. Testi ripresi dall'edizione di agosto, gia' approvata dal cliente, cambiando solo cio' che le date rendevano falso: la FAQ delle class diceva "gli orari saranno pubblicati entro Settembre" e adesso e' settembre, quindi rimanda al calendario che esiste. La FAQ 7 sul Check-up e' nuova e va fatta leggere a Nicola prima della pubblicazione. 186 test verdi, `astro check` invariato (i 3 errori della pagina promo sono preesistenti e non toccati). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FPKFABRT3y26dFNMquF3CS
113 lines
5.7 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|