Files
CRAPP/test/unit/obiettivi-dettaglio.test.ts
davideandClaude Sonnet 5.5 c2c3b249f2 Obiettivi cliccabili con card di dettaglio; presenze del mese sui convocati
Ogni obiettivo di squadra apre un drawer che spiega come si calcola, cosa conta,
cosa è una presenza persa, cosa resta fuori, periodo, fonte dei dati ed esempio.
«90% di presenze del mese» usa ora i convocati dell'evento come posti (tutta la
rosa se non ce ne sono), come le presenze collettive: chi non è convocato non
abbassa più la percentuale. Test unit, card e integrazione; docs aggiornate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 11:34:58 +02:00

107 lines
4.8 KiB
TypeScript

/** Check della card di dettaglio degli obiettivi: `bun test/unit/obiettivi-dettaglio.test.ts`. */
import assert from "node:assert/strict";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { SchedaObiettivo } from "@/components/crapp/ObiettivoDrawer";
import { Drawer } from "@/components/ui/drawer";
import { giocatori } from "@/lib/crapp-data";
import { contestoVuoto, obiettiviSquadra, type ObiettivoSquadra } from "@/lib/obiettivi";
const OGGI = new Date("2026-08-15T10:00:00Z");
const tutti = obiettiviSquadra(giocatori, contestoVuoto, OGGI);
/** Rende la scheda dentro un Drawer aperto, come fa l'app (titolo e descrizione ne hanno bisogno). */
const scheda = (o: ObiettivoSquadra) =>
renderToStaticMarkup(
createElement(Drawer, { open: true }, createElement(SchedaObiettivo, { o })),
);
// --- ogni obiettivo ha tutti i testi, nessun campo vuoto ----------------------------
const ids = tutti.map((o) => o.id).sort();
assert.deepEqual(
ids,
["o1", "o11", "o12", "o13", "o2", "o3", "o4", "o5", "o6", "o7"],
"gli obiettivi sono quelli attesi: se ne aggiungi uno, scrivi il suo dettaglio",
);
for (const o of tutti) {
const d = o.dettaglio;
for (const [nome, testo] of Object.entries({
comeSiCalcola: d.comeSiCalcola,
periodo: d.periodo,
fonte: d.fonte,
esempio: d.esempio,
})) {
assert.ok(testo.trim().length > 20, `${o.id}: «${nome}» troppo corto o vuoto`);
}
for (const voce of [...d.conta, ...d.nonConta, ...(d.perse ?? [])]) {
assert.ok(voce.trim().length > 10, `${o.id}: voce di elenco vuota o troppo corta`);
}
assert.equal(new Set(d.conta).size, d.conta.length, `${o.id}: voci «conta» duplicate`);
assert.equal(new Set(d.nonConta).size, d.nonConta.length, `${o.id}: voci «non conta» duplicate`);
}
// --- «presenza persa» solo sulle due percentuali di presenze ------------------------
for (const o of tutti) {
const haPerse = Boolean(o.dettaglio.perse && o.dettaglio.perse.length > 0);
assert.equal(haPerse, o.id === "o1" || o.id === "o7", `${o.id}: presenza persa solo su o1 e o7`);
}
// --- le due presenze spiegano gli stessi punti chiave -------------------------------
for (const id of ["o1", "o7"]) {
const d = tutti.find((o) => o.id === id)!.dettaglio;
const tutto = [d.comeSiCalcola, ...d.conta, ...(d.perse ?? []), ...d.nonConta].join(" ");
for (const parola of ["convocat", "presente", "in ritardo", "assente", "cene"]) {
assert.ok(tutto.toLowerCase().includes(parola), `${id}: la spiegazione non cita «${parola}»`);
}
assert.ok(
d.nonConta.some((v) => /non è convocato/i.test(v)),
`${id}: dice che il non convocato non pesa`,
);
}
const mese = tutti.find((o) => o.id === "o1")!.dettaglio;
const totale = tutti.find((o) => o.id === "o7")!.dettaglio;
assert.ok(/mese/i.test(mese.periodo) && /azzera/i.test(mese.periodo), "o1: si azzera ogni mese");
assert.ok(/stagione/i.test(totale.periodo) && /mai/i.test(totale.periodo), "o7: non si azzera mai");
// --- la card renderizzata mostra tutte le sezioni ------------------------------------
const o1 = tutti.find((o) => o.id === "o1")!;
const html1 = scheda(o1);
for (const titolo of [
"Stato attuale",
"Come si calcola",
"Cosa conta",
"Presenza persa",
"Non entra nel calcolo",
"Quando vale",
"Da dove arrivano i dati",
"Un esempio",
]) {
assert.ok(html1.includes(titolo), `la card di o1 non mostra «${titolo}»`);
}
assert.ok(html1.includes(o1.titolo.replace(/&/g, "&amp;")), "titolo");
assert.ok(html1.includes(o1.dettaglio.comeSiCalcola), "testo «come si calcola»");
assert.ok(html1.includes(o1.impatto), "frase di impatto");
assert.ok(html1.includes("entro il"), "o1 ha una scadenza mensile");
assert.ok(
!html1.includes("Cosa non conta"),
"con «presenza persa» la sezione si chiama diversamente",
);
// Un obiettivo senza «presenza persa» ha «Cosa non conta» e nessuna sezione in più.
const o12 = tutti.find((o) => o.id === "o12")!;
const html12 = scheda(o12);
assert.ok(html12.includes("Cosa non conta"), "o12: «Cosa non conta»");
assert.ok(!html12.includes("Presenza persa"), "o12: nessuna «presenza persa»");
assert.ok(!html12.includes("Non entra nel calcolo"), "o12: nessun «non entra nel calcolo»");
assert.ok(!html12.includes("entro il"), "o12 non ha scadenza");
// --- stato: in corso / completato, e i numeri mostrati --------------------------------
const finto = (valore: number): ObiettivoSquadra => ({ ...o12, valore, target: 10 });
assert.ok(scheda(finto(4)).includes("In corso"), "sotto il target: in corso");
assert.ok(scheda(finto(4)).includes("40%"), "percentuale mostrata");
assert.ok(scheda(finto(10)).includes("Completato"), "al target: completato");
assert.ok(!scheda(finto(10)).includes("In corso"), "al target non è più in corso");
console.log("obiettivi-dettaglio: ok");