Files
InsanityLab-web/apps/platforms/tests/longevity/pagine.test.ts
T
Adriano 948068b312 longevity: index.astro smista per ruolo, test del gestionale
pages/longevity/index.astro: trainer e admin → /longevity/gestionale, cliente →
/longevity/io come prima (l'assert esistente sul redirect a /longevity/io resta
valido, il ramo cliente lo chiama ancora testualmente).
tests/longevity/pagine.test.ts: le quattro pagine del gestionale esistono, usano
variante="gestionale", non toccano il DB direttamente e non nominano il database
anagrafico riservato; index.astro smista per ruolo.

Refs #2
2026-09-26 13:05:52 +00:00

135 lines
6.6 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['"`]/);
});
});
describe('il gestionale del trainer (Unità 2b)', () => {
const PAGINE_GESTIONALE = [
'src/pages/longevity/gestionale/index.astro',
'src/pages/longevity/gestionale/nuovo.astro',
'src/pages/longevity/gestionale/cliente/[code].astro',
'src/pages/longevity/gestionale/cliente/[code]/checkup.astro',
];
it('ogni pagina del gestionale esiste e usa la variante "gestionale" del layout', () => {
for (const p of PAGINE_GESTIONALE) {
expect(existsSync(join(process.cwd(), p))).toBe(true);
expect(leggi(p)).toMatch(/variante=["']gestionale["']/);
}
});
it('nessuna pagina del gestionale interroga il database direttamente', () => {
// Il gestionale legge sempre attraverso lib/longevity (anagrafica, gestionale, vista):
// una .prepare( dentro una .astro sarebbe una query scritta a mano nella pagina.
for (const p of PAGINE_GESTIONALE) {
expect(leggi(p)).not.toMatch(/\.prepare\(/);
}
});
it('nessuna pagina del gestionale nomina il database anagrafico riservato', () => {
for (const p of PAGINE_GESTIONALE) {
expect(leggi(p)).not.toMatch(/Identity/);
}
});
it('index.astro smista per ruolo: trainer e admin al gestionale, il cliente al proprio referto', () => {
const src = leggi('src/pages/longevity/index.astro');
expect(src).toMatch(/Astro\.redirect\(\s*['"`]\/longevity\/gestionale['"`]/);
expect(src).toMatch(/trainer/);
expect(src).toMatch(/admin/);
expect(src).toMatch(/Astro\.redirect\(\s*['"`]\/longevity\/io['"`]/);
});
});