Files
Password-Manager/TEST_SCENARIOS.md
T
r-zakarya 47c29bd04e fix(esc): reauth / picker / quick-search were missing from the Esc chain
Three static modals had no entry in the fallback Esc handler, so Esc over
them fell through and closed the editor behind (or did nothing when no
editor was open — reauth and the autofill picker could only be dismissed by
mouse).

Also scopes the stray-modal catch added in a95becd to [data-dynamic]. As
written it matched ANY open .modal, so an Esc over reauth / quick-search /
the autofill picker would have removed those static nodes from the DOM for
good, breaking the feature until restart. Only the runtime-built folder
modal carries the marker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 21:17:53 +01:00

9.5 KiB

Scénarios de test — écriture concurrente & fermeture de l'éditeur

Sessions du 2026-07-22. Tous les tests sont manuels (ils portent sur des interactions UI que le harness node:test ne couvre pas). Rebuild obligatoire avant de commencer : delphi-backend\assets\BuildAssets.cmd puis build Delphi.

Convention : PRÉ = état de départ, ACTION, ATTENDU. Un test qui échoue = régression, noter le commit courant.


Les 4 classes de bug

# Classe Cause racine Corrigé par
A Écriture extérieure écrasée au Save L'éditeur garde le payload chargé à l'ouverture et le re-poste entier ; toute écriture faite ailleurs entre-temps est perdue editorAbsorbChange (d146fbe)
B Éditeur fermé sans garde-fou closeSlideOver() appelé sans condition + click-outside non filtré closeEditorForDeleted (3dbce78), whitelist (e1ddc65)
C PUT /entries sans withEncryptedMeta Les colonnes en clair sont droppées côté serveur → le body ne contient rien de stockable et blanchit les *_enc d146fbe
D UI construite sans regarder la vue Le menu kebab est partagé entre vues ; la corbeille n'était pas testée d146fbe

A — Éditeur ouvert vs écriture extérieure

Préparer : 3 entrées A, B, C, dont A dans le dossier Work avec le tag old.

A1 · batch Add tag sur l'entrée éditée

  • PRÉ : A ouverte dans l'éditeur, taper du texte dans NOTE/PASSWORD (ne pas sauver)
  • ACTION : cocher A + B → barre de sélection → Add tag → shared → puis Save dans l'éditeur
  • ATTENDU : le chip shared apparaît immédiatement dans l'éditeur ; après Save, A porte old, shared et la modif tapée. Le bouton Save reste visible (l'entrée était déjà dirty) mais le tag seul ne rend pas dirty.✅

A2 · batch Add tag sur une autre entrée

  • PRÉ : A ouverte + dirty
  • ACTION : cocher B + C seulement → Add tag → x → Save dans l'éditeur
  • ATTENDU : l'éditeur ne bouge pas (aucun chip ajouté), A conserve ses tags, B/C taguées.✅

A3 · batch Move to folder sur l'entrée éditée

  • ACTION : A ouverte + dirty → cocher A → Move to folder… → Perso → Save
  • ATTENDU : le select Folder de l'éditeur bascule sur Perso tout seul ; après Save l'entrée est bien dans Perso.✅

A4 · dossier choisi dans l'éditeur puis move extérieur

  • ACTION : A ouverte, changer Folder → Archive (ne pas sauver) → cocher A → Move to folder… → Perso → Save
  • ATTENDU : Perso gagne (action extérieure = intention la plus récente). Comportement documenté, pas un bug. ✅

A5 · glisser une entrée sur un dossier (moveEntryToFolder)

  • ACTION : A ouverte + dirty → glisser la ligne A sur Perso dans la sidebar → Save
  • ATTENDU : identique à A3.✅

A6 · glisser une multi-sélection sur un dossier (moveEntriesToFolder) ⚠️

  • ACTION : cocher A + B → glisser sur Perso → recharger (F5 impossible : relancer l'app)
  • ATTENDU : dossier changé et nom / username / site / tags intacts.
  • Historique : ce chemin n'appelait pas withEncryptedMeta → il blanchissait les métadonnées chiffrées. Si des entrées ont été déplacées ainsi avant le fix, elles sont peut-être déjà vides — vérifier.✅

A7 · suppression du dossier de l'entrée éditée

  • ACTION : A (dossier Work) ouverte + dirty → sidebar → supprimer le dossier Work → Save
  • ATTENDU : le select de l'éditeur passe à All ; après Save l'entrée est dans All, le dossier supprimé ne réapparaît pas.✅

A8 · tags édités des deux côtés

  • ACTION : A ouverte, ajouter le chip mine dans l'éditeur (ne pas sauver) → batch Add tag shared sur A → Save
  • ATTENDU : old, mine, shared — aucun des trois perdu.✅

A9 · favori / épingle pendant l'édition (non-régression)

  • ACTION : A ouverte + dirty → cliquer ★ et 📌 sur la ligne A → Save
  • ATTENDU : l'entrée reste favorite et épinglée (ces colonnes ne sont pas dans le PUT de l'éditeur).✅

A10 · icône / pièce jointe pendant l'édition (non-régression)

  • ACTION : A ouverte + dirty → Settings → Fetch missing favicons (ou upload d'icône) → Save
  • ATTENDU : l'icône survit au Save (endpoint dédié, hors payload éditeur).✅ icon not refreshed in edit

A11 · sync / import pendant que l'éditeur est ouvert — trou connu

  • ACTION : A ouverte + dirty → lancer Sync now (ou importer un JSON en mode overwrite) qui met A à jour → Save
  • ATTENDU AUJOURD'HUI : le Save de l'éditeur écrase la version venue du sync/import. Non corrigé — voir « Reste à traiter » plus bas.

B — Fermeture de l'éditeur & message de discard

Le message n'apparaît que si confirmOnUnsaved est actif et que l'entrée est dirty. Tester avec askBeforeDelete ON puis OFF (Settings → Security).

B1 · ✕ sur une autre ligne

  • PRÉ : A ouverte + dirty
  • ACTION : cliquer ✕ (Move to trash) sur la ligne B
  • ATTENDU : B part à la corbeille, l'éditeur de A reste ouvert avec les modifs. Vrai dans les deux réglages de askBeforeDelete.

B2 · ✕ sur la ligne éditée, askBeforeDelete = OFF

  • ACTION : A ouverte + dirty → ✕ sur A
  • ATTENDU : modale « Discard unsaved changes? ». Annuler ⇒ rien n'est supprimé et l'éditeur reste ouvert. Confirmer ⇒ A à la corbeille + éditeur fermé.

B3 · ✕ sur la ligne éditée, askBeforeDelete = ON

  • ATTENDU : une seule modale (« Move to trash »). Pas de double prompt.

B4 · batch Delete incluant l'entrée éditée

  • ACTION : A ouverte + dirty → cocher A + B → Delete
  • ATTENDU : une seule modale (« 2 entries will be moved to trash »), puis l'éditeur se ferme.

B5 · batch Delete excluant l'entrée éditée

  • ATTENDU : l'éditeur reste ouvert, modifs intactes.

B6 · corbeille

  • ACTION : ouvrir une entrée de la corbeille via Trash actions, puis : (a) Delete forever dessus, (b) batch purge la contenant, (c) Empty trash
  • ATTENDU : dans les trois cas l'éditeur se ferme s'il portait une entrée purgée, sinon il reste.

B7 · boutons de la barre de sélection

  • ACTION : A ouverte + dirty → cocher B → cliquer successivement Move to folder…, Add tag, Clear
  • ATTENDU : aucun de ces clics ne ferme l'éditeur ni n'affiche de discard.

B8 · bouton Empty trash (barre de titre)

  • ATTENDU : idem, pas de fermeture parasite de l'éditeur.

B9 · chemins de fermeture légitimes (non-régression)

  • Esc, ✕ de l'éditeur, clic sur le fond de la grille, ouverture d'une autre entrée
  • ATTENDU : message de discard à chaque fois si dirty ; annuler garde l'éditeur ouvert sur la bonne entrée.

B10 · verrouillage (comportement assumé)

  • ACTION : A ouverte + dirty → auto-lock ou Lock vault
  • ATTENDU : fermeture sans message, modifs perdues. Volontaire : la sécurité prime.

C — withEncryptedMeta sur tout write /entries

Test générique, à rejouer après toute modif touchant un body POST/PUT /entries : faire l'action, relancer l'app (pour relire depuis SQLite), vérifier que nom / site / username / tags s'affichent encore.

Chemins à couvrir : Save éditeur (neuf + existant), duplicate, import (bulk + overwrite), sync (applyRemoteSnapshot), addTagToEntry, batchAddTag, moveEntryToFolder, moveEntriesToFolder, batchMoveToFolder, migrateMetadataAtRest (au unlock), rotation du master password.

Symptôme du bug : après relance, lignes au nom vide / (no name).


D — Vue corbeille

D1 · menu kebab en table view

  • ACTION : corbeille + vue table → kebab d'une ligne
  • ATTENDU : Restore / Copy… / Delete forever uniquement. Pas de Pin / Favorite / Edit / Duplicate / Move to trash.

D2 · vues cartes et liste

  • ATTENDU : boutons restore + purge (déjà le cas avant), pas de drag possible.

D3 · clic sur une ligne de corbeille

  • ATTENDU : ouvre Trash actions, jamais l'éditeur (une entrée supprimée ne doit pas pouvoir être sauvegardée).

Reste à traiter (constaté pendant l'audit, non corrigé)

  1. Sync / import pendant l'édition (scénario A11). editorAbsorbChange ne couvre que les écritures locales tag/dossier. Un vrai filet serait de mémoriser updated_at à l'ouverture de l'éditeur et, au Save, de comparer avec la valeur courante en mémoire : si elle a bougé, demander « cette entrée a changé ailleurs — écraser ? ». Ça couvre d'un coup sync, import, et tout futur chemin d'écriture.

  2. Modale d'entrée héritée = code mort. openEntryModal n'a aucun appelant ; saveEntry, closeEntryModal, le markup #entryModal et leurs listeners ne sont plus atteignables. Danger : saveEntry fait un PUT partiel (ni kind, ni totp_secret, ni custom_fields, ni template) — exactement le bug que moveEntryToFolder documente comme à ne jamais refaire. À supprimer (JS + HTML) tant qu'il n'est pas recâblé par erreur.

  3. Click-outside : #busyOverlay et .toast ne sont pas dans la liste blanche. Cliquer un toast ou l'overlay d'attente pendant une opération longue ferme l'éditeur. Impact faible, correctif = 2 lignes dans la même liste.