La sezione «Scout» mostra le valutazioni − / + nell'app, non solo nel CSV: percentuale di + e conteggi per fondamentale, selettore Totale / Set 1-5, tabella per giocatore e guida (i) su come leggere i dati. Le azioni (punti, ace, muri, errori) restano in un blocco a parte per le partite scoutate col metodo precedente. Il CSV resta per gli admin. Aggiunge sintesiValutazioni() in scout-store con test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
217 lines
6.2 KiB
TypeScript
217 lines
6.2 KiB
TypeScript
/** Check dello scout: `bun test/unit/scout-store.test.ts`. */
|
||
import assert from "node:assert/strict";
|
||
import { giocatori } from "@/lib/crapp-data";
|
||
import {
|
||
azioniMeta,
|
||
contaPiuSenzaRicezione,
|
||
haPunteggio,
|
||
parzialiFinali,
|
||
sintesiValutazioni,
|
||
contaValutazioni,
|
||
fondamentali,
|
||
giocatoriConScout,
|
||
totaliPerGiocatore,
|
||
totaliSquadra,
|
||
type Azione,
|
||
type Valutazione,
|
||
type ScoutMatch,
|
||
} from "@/lib/scout-store";
|
||
|
||
const a = (tipo: Azione["tipo"], giocatoreId?: string, set = 1): Azione => ({
|
||
id: `${tipo}-${giocatoreId ?? "team"}-${set}-${Math.random()}`,
|
||
tipo,
|
||
...(giocatoreId ? { giocatoreId } : {}),
|
||
set,
|
||
ts: 0,
|
||
});
|
||
|
||
const match = (
|
||
id: string,
|
||
setNostri: number,
|
||
setLoro: number,
|
||
azioni: Azione[] = [],
|
||
): ScoutMatch => ({
|
||
id,
|
||
data: "2026-09-01",
|
||
avversario: "Avversari",
|
||
casa: true,
|
||
setNostri,
|
||
setLoro,
|
||
parziali: [],
|
||
azioni,
|
||
valutazioni: [],
|
||
});
|
||
|
||
// --- totaliPerGiocatore: ace e muri valgono anche come punto ------------------
|
||
const azioni = [
|
||
a("attacco", "g1"),
|
||
a("ace", "g1"),
|
||
a("muro", "g1"),
|
||
a("errore", "g1"),
|
||
a("attacco", "g2"),
|
||
a("punto_avv"),
|
||
a("errore_avv"),
|
||
];
|
||
const totali = totaliPerGiocatore(azioni);
|
||
assert.deepEqual(totali.get("g1"), { punti: 3, ace: 1, muri: 1, errori: 1 });
|
||
assert.deepEqual(totali.get("g2"), { punti: 1, ace: 0, muri: 0, errori: 0 });
|
||
assert.equal(totali.size, 2, "le azioni senza giocatore non creano righe");
|
||
assert.equal(totaliPerGiocatore([]).size, 0);
|
||
|
||
// --- totaliSquadra -----------------------------------------------------------
|
||
assert.deepEqual(totaliSquadra([match("m1", 3, 0, azioni), match("m2", 3, 1, azioni)]), {
|
||
punti: 8,
|
||
ace: 2,
|
||
muri: 2,
|
||
errori: 2,
|
||
});
|
||
assert.deepEqual(totaliSquadra([]), { punti: 0, ace: 0, muri: 0, errori: 0 });
|
||
|
||
// --- giocatoriConScout: solo MVP da scout, presenze separate -----------------
|
||
const base = giocatori.find((g) => g.id === "g1")!;
|
||
const conScout = giocatoriConScout([match("m1", 3, 0, azioni)], { m1: base.nome });
|
||
const dopo = conScout.find((g) => g.id === "g1")!;
|
||
assert.equal(dopo.presenze, 0, "le azioni scout non alimentano le presenze personali");
|
||
assert.equal(dopo.mvp, 1, "l'MVP eletto viene contato");
|
||
assert.equal(dopo.totaliEventi, 0, "totaliEventi arriva dagli eventi CrAPP, non dallo scout");
|
||
|
||
const assente = conScout.find((g) => g.id === "g3")!;
|
||
assert.equal(assente.presenze, 0, "senza risposte presenze resta a zero");
|
||
assert.equal(assente.mvp, 0);
|
||
assert.equal(assente.totaliEventi, 0);
|
||
|
||
const senzaPartite = giocatoriConScout([]);
|
||
assert.ok(
|
||
senzaPartite.every((g) => g.presenze === 0 && g.mvp === 0 && g.totaliEventi === 0),
|
||
"senza partite scout le statistiche restano a zero",
|
||
);
|
||
|
||
// --- metadati azioni ---------------------------------------------------------
|
||
for (const [tipo, meta] of Object.entries(azioniMeta)) {
|
||
assert.ok(meta.label && meta.short, `${tipo}: etichette presenti`);
|
||
}
|
||
|
||
console.log("scout-store: ok");
|
||
|
||
// --- valutazioni − / +: per giocatore, fondamentale e set; fuori dal punteggio -------
|
||
const v = (
|
||
giocatoreId: string,
|
||
fondamentale: Valutazione["fondamentale"],
|
||
segno: Valutazione["segno"],
|
||
set = 1,
|
||
): Valutazione => ({
|
||
id: `${giocatoreId}-${fondamentale}-${segno}-${set}-${Math.random()}`,
|
||
giocatoreId,
|
||
fondamentale,
|
||
segno,
|
||
set,
|
||
ts: 0,
|
||
});
|
||
|
||
assert.deepEqual(
|
||
fondamentali.map((f) => f.id),
|
||
["battuta", "ricezione", "attacco", "muro"],
|
||
);
|
||
const val = [
|
||
v("g1", "battuta", "+"),
|
||
v("g1", "battuta", "+"),
|
||
v("g1", "battuta", "-"),
|
||
v("g1", "ricezione", "-", 2),
|
||
v("g1", "muro", "+", 2),
|
||
v("g2", "attacco", "+"),
|
||
];
|
||
assert.deepEqual(contaValutazioni(val, "g1", 1), {
|
||
battuta: { piu: 2, meno: 1 },
|
||
ricezione: { piu: 0, meno: 0 },
|
||
attacco: { piu: 0, meno: 0 },
|
||
muro: { piu: 0, meno: 0 },
|
||
});
|
||
assert.deepEqual(
|
||
contaValutazioni(val, "g1", 2).ricezione,
|
||
{ piu: 0, meno: 1 },
|
||
"il set 2 è separato",
|
||
);
|
||
assert.deepEqual(
|
||
contaValutazioni(val, "g1").battuta,
|
||
{ piu: 2, meno: 1 },
|
||
"senza set: tutta la partita",
|
||
);
|
||
assert.deepEqual(contaValutazioni(val, "g1").ricezione, { piu: 0, meno: 1 });
|
||
assert.deepEqual(
|
||
contaValutazioni(val, "g3", 1).battuta,
|
||
{ piu: 0, meno: 0 },
|
||
"giocatore senza valutazioni",
|
||
);
|
||
assert.equal(totaliPerGiocatore([]).size, 0, "le valutazioni non sono azioni: nessun punto");
|
||
|
||
// --- + del riquadro giocatore: tutti i fondamentali tranne la ricezione, nel set -------
|
||
const piuVal = [
|
||
v("g1", "battuta", "+"),
|
||
v("g1", "attacco", "+"),
|
||
v("g1", "muro", "+"),
|
||
v("g1", "ricezione", "+"),
|
||
v("g1", "attacco", "-"),
|
||
v("g1", "battuta", "+", 2),
|
||
v("g2", "attacco", "+"),
|
||
];
|
||
assert.equal(
|
||
contaPiuSenzaRicezione(piuVal, "g1", 1),
|
||
3,
|
||
"niente ricezione, niente −, solo il set 1",
|
||
);
|
||
assert.equal(contaPiuSenzaRicezione(piuVal, "g1", 2), 1);
|
||
assert.equal(contaPiuSenzaRicezione(piuVal, "g3", 1), 0);
|
||
|
||
// --- parziali finali: il set in corso conta solo se ha dati ------------------------
|
||
assert.deepEqual(
|
||
parzialiFinali([[25, 20]], [0, 0], true),
|
||
[
|
||
[25, 20],
|
||
[0, 0],
|
||
],
|
||
"set con sole valutazioni",
|
||
);
|
||
assert.deepEqual(
|
||
parzialiFinali([[25, 20]], [0, 0], false),
|
||
[[25, 20]],
|
||
"set vuoto: non si archivia",
|
||
);
|
||
assert.deepEqual(parzialiFinali([], [0, 0], false), [], "nessun set da salvare");
|
||
|
||
// --- haPunteggio: le partite di sole valutazioni non sono un risultato -------------
|
||
assert.equal(
|
||
haPunteggio({
|
||
parziali: [
|
||
[0, 0],
|
||
[0, 0],
|
||
],
|
||
}),
|
||
false,
|
||
);
|
||
assert.equal(haPunteggio({ parziali: [] }), false);
|
||
assert.equal(
|
||
haPunteggio({
|
||
parziali: [
|
||
[0, 0],
|
||
[25, 18],
|
||
],
|
||
}),
|
||
true,
|
||
);
|
||
|
||
// --- sintesi per la lettura nell'app: squadra e giocatori, partita o singolo set -----
|
||
const tutta = sintesiValutazioni(val);
|
||
assert.deepEqual(tutta.squadra.battuta, { piu: 2, meno: 1 });
|
||
assert.deepEqual(tutta.squadra.attacco, { piu: 1, meno: 0 });
|
||
assert.deepEqual(tutta.squadra.ricezione, { piu: 0, meno: 1 });
|
||
assert.deepEqual(tutta.perGiocatore.map((r) => r.giocatoreId).sort(), ["g1", "g2"]);
|
||
const set2 = sintesiValutazioni(val, 2);
|
||
assert.deepEqual(
|
||
set2.perGiocatore.map((r) => r.giocatoreId),
|
||
["g1"],
|
||
);
|
||
assert.deepEqual(set2.squadra.muro, { piu: 1, meno: 0 });
|
||
assert.deepEqual(set2.squadra.battuta, { piu: 0, meno: 0 });
|
||
assert.equal(sintesiValutazioni([]).perGiocatore.length, 0);
|
||
console.log("scout-store sintesi: ok");
|