Struttura: il sito esistente si sposta sotto apps/sito
Primo passo della separazione sito / piattaforme decisa da Adriano il 03/09: due applicazioni autosufficienti, senza workspace di root, cosi' che ognuna resti un progetto Astro completo ed estraibile. Qui non cambia niente del codice: solo la radice. I pattern del .gitignore che avevano una barra erano ancorati alla radice e dopo lo spostamento non avrebbero piu' coperto niente (data/*.db*, uploads/*): resi validi a qualunque profondita'. Suite: 45 file, 347 test verdi prima e dopo lo spostamento. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user