// tests/longevity/pagine.test.ts import { describe, it, expect } from 'vitest'; import { readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; const leggi = (p: string) => readFileSync(join(process.cwd(), p), 'utf8'); describe('il guscio della piattaforma', () => { it('il layout esiste e non monta header e footer pubblici', () => { const l = leggi('src/layouts/Longevity.astro'); expect(l).not.toMatch(/import\s+Header/); expect(l).not.toMatch(/import\s+Footer/); expect(l).toMatch(/noindex/); // area riservata: mai indicizzata }); it('il foglio di stile e globale e prefissato, non scoped', () => { expect(existsSync(join(process.cwd(), 'src/styles/longevity.css'))).toBe(true); const css = leggi('src/styles/longevity.css'); const classi = [...css.matchAll(/^\.([a-z-]+)/gm)].map((m) => m[1]); expect(classi.length).toBeGreaterThan(3); // "lg" da solo o "lg-qualcosa": il solo startsWith('lg') lascerebbe passare anche "lgfoo". expect(classi.every((c) => c === 'lg' || c.startsWith('lg-'))).toBe(true); }); it('usa i token del sito invece di inventare colori', () => { const css = leggi('src/styles/longevity.css'); expect(css).toMatch(/var\(--c-/); }); it('nessuna variabile porta un nome da semaforo (verde/buono/ok)', () => { const css = leggi('src/styles/longevity.css').toLowerCase(); // il mattone del fuori range e ammesso; un verde "tutto bene" no expect(css).not.toMatch(/--lg-ok|--lg-buono|--lg-verde/); }); it('in tutto il foglio compaiono solo i colori ammessi, salvo le eccezioni dichiarate', () => { // Il test sopra cerca nomi vietati: chi scrivesse un verde in esadecimale, o lo // chiamasse con un nome innocuo (var(--lg-buona-salute)), passerebbe indisturbato. // Qui si fa il contrario: si elenca ciò che PUÒ comparire, e qualunque altro colore — // letterale (esadecimale, rgb(), nome CSS) o var() con un altro nome — è una // regressione. // // PRIMA questo test filtrava le regole sul selettore ("solo quelle con 'lg-score' nel // nome"): un elenco di QUALI regole guardare invecchia appena nasce una classe nuova — // è successo con .lg-radar__voce-nome, .lg-radar__intro, .lg-macro__nota (Task 3): non // contengono la stringa "lg-score", quindi non venivano MAI controllate, in silenzio. // Il foglio è interamente prefissato `lg-` per costruzione (verificato dal test sopra): // non serve scegliere quali regole guardare, si guardano TUTTE, e le eccezioni // legittime si dichiarano una per una — selettore, valore esatto, motivo — così un // colore fuori lista entra solo con una decisione visibile, mai con un buco nel filtro. const css = leggi('src/styles/longevity.css'); const AMMESSI = new Set([ '--c-accent', '--c-accent-dark', // l'accento del sito: l'unica leva per l'intensità '--c-heading', '--c-text', '--c-text-light', // testo neutro, uguale ovunque nel sito '--c-bg', '--c-bg-alt', // sfondi neutri '--lg-line', '--lg-surface', // struttura locale, neutra '--lg-tratteggio', // il perimetro di ciò che non sappiamo '--lg-fuori-range', // SOLO una misura sospetta, mai un punteggio ]); // Eccezioni dichiarate: selettore esatto + valore esatto + perché non è un giudizio sul // punteggio. Ogni riga qui è una decisione presa a vista, non un buco silenzioso. const ECCEZIONI: { selettore: string; valore: string; motivo: string }[] = [ { selettore: '.lg-form__nav button:last-child:not(:only-child)', valore: '#fff', motivo: "testo bianco per contrasto sul bottone pieno d'accento — non un colore di punteggio", }, ]; const NON_COLORE = /^(transparent|inherit|initial|unset|currentcolor|none)$/i; const PROP_COLORE = /^(color|background|background-color|border(-\w+)?-color|outline-color|fill|stroke)$/i; const violazioni: string[] = []; for (const regola of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) { const selettore = regola[1].trim(); for (const dich of regola[2].split(';')) { const due = dich.indexOf(':'); if (due < 0) continue; const prop = dich.slice(0, due).trim().toLowerCase(); const val = dich.slice(due + 1).trim(); if (!PROP_COLORE.test(prop) || val === '' || NON_COLORE.test(val)) continue; const varMatch = val.match(/^var\(\s*(--[a-z0-9-]+)\s*(?:,.*)?\)$/i); if (varMatch && AMMESSI.has(varMatch[1])) continue; if (ECCEZIONI.some((e) => e.selettore === selettore && e.valore === val)) continue; violazioni.push(`${selettore} { ${prop}: ${val} }`); } } expect(violazioni).toEqual([]); }); it('la rotta del cliente e sotto /longevity, e rimanda davvero al referto', () => { const p = join(process.cwd(), 'src/pages/longevity/index.astro'); expect(existsSync(p)).toBe(true); const src = readFileSync(p, 'utf8'); expect(src).toMatch(/Astro\.redirect\(\s*['"`]\/longevity\/io['"`]/); }); });