# Piano dei test Decisioni (strumenti, livelli, struttura): [ADR-0003](adr/0003-strategia-di-test.md). Qui: regola, flusso, soglie e **matrice requisito → test**, sorgente di verità per la copertura. Le spunte `[ ]` passano a `[x]` quando il test esiste e passa. ## Regola **Nuova funzione, correzione o cambio di comportamento ⇒ test nello stesso commit.** Niente test, funzione non finita. - Una nuova funzione aggiunge righe alla matrice qui sotto (con ID) prima di scrivere il codice. - Una correzione di bug parte da un test che fallisce e lo lascia come regressione. - Una modifica che cambia un requisito aggiorna prima il doc, poi la riga della matrice, poi i test, poi il codice. - Una riga senza test non è copertura: va lasciata `[ ]` e conta come debito visibile. ## Flusso (estende il doc-first di [README.md](README.md)) ADR (se serve) → MVP.md (requisito e criterio) → riga in questa matrice → test che falliscono → codice → test verdi → ROADMAP/README/CLAUDE.md. ## Livelli | Livello | Cosa prova | Come | |---|---|---| | **U** — unitario | `Core/` e `MainViewModel` | xUnit, finto runner, cartella dati temporanea | | **R** — renderer | `MarkdownView` | xUnit + Headless, asserzioni sull'albero visuale | | **G** — GUI headless | `MainWindow`, template, tastiera, finestre di dialogo | xUnit + Headless, senza schermo | | **P** — processo | avvio reale di un processo (`Claude`) | `FakeClaude` nel PATH | | **E** — end-to-end | l'eseguibile pubblicato (CLI, 127, exit code) | publish + processo figlio + `FakeClaude` | ## Struttura prevista ``` agent-wiki-ui.slnx src/agent-wiki-ui/ app (Core/, Gui/, Program.cs) tests/agent-wiki-ui.Tests/ U, R, G, P, E (Trait "Category": Unit | Gui | Process | E2E) tests/FakeClaude/ eseguibile finto: registra argv/cwd/stdin; testo, stderr, exit code e ritardo da variabili d'ambiente ``` ## Predisposizioni di testabilità (prerequisito dei test) - [ ] `ChatStore` come istanza con cartella iniettata (oggi statico su `LocalApplicationData`) - [ ] `IClaudeRunner` dietro `Core/Claude.cs`, iniettato nel `MainViewModel` - [ ] `InternalsVisibleTo` verso il progetto di test - [ ] Spostamento in `src/` e `tests/` con soluzione unica ## Soglie di copertura (la build fallisce sotto soglia) | Area | Righe | Rami | |---|---|---| | `Core/` | ≥ 95% | ≥ 90% | | `Gui/MainViewModel` | ≥ 95% | ≥ 90% | | `Gui/MarkdownView` | ≥ 90% | ≥ 85% | | `Gui/MainWindow*`, `ConfirmDialog` (code-behind) | ≥ 85% | — | | Totale | ≥ 90% | — | Esclusi dal calcolo: XAML generato, `Program.cs` (tre righe, coperte da E2E), `App.axaml.cs`. La soglia non sostituisce la matrice: copertura di righe alta con requisiti scoperti non basta. ## Matrice requisito → test Fonte dei requisiti: [MVP.md](MVP.md) e gli ADR. Livello: vedi sopra. Il nome del test è indicativo. ### Avvio di `claude` — `Core/Claude.cs` | ID | Requisito | Liv. | Test | | |---|---|---|---|---| | CL-01 | Il prompt è un solo argomento, anche con virgolette e spazi (criterio MVP 4) | U/P | `Prompt_con_virgolette_arriva_intatto` | [ ] | | CL-02 | Primo messaggio: `--session-id `; successivi: `--resume ` | U | `Sessione_prima_e_ripresa` | [ ] | | CL-03 | `--model`/`--effort` omessi se vuoti, presenti se valorizzati | U | `Flag_opzionali` | [ ] | | CL-04 | Cartella di lavoro = cartella dell'exe, non la cwd del chiamante (criterio MVP 1) | U/P | `Cartella_di_lavoro_e_quella_dell_exe` | [ ] | | CL-05 | Stdin chiuso subito: nessuna attesa di 3 s | P | `Stdin_chiuso_non_blocca` | [ ] | | CL-06 | CLI: l'exit code di `claude` è propagato (criterio MVP 3) | P/E | `ExitCode_propagato` | [ ] | | CL-07 | CLI: `claude` assente ⇒ messaggio chiaro ed exit code 127 | P/E | `Claude_assente_127` | [ ] | | CL-08 | CLI: stdout/stderr ereditati e ritrasmessi (criterio MVP 2: streaming) | E | `Output_ereditato_in_tempo_reale` | [ ] | | CL-09 | GUI: output catturato e ripulito agli estremi; stderr separato | P | `Output_catturato` | [ ] | | CL-10 | GUI: `claude` assente ⇒ eccezione riconoscibile, non crash | P | `Claude_assente_in_GUI` | [ ] | | CL-11 | Il catalogo modelli contiene gli ID attesi e gli effort previsti | U | `Catalogo_modelli_ed_effort` | [ ] | | CL-12 | Senza argomenti non parte la CLI; con argomenti sì (tutti uniti in un prompt) | E | `Argomenti_uniti_in_un_prompt` | [ ] | ### Cronologia — `Core/ChatStore.cs`, `Core/AppInfo.cs` | ID | Requisito | Liv. | Test | | |---|---|---|---|---| | ST-01 | Salvataggio e rilettura: id, titolo, data, `started`, messaggi con ruolo e testo | U | `Salva_e_rileggi` | [ ] | | ST-02 | Una chat senza messaggi non viene salvata | U | `Chat_vuota_non_salvata` | [ ] | | ST-03 | Titolo = inizio del primo messaggio utente: 50 caratteri + "…", a capo sostituiti da spazi | U | `Titolo_troncato` | [ ] | | ST-04 | L'id deve essere un Guid: un id con `..` o separatori non tocca il disco fuori dalla cartella | U | `Id_non_guid_rifiutato` | [ ] | | ST-05 | L'elenco è ordinato dalla più recente | U | `Elenco_ordinato` | [ ] | | ST-06 | File JSON corrotto ignorato senza eccezioni | U | `File_corrotto_ignorato` | [ ] | | ST-07 | Eliminazione: rimuove solo il file di quella chat; id inesistente senza errori | U | `Elimina_chat` | [ ] | | ST-08 | Compatibilità del formato: un JSON scritto dalla versione WinForms si legge | U | `Formato_storico_compatibile` | [ ] | | ST-09 | Migrazione: la cartella di un nome precedente (`LlmRun`) si sposta nella nuova al primo avvio; se la nuova esiste non si tocca nulla | U | `Migrazione_da_nome_precedente` | [ ] | | ST-10 | `AppInfo.Name` = `AssemblyName` del csproj | U | `Nome_da_assembly` | [ ] | | ST-11 | Errore di I/O nel salvataggio non perde la chat a schermo | U | vedi VM-12 | [ ] | ### Logica della chat — `Gui/MainViewModel.cs` | ID | Requisito | Liv. | Test | | |---|---|---|---|---| | VM-01 | Senza chat salvate: schermata iniziale (`ShowEmpty`), elenco vuoto | U | `Avvio_senza_chat` | [ ] | | VM-02 | All'avvio si riapre la chat più recente | U | `Avvio_riapre_ultima` | [ ] | | VM-03 | `CanSend` falso se il testo è vuoto o solo spazi, o se occupato | U | `CanSend` | [ ] | | VM-04 | Invio: il messaggio utente compare e viene **salvato prima** della risposta | U | `Messaggio_salvato_prima_della_risposta` | [ ] | | VM-05 | Risposta riuscita: aggiunta come "ai", `started` diventa vero, il secondo invio usa `--resume` | U | `Risposta_e_ripresa` | [ ] | | VM-06 | Risposta fallita (exit ≠ 0): messaggio "err" con codice e stderr; `started` resta falso e il tentativo successivo usa ancora `--session-id` | U | `Errore_non_avvia_la_sessione` | [ ] | | VM-07 | `claude` assente: messaggio "'claude' non trovato nel PATH." | U | `Claude_assente_messaggio` | [ ] | | VM-08 | Durante l'attesa: Invia e Nuova chat disabilitati; aprire/eliminare è ignorato | U | `Occupato_blocca_azioni` | [ ] | | VM-09 | Modello: "Predefinito" ⇒ nessun flag; modello scelto ⇒ ID; "Altro ID…" ⇒ testo libero ripulito | U | `Scelta_modello` | [ ] | | VM-10 | Effort: "Predefinito" ⇒ nessun flag; altrimenti il valore | U | `Scelta_effort` | [ ] | | VM-11 | Nuova chat: svuota la vista, nuovo guid, `started` falso, elenco aggiornato | U | `Nuova_chat` | [ ] | | VM-12 | Errore di I/O nel salvataggio non interrompe l'invio né cancella i messaggi | U | `Salvataggio_fallito_non_perde_chat` | [ ] | | VM-13 | Apertura chat: carica messaggi, `started` e id; chat inesistente ⇒ elenco ricaricato | U | `Apri_chat` | [ ] | | VM-14 | Eliminazione: con conferma rimuove; senza conferma non fa nulla; se è la chat aperta parte una nuova | U | `Elimina_con_e_senza_conferma` | [ ] | | VM-15 | Suggerimento: precompila il campo e chiede il focus | U | `Suggerimento_precompila` | [ ] | | VM-16 | Barra laterale: il comando commuta `SidebarOpen` | U | `Barra_laterale` | [ ] | | VM-17 | Copia: il comando passa il testo alla funzione degli appunti | U | `Copia_negli_appunti` | [ ] | | VM-18 | Notifiche di proprietà: `HasMessages`, `ShowEmpty`, `CanSend`, `IsCustomModel` cambiano quando devono | U | `Notifiche_proprieta` | [ ] | | VM-19 | Il titolo e la data dell'elenco riflettono l'ultimo scambio; la chat aperta è marcata attiva | U | `Elenco_chat_attiva` | [ ] | ### Renderer markdown — `Gui/MarkdownView.cs` | ID | Requisito | Liv. | Test | | |---|---|---|---|---| | MD-01 | Sorgente vuota o nulla ⇒ nessun contenuto, senza errori | R | `Sorgente_vuota` | [ ] | | MD-02 | Titoli h1–h6 con dimensioni decrescenti e peso semi-grassetto | R | `Titoli` | [ ] | | MD-03 | Paragrafi; a capo morbido = spazio, a capo forzato = riga nuova | R | `A_capo` | [ ] | | MD-04 | Grassetto, corsivo, barrato, anche annidati | R | `Enfasi` | [ ] | | MD-05 | Codice inline in carattere monospazio con sfondo | R | `Codice_inline` | [ ] | | MD-06 | Blocco di codice: monospazio, scorrimento orizzontale, pulsante Copia che copia solo il codice | R/G | `Blocco_codice_e_copia` | [ ] | | MD-07 | Liste puntate e numerate (rispetta il numero iniziale), annidate | R | `Liste` | [ ] | | MD-08 | Citazioni con barra laterale, anche annidate | R | `Citazioni` | [ ] | | MD-09 | Tabelle: intestazione in evidenza, allineamenti sinistra/centro/destra, righe irregolari | R | `Tabelle` | [ ] | | MD-10 | Linea orizzontale | R | `Linea_orizzontale` | [ ] | | MD-11 | Link: colore d'accento e sottolineatura; il clic apre solo `http(s)` | R | `Link_http_https` | [ ] | | MD-12 | Link: nessuna apertura se c'è una selezione in corso; cursore a mano sopra il link | R | `Link_con_selezione` | [ ] | | MD-13 | Posizioni dei link corrette con enfasi, codice inline e a capo prima del link | R | `Intervalli_link` | [ ] | | MD-14 | Immagini: si mostra il testo alternativo, non si scarica nulla | R | `Immagini_come_testo` | [ ] | | MD-15 | HTML grezzo disabilitato: i tag compaiono come testo, non come elementi | R | `Html_disabilitato` | [ ] | | MD-16 | Cambio di tema (chiaro/scuro): i colori si aggiornano ricostruendo la vista | R | `Cambio_tema` | [ ] | | MD-17 | Markdown malformato o blocchi sconosciuti non fanno crashare | R | `Markdown_malformato` | [ ] | | MD-18 | Testo selezionabile | R | `Testo_selezionabile` | [ ] | ### Finestra e interazione — `Gui/MainWindow*`, `Gui/App.axaml`, `Gui/ConfirmDialog.cs` | ID | Requisito | Liv. | Test | | |---|---|---|---|---| | UI-01 | La finestra si apre e mostra la schermata iniziale con quattro suggerimenti | G | `Apertura_schermata_iniziale` | [ ] | | UI-02 | Proporzioni 16:10, 75% dell'area utile (max 1440), minimo 900x600; senza schermo primario 1280x800 | G | `Dimensioni_finestra` | [ ] | | UI-03 | `Invio` invia, `Maiusc+Invio` va a capo, nessun invio a campo vuoto | G | `Tasti_invio` | [ ] | | UI-04 | Il pulsante Invia è disabilitato con campo vuoto o in attesa | G | `Pulsante_invia` | [ ] | | UI-05 | I messaggi hanno aspetto per ruolo: utente, Claude (con Copia), errore | G | `Template_per_ruolo` | [ ] | | UI-06 | La conversazione scorre in fondo a ogni nuovo messaggio | G | `Scorrimento_in_fondo` | [ ] | | UI-07 | Indicatore di attesa visibile solo durante l'elaborazione | G | `Indicatore_attesa` | [ ] | | UI-08 | Elenco chat: voce attiva evidenziata, clic apre, ✕ elimina; "Nessuna chat salvata" se vuoto | G | `Elenco_chat` | [ ] | | UI-09 | Il campo "Altro ID…" compare solo con quella voce del modello | G | `Campo_modello_personalizzato` | [ ] | | UI-10 | Il pulsante a tre linee mostra/nasconde la barra laterale | G | `Mostra_nascondi_barra` | [ ] | | UI-11 | Dialogo di conferma: restituisce vero con Elimina, falso con Annulla o chiusura | G | `Dialogo_conferma` | [ ] | | UI-12 | Accessibilità: ogni pulsante a sola icona e il campo messaggio hanno `AutomationProperties.Name` | G | `Nomi_accessibili` | [ ] | | UI-13 | Tema: sia il chiaro sia lo scuro risolvono tutti i token usati (nessuna risorsa mancante) | G | `Token_tema_completi` | [ ] | | UI-14 | Focus sul campo messaggio all'apertura, dopo la risposta e dopo "Nuova chat" | G | `Focus_campo` | [ ] | | UI-15 | Il nome dell'app compare nel titolo della finestra e nella barra laterale | G | `Nome_app_visibile` | [ ] | ### Eseguibile pubblicato | ID | Requisito | Liv. | Test | | |---|---|---|---|---| | PUB-01 | Il publish single-file `linux-x64` si compila e la CLI risponde con `FakeClaude` | E | `Publish_linux_smoke` | [ ] | | PUB-02 | Il publish single-file `win-x64` si compila e la CLI risponde con `FakeClaude` | E | `Publish_windows_smoke` | [ ] | | PUB-03 | Le librerie native (Skia/HarfBuzz) vengono estratte e la GUI si avvia (provata con timeout) | E | `Gui_si_avvia` | [ ] | ### Più motori (ADR-0002) — da compilare quando si implementa la Fase 4 Per **ogni** motore, la stessa batteria di contratto: | ID | Requisito | Liv. | | |---|---|---|---| | EN-01 | Argomenti del primo messaggio e della ripresa | U | [ ] | | EN-02 | Rilevamento nel PATH; assente ⇒ disabilitato e messaggio chiaro | U/P | [ ] | | EN-03 | Lettura dell'id di sessione dall'output (Codex) | U | [ ] | | EN-04 | Parser dell'output → markdown, con output di esempio reali salvati nei test | U | [ ] | | EN-05 | Modelli ed effort mostrati secondo il motore | U/G | [ ] | | EN-06 | Il JSON della chat memorizza il motore; cambiare motore apre una nuova chat | U | [ ] | | EN-07 | Politica dei permessi: nessun flag che salta i controlli senza scelta esplicita | U | [ ] | | EN-08 | Impostazioni persistite e rilette | U | [ ] | | EN-09 | Gestione del blocco di `agy -p` con output rediretto (timeout, errore chiaro) | P | [ ] | ## Come si eseguono (a implementazione avvenuta) Comandi previsti, da confermare con ADR-0003 → "Da verificare": - Tutti i test: `dotnet test` - Con copertura e soglie: `dotnet test --collect:"XPlat Code Coverage"` e controllo delle soglie della tabella sopra - Solo una categoria: `dotnet test --filter "Category=Unit"` (o `Gui`, `Process`, `E2E`) Su Linux i test GUI girano senza display (Headless). ## Definition of Done di una modifica - [ ] Doc aggiornati (MVP, ROADMAP, ADR se serve) - [ ] Righe nuove o modificate nella matrice, con ID - [ ] Test scritti e verdi su Windows e Linux - [ ] Soglie di copertura rispettate - [ ] Nessun requisito della modifica resta con la casella `[ ]` senza motivo scritto