Azzera errori di lint e di tipo e corregge il test sugli slot della rosa

- schema-profili: il test pretendeva che lo slot g1 fosse libero, ma in
  produzione è collegato a un giocatore vero; ora verifica che il
  tentativo anonimo non cambi il valore.
- rosa: il default dei voti MVP era fuori da useMemo e faceva ricalcolare
  la rosa a ogni render finché i voti non erano caricati.
- test dei badge, profili-core, push-sw, scout-stato: tipi allineati al
  codice (MappaPresenze, nascita da M18, renotify, narrowing di deepEqual).
- vite.config: tipizzato come UserConfig e rimosso esbuild.keepNames,
  ignorato da Vite 8 che non usa più esbuild.
- formattazione Prettier in BarraSottosezioni e classifica.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
davideandClaude Opus 5.5 committed 2026-09-24 12:14:37 +02:00
1 parent 61d4dacb3a
commit 431530af2a
13 files changed
+56 -44

No files matched your search

+5 -3
View File
@@ -27,6 +27,8 @@ import { daRiga, type RigaEvento, type Evento } from "@/lib/eventi";
import { giocatori, type Giocatore } from "@/lib/crapp-data";
import { statoLocale } from "../helpers/locale";
import { prova, riepilogo, salta } from "../helpers/prova";
import type { Stato } from "@/lib/crapp-data";
import type { MappaPresenze } from "@/lib/presenze";
const locale = statoLocale();
@@ -97,16 +99,16 @@ if (!locale) {
}
/** Rilegge esattamente come `fetchPresenze()`. */
async function leggiPresenze(): Promise<Record<string, Record<string, string>>> {
async function leggiPresenze(): Promise<MappaPresenze> {
const res = await rest(
`risposte_presenze?evento_id=like.${PREFISSO}*&select=evento_id,giocatore_id,stato`,
);
const righe = (await res.json()) as Array<{
evento_id: string;
giocatore_id: string;
stato: string;
stato: Stato;
}>;
const mappa: Record<string, Record<string, string>> = {};
const mappa: MappaPresenze = {};
for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato;
return mappa;
}
+5 -3
View File
@@ -20,6 +20,8 @@ import { daRiga, type RigaEvento, type Evento } from "@/lib/eventi";
import { giocatori, type Giocatore } from "@/lib/crapp-data";
import { statoLocale } from "../helpers/locale";
import { prova, riepilogo, salta } from "../helpers/prova";
import type { Stato } from "@/lib/crapp-data";
import type { MappaPresenze } from "@/lib/presenze";
const locale = statoLocale();
@@ -87,16 +89,16 @@ if (!locale) {
return righe.map(daRiga);
}
async function leggiPresenze(): Promise<Record<string, Record<string, string>>> {
async function leggiPresenze(): Promise<MappaPresenze> {
const res = await rest(
`risposte_presenze?evento_id=like.${PREFISSO}*&select=evento_id,giocatore_id,stato`,
);
const righe = (await res.json()) as Array<{
evento_id: string;
giocatore_id: string;
stato: string;
stato: Stato;
}>;
const mappa: Record<string, Record<string, string>> = {};
const mappa: MappaPresenze = {};
for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato;
return mappa;
}
+5 -3
View File
@@ -19,6 +19,8 @@ import { daRiga, type RigaEvento, type Evento } from "@/lib/eventi";
import { giocatori, type Giocatore } from "@/lib/crapp-data";
import { statoLocale } from "../helpers/locale";
import { prova, riepilogo, salta } from "../helpers/prova";
import type { Stato } from "@/lib/crapp-data";
import type { MappaPresenze } from "@/lib/presenze";
const locale = statoLocale();
@@ -90,7 +92,7 @@ if (!locale) {
}
async function leggiPresenzeETempi(): Promise<{
presenze: Record<string, Record<string, string>>;
presenze: MappaPresenze;
tempi: Record<string, Record<string, string>>;
}> {
const res = await rest(
@@ -99,10 +101,10 @@ if (!locale) {
const righe = (await res.json()) as Array<{
evento_id: string;
giocatore_id: string;
stato: string;
stato: Stato;
risposto_il: string;
}>;
const presenze: Record<string, Record<string, string>> = {};
const presenze: MappaPresenze = {};
const tempi: Record<string, Record<string, string>> = {};
for (const r of righe) {
(presenze[r.evento_id] ??= {})[r.giocatore_id] = r.stato;
+5 -3
View File
@@ -17,6 +17,8 @@ import { daRiga, type RigaEvento, type Evento } from "@/lib/eventi";
import { giocatori, type Giocatore } from "@/lib/crapp-data";
import { statoLocale } from "../helpers/locale";
import { prova, riepilogo, salta } from "../helpers/prova";
import type { Stato } from "@/lib/crapp-data";
import type { MappaPresenze } from "@/lib/presenze";
const locale = statoLocale();
@@ -81,16 +83,16 @@ if (!locale) {
return righe.map(daRiga);
}
async function leggiPresenze(): Promise<Record<string, Record<string, string>>> {
async function leggiPresenze(): Promise<MappaPresenze> {
const res = await rest(
`risposte_presenze?evento_id=like.${PREFISSO}*&select=evento_id,giocatore_id,stato`,
);
const righe = (await res.json()) as Array<{
evento_id: string;
giocatore_id: string;
stato: string;
stato: Stato;
}>;
const mappa: Record<string, Record<string, string>> = {};
const mappa: MappaPresenze = {};
for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato;
return mappa;
}
+9 -3
View File
@@ -59,6 +59,14 @@ if (!URL_BASE || !CHIAVE_SERVIZIO || !CHIAVE_PUBBLICA) {
// Attenzione al falso verde: su un UPDATE che non tocca nessuna riga PostgREST
// risponde comunque 2xx. Quello che conta è che il dato non cambi.
await prova("un anonimo non si collega a uno slot della rosa", async () => {
const leggiSlot = async () => {
const r = await rest("giocatori_squadra?id=eq.g1&select=auth_user_id", CHIAVE_SERVIZIO);
const righe = (await r.json()) as Array<{ auth_user_id: string | null }>;
return righe[0]?.auth_user_id ?? null;
};
// Sul database di produzione g1 può essere già collegato a un giocatore vero:
// conta che il tentativo anonimo non cambi il valore, non che lo slot sia libero.
const prima = await leggiSlot();
const res = await rest("giocatori_squadra?id=eq.g1", CHIAVE_PUBBLICA, {
method: "PATCH",
headers: { Prefer: "return=representation" },
@@ -68,9 +76,7 @@ if (!URL_BASE || !CHIAVE_SERVIZIO || !CHIAVE_PUBBLICA) {
const aggiornate = (await res.json()) as unknown[];
assert.equal(aggiornate.length, 0, "nessuna riga aggiornata senza sessione");
}
const dopo = await rest("giocatori_squadra?id=eq.g1&select=auth_user_id", CHIAVE_SERVIZIO);
const righe = (await dopo.json()) as Array<{ auth_user_id: string | null }>;
assert.equal(righe[0]?.auth_user_id ?? null, null, "lo slot g1 è rimasto libero");
assert.equal(await leggiSlot(), prima, "lo slot g1 non è stato modificato");
});
// --- M8: tracciamento tesseramento CSI --------------------------------------
@@ -26,6 +26,8 @@ import { daRiga, type RigaEvento, type Evento } from "@/lib/eventi";
import { giocatori, type Giocatore } from "@/lib/crapp-data";
import { statoLocale } from "../helpers/locale";
import { prova, riepilogo, salta } from "../helpers/prova";
import type { Stato } from "@/lib/crapp-data";
import type { MappaPresenze } from "@/lib/presenze";
const locale = statoLocale();
@@ -93,16 +95,16 @@ if (!locale) {
return righe.map(daRiga);
}
async function leggiPresenze(): Promise<Record<string, Record<string, string>>> {
async function leggiPresenze(): Promise<MappaPresenze> {
const res = await rest(
`risposte_presenze?evento_id=like.${PREFISSO}*&select=evento_id,giocatore_id,stato`,
);
const righe = (await res.json()) as Array<{
evento_id: string;
giocatore_id: string;
stato: string;
stato: Stato;
}>;
const mappa: Record<string, Record<string, string>> = {};
const mappa: MappaPresenze = {};
for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato;
return mappa;
}
+2
View File
@@ -114,6 +114,7 @@ const squadra: GiocatoreSquadra[] = [
email: null,
numeroTessera: null,
dataTessera: null,
nascita: null,
},
{
id: "g2",
@@ -126,6 +127,7 @@ const squadra: GiocatoreSquadra[] = [
email: null,
numeroTessera: null,
dataTessera: null,
nascita: null,
},
];
const csv = csvTesseramento(squadra, { g1: completo });
+8 -3
View File
@@ -4,11 +4,16 @@ import { readFileSync } from "node:fs";
import { runInNewContext } from "node:vm";
const codice = readFileSync(new URL("../../public/push-sw.js", import.meta.url), "utf8");
type Evento = { data?: { json: () => unknown } | null; waitUntil: (p: Promise<unknown>) => void };
type Evento = {
data?: { json: () => unknown } | null | undefined;
waitUntil: (p: Promise<unknown>) => void;
};
// `renotify` è supportato da Chromium ma manca dai tipi DOM di TypeScript.
type OpzioniNotifica = NotificationOptions & { renotify?: boolean };
function avviaWorker() {
const listener = new Map<string, (event: Evento) => void>();
const notifiche: Array<{ titolo: string; opzioni: NotificationOptions }> = [];
const notifiche: Array<{ titolo: string; opzioni: OpzioniNotifica }> = [];
let completa: (() => void) | undefined;
const mostrata = new Promise<void>((resolve) => (completa = resolve));
const installata = Promise.resolve();
@@ -23,7 +28,7 @@ function avviaWorker() {
matchAll: () => assert.fail("la push deve funzionare senza consultare finestre aperte"),
},
registration: {
showNotification(titolo: string, opzioni: NotificationOptions) {
showNotification(titolo: string, opzioni: OpzioniNotifica) {
notifiche.push({ titolo, opzioni });
return mostrata;
},
+2 -1
View File
@@ -4,7 +4,8 @@ import { SCOUT_STATO_KEY, statoIniziale } from "@/lib/scout-stato";
// --- statoIniziale: parte vuoto, con avversario e campo memorizzati -----------
const stato = statoIniziale("Avversari", true);
assert.deepEqual(stato, { azioni: [], setChiusi: [], avversario: "Avversari", casa: true });
// Confronta una copia: deepEqual restringerebbe `stato` al letterale, con `azioni: never[]`.
assert.deepEqual({ ...stato }, { azioni: [], setChiusi: [], avversario: "Avversari", casa: true });
const trasferta = statoIniziale("Altra Squadra", false);
assert.equal(trasferta.casa, false);