Files
CRAPP/test/unit/badges.test.ts
T
davideandClaude Sonnet 5 a451d4187f Completa la copertura test del badge Risposta lampo e ne documenta il comportamento
Analisi della logica di serieConferme() senza bug trovati; aggiunte le soglie
esplicite 3/8/15 e i rami mancanti (convocati ristretti, tipo misto, bordo
esatto delle 24h, evento futuro), più un integration test end-to-end che
verifica la mappatura creato_il/risposto_il con dati reali.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 15:07:59 +02:00

239 lines
9.1 KiB
TypeScript

/** Check dei badge e dei gradi: `bun src/lib/badges.test.ts`. */
import assert from "node:assert/strict";
import { giocatori, type Giocatore } from "@/lib/crapp-data";
import {
badgeDefs,
badgeGiocatore,
badgeSegreti,
badgeSegretiSbloccati,
collezioneBadge,
descrizioneSoglie,
gradiOrdine,
gradoRaggiunto,
mancanoPer,
microcopyBadge,
prossimoTraguardo,
segretiNascosti,
statoBadge,
tuttiBadge,
} from "@/lib/badges";
/** Giocatore azzerato: ogni test dichiara solo i valori che gli servono. */
function g(valori: Partial<Giocatore> = {}): Giocatore {
return {
...giocatori[0]!,
presenze: 0,
mvp: 0,
mediaVoto: 0,
votiPagella: 0,
palloni: 0,
cacche: 0,
cacchePartita: 0,
infortuni: 0,
ritardi: 0,
streak: 0,
serieAllenamenti: 0,
seriePartite: 0,
serieConferme: 0,
...valori,
};
}
const mvpDef = badgeDefs.find((b) => b.id === "mvp")!;
// --- gradoRaggiunto: soglie 1 / 3 / 5 ----------------------------------------
assert.equal(gradoRaggiunto(mvpDef, 0), null, "sotto la prima soglia nessun grado");
assert.equal(gradoRaggiunto(mvpDef, 1), "bronzo", "la soglia è inclusiva");
assert.equal(gradoRaggiunto(mvpDef, 2), "bronzo");
assert.equal(gradoRaggiunto(mvpDef, 3), "argento");
assert.equal(gradoRaggiunto(mvpDef, 99), "oro", "oltre l'oro resta oro");
assert.deepEqual(gradiOrdine, ["bronzo", "argento", "oro"]);
// --- statoBadge: progresso verso la soglia successiva ------------------------
const aMeta = statoBadge(mvpDef, g({ mvp: 2 }));
assert.deepEqual(
[aMeta.grado, aMeta.prossimo, aMeta.prossimaSoglia, aMeta.progresso],
["bronzo", "argento", 3, 67],
);
const alMassimo = statoBadge(mvpDef, g({ mvp: 5 }));
assert.deepEqual(
[alMassimo.grado, alMassimo.prossimo, alMassimo.prossimaSoglia],
["oro", null, null],
);
assert.equal(alMassimo.progresso, 100, "raggiunto l'oro il progresso è pieno");
// La media voto usa soglie decimali: non deve arrotondare per eccesso.
const pagella = badgeDefs.find((b) => b.id === "pagella")!;
assert.equal(gradoRaggiunto(pagella, 6.4), null);
assert.equal(gradoRaggiunto(pagella, 6.5), "bronzo");
// Sotto i voti minimi il badge resta bloccato anche con una media altissima: un voto solo
// non deve poter sbloccare/sfilare Pagellone.
assert.equal(
statoBadge(pagella, g({ mediaVoto: 10, votiPagella: 1 })).grado,
null,
"1 voto su 10: non basta, sotto la soglia minima",
);
assert.equal(
statoBadge(pagella, g({ mediaVoto: 10, votiPagella: 4 })).grado,
null,
"4 voti: ancora sotto la soglia minima (5)",
);
assert.equal(
statoBadge(pagella, g({ mediaVoto: 10, votiPagella: 5 })).grado,
"oro",
"5 voti: la soglia minima è raggiunta, la media conta",
);
assert.equal(
statoBadge(pagella, g({ mediaVoto: 6.5, votiPagella: 5 })).grado,
"bronzo",
"sopra la soglia minima, valgono le normali soglie di grado",
);
// --- palloni: soglie 3 / 6 / 10 -----------------------------------------------
const palloniDef = badgeDefs.find((b) => b.id === "palloni")!;
assert.equal(gradoRaggiunto(palloniDef, 2), null, "sotto la prima soglia nessun grado");
assert.equal(gradoRaggiunto(palloniDef, 3), "bronzo", "la soglia è inclusiva");
assert.equal(gradoRaggiunto(palloniDef, 5), "bronzo");
assert.equal(gradoRaggiunto(palloniDef, 6), "argento");
assert.equal(gradoRaggiunto(palloniDef, 9), "argento");
assert.equal(gradoRaggiunto(palloniDef, 10), "oro");
assert.equal(gradoRaggiunto(palloniDef, 99), "oro", "oltre l'oro resta oro");
assert.equal(
statoBadge(palloniDef, g({ palloni: 5 })).progresso,
83,
"5 turni su 6 per l'argento: 83%",
);
// --- presenze: soglie 5 / 15 / 30 ---------------------------------------------
const presenzeDef = badgeDefs.find((b) => b.id === "presenze")!;
assert.equal(gradoRaggiunto(presenzeDef, 4), null, "sotto la prima soglia nessun grado");
assert.equal(gradoRaggiunto(presenzeDef, 5), "bronzo", "la soglia è inclusiva");
assert.equal(gradoRaggiunto(presenzeDef, 14), "bronzo");
assert.equal(gradoRaggiunto(presenzeDef, 15), "argento");
assert.equal(gradoRaggiunto(presenzeDef, 29), "argento");
assert.equal(gradoRaggiunto(presenzeDef, 30), "oro");
assert.equal(gradoRaggiunto(presenzeDef, 999), "oro", "oltre l'oro resta oro");
// --- serie-allenamenti: soglie 3 / 6 / 10 -------------------------------------
const serieAllenamentiDef = badgeDefs.find((b) => b.id === "serie-allenamenti")!;
assert.equal(gradoRaggiunto(serieAllenamentiDef, 2), null, "sotto la prima soglia nessun grado");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 3), "bronzo", "la soglia è inclusiva");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 5), "bronzo");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 6), "argento");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 9), "argento");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 10), "oro");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 50), "oro", "oltre l'oro resta oro");
// --- serie-conferme: soglie 3 / 8 / 15 ----------------------------------------
const serieConfermeDef = badgeDefs.find((b) => b.id === "serie-conferme")!;
assert.equal(gradoRaggiunto(serieConfermeDef, 2), null, "sotto la prima soglia nessun grado");
assert.equal(gradoRaggiunto(serieConfermeDef, 3), "bronzo", "la soglia è inclusiva");
assert.equal(gradoRaggiunto(serieConfermeDef, 7), "bronzo");
assert.equal(gradoRaggiunto(serieConfermeDef, 8), "argento");
assert.equal(gradoRaggiunto(serieConfermeDef, 14), "argento");
assert.equal(gradoRaggiunto(serieConfermeDef, 15), "oro");
assert.equal(gradoRaggiunto(serieConfermeDef, 50), "oro", "oltre l'oro resta oro");
// --- badgeGiocatore ----------------------------------------------------------
assert.equal(badgeGiocatore(g()).length, badgeDefs.length, "i badge normali sono sempre tutti");
assert.ok(
badgeGiocatore(g()).every((b) => b.grado === null),
"un giocatore a zero non ha gradi",
);
// --- badge segreti: invisibili finché non si sbloccano ------------------------
const nessunSegreto = g({ mvp: 2, mediaVoto: 7.9 });
assert.equal(badgeSegretiSbloccati(nessunSegreto).length, 0, "serve media 8, non 7.9");
assert.equal(segretiNascosti(nessunSegreto), badgeSegreti.length);
const tiebreak = badgeSegretiSbloccati(g({ mvp: 2, mediaVoto: 8 }));
assert.deepEqual(
tiebreak.map((b) => b.def.id),
["s-tiebreak"],
"sblocca solo il segreto il cui requisito è soddisfatto",
);
assert.deepEqual(
badgeSegretiSbloccati(g({ infortuni: 3 })).map((b) => b.def.id),
["s-infermeria"],
);
assert.equal(badgeSegretiSbloccati(g({ infortuni: 2 })).length, 0, "2 infortuni non bastano");
assert.deepEqual(
badgeSegretiSbloccati(g({ ritardi: 5 })).map((b) => b.def.id),
["s-ritardi"],
);
assert.deepEqual(
badgeSegretiSbloccati(g({ cacche: 3 })).map((b) => b.def.id),
["s-cacche"],
);
assert.deepEqual(
badgeSegretiSbloccati(g({ serieConferme: 10, presenze: 15 })).map((b) => b.def.id),
["s-mai-forfait"],
);
assert.equal(
badgeSegretiSbloccati(g({ serieConferme: 10, presenze: 14 })).length,
0,
"servono entrambe le condizioni",
);
// --- collezioneBadge ---------------------------------------------------------
const vuota = collezioneBadge(g());
assert.equal(vuota.ottenuti, 0);
assert.equal(vuota.totali, tuttiBadge.length);
assert.equal(vuota.sbloccati.length, 0);
assert.equal(vuota.inProgresso.length, badgeDefs.length);
assert.equal(vuota.nascosti, badgeSegreti.length);
const piena = collezioneBadge(
g({ mvp: 5, mediaVoto: 8, votiPagella: 5, presenze: 30, infortuni: 3 }),
);
assert.equal(
piena.ottenuti,
piena.sbloccati.length + piena.segreti.length,
"gli ottenuti sommano normali e segreti",
);
assert.equal(piena.nascosti, badgeSegreti.length - piena.segreti.length);
assert.ok(piena.ottenuti > 0 && piena.ottenuti < piena.totali);
// --- prossimoTraguardo: il badge in proporzione più vicino -------------------
const vicino = prossimoTraguardo(g({ mvp: 2, presenze: 1 }));
assert.equal(vicino?.def.id, "mvp", "67% batte 20%");
assert.equal(
prossimoTraguardo(
g({
mvp: 5,
mediaVoto: 10,
votiPagella: 5,
palloni: 10,
presenze: 30,
serieAllenamenti: 10,
serieConferme: 15,
}),
),
null,
"tutto al massimo: nessun traguardo residuo",
);
// --- microcopy ---------------------------------------------------------------
assert.equal(mancanoPer(statoBadge(mvpDef, g({ mvp: 2 }))), "Ti mancano 1 MVP per il argento");
assert.equal(mancanoPer(alMassimo), "Livello massimo raggiunto");
assert.equal(microcopyBadge(alMassimo), "Hai fatto tutto: badge d'oro in bacheca.");
assert.equal(microcopyBadge(aMeta), "Sei in piena corsa, continua così.");
assert.equal(microcopyBadge(statoBadge(mvpDef, g())), "Ogni partita conta: si parte da qui.");
assert.equal(descrizioneSoglie(mvpDef), "1/3/5 MVP");
// --- invarianti sulle definizioni --------------------------------------------
for (const def of tuttiBadge) {
assert.ok(def.soglie.bronzo <= def.soglie.argento, `${def.id}: soglie crescenti`);
assert.ok(def.soglie.argento <= def.soglie.oro, `${def.id}: soglie crescenti`);
assert.ok(def.nome && def.descrizione && def.unita, `${def.id}: testi presenti`);
}
assert.equal(
new Set(tuttiBadge.map((b) => b.id)).size,
tuttiBadge.length,
"gli id dei badge sono unici",
);
console.log("badges: ok");