Completa la copertura test di tutti i badge e corregge due bug trovati in audit
Audit dedicato su tutti i 16 badge: s-tiebreak non applicava la soglia minima di voti pagella di Pagellone sullo stesso campo mediaVoto (corretto), s-cacche prometteva "partite di campionato" senza che il codice lo verificasse mai (corretta la descrizione, comportamento invariato). Aggiunti test unit e integration end-to-end mancanti su badge segreti e social, con dati scritti a database anche per i cinque segreti che prima ne erano privi. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,27 @@ assert.equal(vincitoreCategoria(voti, "m1", "cuore"), null, "nessun voto, nessun
|
||||
const pari = [v("m3", "meme", "g1", "g2", "Bruno"), v("m3", "meme", "g2", "g5", "Anna")];
|
||||
assert.equal(vincitoreCategoria(pari, "m3", "meme"), null, "parità: nessun vincitore");
|
||||
|
||||
// Un solo voto totale: vince comunque, non serve concorrenza per avere un vantaggio netto.
|
||||
const votoSingolo = [v("m4", "cuore", "g1", "g9", "Zoe")];
|
||||
assert.equal(
|
||||
vincitoreCategoria(votoSingolo, "m4", "cuore")?.nome,
|
||||
"Zoe",
|
||||
"un voto solo basta se non c'è nessun altro candidato",
|
||||
);
|
||||
|
||||
// Tre candidati: i primi due pari in testa, il terzo staccato. Il pareggio conta comunque,
|
||||
// non basta che qualcun altro sia sotto per assegnare la categoria.
|
||||
const triplaPari = [
|
||||
v("m5", "spirito", "g1", "g8", "Uno"),
|
||||
v("m5", "spirito", "g2", "g7", "Due"),
|
||||
v("m5", "spirito", "g3", "g6", "Tre"),
|
||||
];
|
||||
assert.equal(
|
||||
vincitoreCategoria(triplaPari, "m5", "spirito"),
|
||||
null,
|
||||
"primo e secondo pari: nessun vincitore anche con un terzo staccato",
|
||||
);
|
||||
|
||||
// --- mioVotoSocial -----------------------------------------------------------
|
||||
assert.equal(mioVotoSocial(voti, "m1", "affidabile", "g1")?.votato_id, "g2");
|
||||
assert.equal(mioVotoSocial(voti, "m1", "meme", "g3"), null, "non ho votato questa categoria");
|
||||
@@ -51,6 +72,19 @@ assert.deepEqual(badgeSocialVinti(voti, "g9"), {}, "chi non vince non ha badge")
|
||||
assert.deepEqual(badgeSocialVinti(pari, "g2"), {}, "una parità non assegna badge");
|
||||
assert.deepEqual(badgeSocialVinti([], "g2"), {});
|
||||
|
||||
// Categorie e partite diverse non si mischiano: g2 vince "affidabile" in m1/m2 (già sopra) e
|
||||
// "fairplay" in m2, un'altra categoria nella stessa partita — i due conteggi restano separati.
|
||||
const conAltraCategoria: VotoSocial[] = [
|
||||
...voti,
|
||||
v("m2", "fairplay", "g3", "g2", "Bruno"),
|
||||
v("m2", "fairplay", "g4", "g2", "Bruno"),
|
||||
];
|
||||
assert.deepEqual(
|
||||
badgeSocialVinti(conAltraCategoria, "g2"),
|
||||
{ affidabile: 2, fairplay: 1 },
|
||||
"vittorie in categorie diverse, anche nella stessa partita, si contano separate",
|
||||
);
|
||||
|
||||
// --- invarianti sulle categorie ----------------------------------------------
|
||||
assert.equal(
|
||||
new Set(categorieSocial.map((c) => c.id)).size,
|
||||
|
||||
@@ -90,6 +90,27 @@ assert.equal(
|
||||
"sopra la soglia minima, valgono le normali soglie di grado",
|
||||
);
|
||||
|
||||
// Confini argento/oro, non solo bronzo: stesso arrotondamento per difetto.
|
||||
assert.equal(gradoRaggiunto(pagella, 7.4), "bronzo");
|
||||
assert.equal(gradoRaggiunto(pagella, 7.5), "argento");
|
||||
assert.equal(gradoRaggiunto(pagella, 8.4), "argento");
|
||||
assert.equal(gradoRaggiunto(pagella, 8.5), "oro");
|
||||
|
||||
// Progresso con soglia decimale: valore/prossimaSoglia, non arrotondato per eccesso.
|
||||
assert.equal(
|
||||
statoBadge(pagella, g({ mediaVoto: 7, votiPagella: 5 })).progresso,
|
||||
93,
|
||||
"7/7.5 = 93.3%, arrotondato a 93",
|
||||
);
|
||||
|
||||
// Sotto la soglia minima di voti il valore è forzato a 0: anche il progresso torna a 0%,
|
||||
// non alla percentuale che la media reale avrebbe suggerito.
|
||||
assert.equal(
|
||||
statoBadge(pagella, g({ mediaVoto: 10, votiPagella: 1 })).progresso,
|
||||
0,
|
||||
"valore azzerato dal gate: progresso azzerato anch'esso, non ingannevole",
|
||||
);
|
||||
|
||||
// --- 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");
|
||||
@@ -147,12 +168,22 @@ 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 }));
|
||||
const tiebreak = badgeSegretiSbloccati(g({ mvp: 2, mediaVoto: 8, votiPagella: 5 }));
|
||||
assert.deepEqual(
|
||||
tiebreak.map((b) => b.def.id),
|
||||
["s-tiebreak"],
|
||||
"sblocca solo il segreto il cui requisito è soddisfatto",
|
||||
);
|
||||
assert.equal(
|
||||
badgeSegretiSbloccati(g({ mvp: 2, mediaVoto: 8, votiPagella: 4 })).length,
|
||||
0,
|
||||
"come Pagellone: sotto la soglia minima di voti la media non conta, nemmeno qui",
|
||||
);
|
||||
assert.equal(
|
||||
badgeSegretiSbloccati(g({ mvp: 1, mediaVoto: 8, votiPagella: 5 })).length,
|
||||
0,
|
||||
"un solo MVP non basta",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
badgeSegretiSbloccati(g({ infortuni: 3 })).map((b) => b.def.id),
|
||||
@@ -163,10 +194,12 @@ assert.deepEqual(
|
||||
badgeSegretiSbloccati(g({ ritardi: 5 })).map((b) => b.def.id),
|
||||
["s-ritardi"],
|
||||
);
|
||||
assert.equal(badgeSegretiSbloccati(g({ ritardi: 4 })).length, 0, "4 ritardi non bastano");
|
||||
assert.deepEqual(
|
||||
badgeSegretiSbloccati(g({ cacche: 3 })).map((b) => b.def.id),
|
||||
["s-cacche"],
|
||||
);
|
||||
assert.equal(badgeSegretiSbloccati(g({ cacche: 2 })).length, 0, "2 cacche non bastano");
|
||||
assert.deepEqual(
|
||||
badgeSegretiSbloccati(g({ serieConferme: 10, presenze: 15 })).map((b) => b.def.id),
|
||||
["s-mai-forfait"],
|
||||
@@ -176,6 +209,27 @@ assert.equal(
|
||||
0,
|
||||
"servono entrambe le condizioni",
|
||||
);
|
||||
// Confini isolati: ogni soglia testata da sola, con l'altra abbondantemente sopra.
|
||||
assert.equal(
|
||||
badgeSegretiSbloccati(g({ serieConferme: 9, presenze: 30 })).length,
|
||||
0,
|
||||
"serieConferme appena sotto soglia, presenze abbondanti: non basta",
|
||||
);
|
||||
assert.deepEqual(
|
||||
badgeSegretiSbloccati(g({ serieConferme: 10, presenze: 30 })).map((b) => b.def.id),
|
||||
["s-mai-forfait"],
|
||||
"serieConferme esattamente al confine, presenze abbondanti: sblocca",
|
||||
);
|
||||
assert.equal(
|
||||
badgeSegretiSbloccati(g({ serieConferme: 30, presenze: 14 })).length,
|
||||
0,
|
||||
"presenze appena sotto soglia, serieConferme abbondante: non basta",
|
||||
);
|
||||
assert.deepEqual(
|
||||
badgeSegretiSbloccati(g({ serieConferme: 30, presenze: 15 })).map((b) => b.def.id),
|
||||
["s-mai-forfait"],
|
||||
"presenze esattamente al confine, serieConferme abbondante: sblocca",
|
||||
);
|
||||
|
||||
// --- collezioneBadge ---------------------------------------------------------
|
||||
const vuota = collezioneBadge(g());
|
||||
|
||||
@@ -99,4 +99,14 @@ assert.equal(
|
||||
1,
|
||||
);
|
||||
|
||||
// Uno stesso giocatore infortunato in un evento e in ritardo in un altro: i due conteggi
|
||||
// restano indipendenti, nessuno "ruba" all'altro.
|
||||
const misto: MappaPresenze = {
|
||||
e1: { g4: "infortunato" },
|
||||
e2: { g4: "ritardo" },
|
||||
e3: { g4: "infortunato" },
|
||||
};
|
||||
assert.deepEqual(contaInfortuni(misto, eventi, OGGI), { g4: 2 });
|
||||
assert.deepEqual(contaRitardi(misto, eventi, OGGI), { g4: 1 });
|
||||
|
||||
console.log("infortuni: ok");
|
||||
|
||||
@@ -52,6 +52,20 @@ assert.deepEqual(
|
||||
"un solo votante basta se non c'è concorrenza",
|
||||
);
|
||||
|
||||
// Tre candidati: i primi due pari in testa, un terzo staccato. Deve restare senza MVP,
|
||||
// non basta che il terzo sia sotto: conta solo il confronto fra il primo e il secondo.
|
||||
const triplaPari = [
|
||||
v("m6", "g1", "g9", "Zeno"),
|
||||
v("m6", "g2", "g8", "Anna"),
|
||||
v("m6", "g3", "g7", "Bea"),
|
||||
];
|
||||
assert.deepEqual(vincitoriMvp(triplaPari), {}, "primo e secondo pari: nessun MVP anche a 3 vie");
|
||||
assert.deepEqual(
|
||||
mvpVintiPerGiocatore(triplaPari),
|
||||
{},
|
||||
"stessa parità: nessuna vittoria netta da contare",
|
||||
);
|
||||
|
||||
// --- mioVoto -----------------------------------------------------------------
|
||||
assert.equal(mioVoto(partita, "m1", "g1")?.votato_nome, "Bruno");
|
||||
assert.equal(mioVoto(partita, "m1", "g9"), null, "chi non ha votato non ha voto");
|
||||
|
||||
@@ -121,6 +121,18 @@ assert.deepEqual(
|
||||
"nessun turno assegnato: conteggio vuoto",
|
||||
);
|
||||
|
||||
// A differenza di `eventiPalloni()` (che scarta i compleanni), `conteggioTurni()` non filtra
|
||||
// per tipo: guarda solo `turni` ed `e.data < oggi`. Un turno registrato per errore su un
|
||||
// evento che il resto del modulo tratterebbe come "non richiede palloni" conterebbe comunque
|
||||
// per il badge. Comportamento attuale documentato (non l'UI non offre questa combinazione),
|
||||
// non una correzione: se cambia, questo test deve fallire e ricordarlo.
|
||||
const conCompleanno: Evento[] = [evento("cb1", "2026-09-01", "compleanno")];
|
||||
assert.deepEqual(
|
||||
conteggioTurni({ cb1: "g1" }, conCompleanno, OGGI_CONTEGGIO),
|
||||
{ g1: 1 },
|
||||
"conteggioTurni() non esclude i compleanni come fa eventiPalloni(): nessun filtro per tipo",
|
||||
);
|
||||
|
||||
// --- oggiISO -------------------------------------------------------------------
|
||||
assert.match(oggiISO(), /^\d{4}-\d{2}-\d{2}$/);
|
||||
// Stesso controllo di dataOggi() in scout-live.test.ts: oggiISO() ne è un alias, il fuso
|
||||
|
||||
Reference in New Issue
Block a user