From 431530af2af9f23bbef0eb4b66c64649b52c5d5f Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Thu, 24 Sep 2026 12:14:37 +0200 Subject: [PATCH] Azzera errori di lint e di tipo e corregge il test sugli slot della rosa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- src/components/crapp/BarraSottosezioni.tsx | 10 +++------- src/lib/rosa.ts | 4 ++-- src/routes/classifica.tsx | 13 +++---------- test/integration/presenze-badge.test.ts | 8 +++++--- test/integration/s-infermeria-badge.test.ts | 8 +++++--- test/integration/s-mai-forfait-badge.test.ts | 8 +++++--- test/integration/s-ritardi-badge.test.ts | 8 +++++--- test/integration/schema-profili.test.ts | 12 +++++++++--- test/integration/serie-allenamenti-badge.test.ts | 8 +++++--- test/unit/profili-core.test.ts | 2 ++ test/unit/push-sw.test.ts | 11 ++++++++--- test/unit/scout-stato.test.ts | 3 ++- vite.config.ts | 5 ++--- 13 files changed, 56 insertions(+), 44 deletions(-) diff --git a/src/components/crapp/BarraSottosezioni.tsx b/src/components/crapp/BarraSottosezioni.tsx index 080a029..a2d4315 100644 --- a/src/components/crapp/BarraSottosezioni.tsx +++ b/src/components/crapp/BarraSottosezioni.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState, type ReactNode } from "react"; -import { motion } from "motion/react"; +import { motion, type Transition } from "motion/react"; import { cn } from "@/lib/utils"; import { proietta } from "@/lib/molla"; import { useMotoRidotto } from "@/lib/motion"; @@ -13,7 +13,7 @@ export type VoceSottosezione = { }; /** Tween breve: evita molle + exit che tengono due pannelli in DOM insieme. */ -const transizioneTab = { type: "tween" as const, duration: 0.16, ease: [0.25, 0.1, 0.25, 1] }; +const transizioneTab: Transition = { type: "tween", duration: 0.16, ease: [0.25, 0.1, 0.25, 1] }; /** * Barra di sottosezioni in un'unica fila e pannello che mostra una sola sezione @@ -116,11 +116,7 @@ export function BarraSottosezioni({ }} className={cn( "min-h-11 touch-manipulation whitespace-nowrap text-sm font-bold uppercase tracking-wide transition-colors", - riempiLarghezza - ? "min-w-0 flex-1" - : sottolineatura - ? "shrink-0" - : "w-full", + riempiLarghezza ? "min-w-0 flex-1" : sottolineatura ? "shrink-0" : "w-full", sottolineatura ? cn( "rounded-xl px-1 py-2.5 text-center", diff --git a/src/lib/rosa.ts b/src/lib/rosa.ts index 0c825d4..e92aead 100644 --- a/src/lib/rosa.ts +++ b/src/lib/rosa.ts @@ -67,7 +67,7 @@ export function useRosa(): Giocatore[] { const { eventi } = useEventi(); const { presenze: mappaPresenze, tempi } = useRispostePresenze(); - const votiMvp = voti.data ?? []; + const votiMvp = voti.data; return useMemo(() => { const medie = mediePagelle(pagelle); @@ -76,7 +76,7 @@ export function useRosa(): Giocatore[] { // premiare chi ha davvero portato i palloni, non chi l'algoritmo di rotazione ha // scelto per un evento passato senza che nessuno confermasse nulla. const palloni = conteggioTurni(turniSalvati, eventi); - const mvpVinti = mvpVintiPerGiocatore(votiMvp); + const mvpVinti = mvpVintiPerGiocatore(votiMvp ?? []); return squadra .filter((g) => g.attivo) diff --git a/src/routes/classifica.tsx b/src/routes/classifica.tsx index 364fdee..200ff41 100644 --- a/src/routes/classifica.tsx +++ b/src/routes/classifica.tsx @@ -26,9 +26,7 @@ function LogoPartita({ avversario: string; }) { if (nostro) { - return ( - - ); + return ; } if (logoAvversario) { return ; @@ -254,10 +252,7 @@ function Classifica() { {setCasa}-{setOspite} {cliccabile ? ( - + ) : null}
@@ -270,9 +265,7 @@ function Classifica() { key={i} className={cn( "rounded-lg px-2 py-1 text-xs font-semibold tabular-nums", - setVintoDaNoi - ? "bg-secondary" - : "bg-muted text-muted-foreground", + setVintoDaNoi ? "bg-secondary" : "bg-muted text-muted-foreground", )} > {puntiCasa}-{puntiOspite} diff --git a/test/integration/presenze-badge.test.ts b/test/integration/presenze-badge.test.ts index 0c362cd..ae86bdb 100644 --- a/test/integration/presenze-badge.test.ts +++ b/test/integration/presenze-badge.test.ts @@ -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>> { + async function leggiPresenze(): Promise { 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> = {}; + const mappa: MappaPresenze = {}; for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato; return mappa; } diff --git a/test/integration/s-infermeria-badge.test.ts b/test/integration/s-infermeria-badge.test.ts index 0234bb0..00de1d4 100644 --- a/test/integration/s-infermeria-badge.test.ts +++ b/test/integration/s-infermeria-badge.test.ts @@ -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>> { + async function leggiPresenze(): Promise { 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> = {}; + const mappa: MappaPresenze = {}; for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato; return mappa; } diff --git a/test/integration/s-mai-forfait-badge.test.ts b/test/integration/s-mai-forfait-badge.test.ts index 59a7c83..dbf804a 100644 --- a/test/integration/s-mai-forfait-badge.test.ts +++ b/test/integration/s-mai-forfait-badge.test.ts @@ -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>; + presenze: MappaPresenze; tempi: Record>; }> { 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> = {}; + const presenze: MappaPresenze = {}; const tempi: Record> = {}; for (const r of righe) { (presenze[r.evento_id] ??= {})[r.giocatore_id] = r.stato; diff --git a/test/integration/s-ritardi-badge.test.ts b/test/integration/s-ritardi-badge.test.ts index 3a7fbe4..7b48680 100644 --- a/test/integration/s-ritardi-badge.test.ts +++ b/test/integration/s-ritardi-badge.test.ts @@ -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>> { + async function leggiPresenze(): Promise { 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> = {}; + const mappa: MappaPresenze = {}; for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato; return mappa; } diff --git a/test/integration/schema-profili.test.ts b/test/integration/schema-profili.test.ts index 76fee1a..da9ff9d 100644 --- a/test/integration/schema-profili.test.ts +++ b/test/integration/schema-profili.test.ts @@ -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 -------------------------------------- diff --git a/test/integration/serie-allenamenti-badge.test.ts b/test/integration/serie-allenamenti-badge.test.ts index e1659c9..c2abf80 100644 --- a/test/integration/serie-allenamenti-badge.test.ts +++ b/test/integration/serie-allenamenti-badge.test.ts @@ -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>> { + async function leggiPresenze(): Promise { 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> = {}; + const mappa: MappaPresenze = {}; for (const r of righe) (mappa[r.evento_id] ??= {})[r.giocatore_id] = r.stato; return mappa; } diff --git a/test/unit/profili-core.test.ts b/test/unit/profili-core.test.ts index e19b741..4810956 100644 --- a/test/unit/profili-core.test.ts +++ b/test/unit/profili-core.test.ts @@ -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 }); diff --git a/test/unit/push-sw.test.ts b/test/unit/push-sw.test.ts index c29c918..d188369 100644 --- a/test/unit/push-sw.test.ts +++ b/test/unit/push-sw.test.ts @@ -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) => void }; +type Evento = { + data?: { json: () => unknown } | null | undefined; + waitUntil: (p: Promise) => void; +}; +// `renotify` è supportato da Chromium ma manca dai tipi DOM di TypeScript. +type OpzioniNotifica = NotificationOptions & { renotify?: boolean }; function avviaWorker() { const listener = new Map void>(); - const notifiche: Array<{ titolo: string; opzioni: NotificationOptions }> = []; + const notifiche: Array<{ titolo: string; opzioni: OpzioniNotifica }> = []; let completa: (() => void) | undefined; const mostrata = new Promise((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; }, diff --git a/test/unit/scout-stato.test.ts b/test/unit/scout-stato.test.ts index 09f451e..24ce890 100644 --- a/test/unit/scout-stato.test.ts +++ b/test/unit/scout-stato.test.ts @@ -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); diff --git a/vite.config.ts b/vite.config.ts index 5a00681..5e13b28 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, loadEnv, mergeConfig } from "vite"; +import { defineConfig, loadEnv, mergeConfig, type UserConfig } from "vite"; import tailwindcss from "@tailwindcss/vite"; import tsConfigPaths from "vite-tsconfig-paths"; import viteReact from "@vitejs/plugin-react"; @@ -46,14 +46,13 @@ export default defineConfig(async ({ command, mode }) => { } plugins.push(viteReact()); - let config = { + let config: UserConfig = { define: envDefine, ...(isDevBuild ? { environments: { client: { define: { "process.env.NODE_ENV": JSON.stringify("development") } }, }, - esbuild: { keepNames: true }, } : {}), css: { transformer: "lightningcss" as const },