510d7ca4ec
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
97 lines
5.1 KiB
TypeScript
97 lines
5.1 KiB
TypeScript
// 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['"`]/);
|
|
});
|
|
});
|