Files
_Assistant_Lead_Tech/knowledge/frontend/risques/tests.md
MaksTinyWorkshop b3417ad77b capitalisation: intégration ~60 entrées RL799_V2 (triage 2026-05-02)
Triage du 95_a_capitaliser.md (~75 propositions) :
- 60 entrées intégrées dans knowledge/ (backend, frontend, workflow)
- 4 nouveaux fichiers : backend/patterns/tests.md, backend/risques/tests.md,
  frontend/patterns/general.md, workflow/patterns/general.md
- 6 doublons rejetés
- Mise à jour des READMEs index pour refléter les nouvelles entrées
- 95_a_capitaliser.md restauré à sa structure initiale
- 40_decisions_et_archi.md : décision mono-tenant déployable vs SaaS multi-tenant
- 90_debug_et_postmortem.md : sub-agents Write indisponible, effet iceberg CI,
  prisma migrate diffs cosmétiques

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:12:44 +02:00

16 KiB

title: Frontend — Risques & vigilance : Tests domain: frontend bucket: risques tags: [tests, jest, react-native, ts-jest, coverage, facade] applies_to: [analysis, implementation, review, debug] severity: high validated_on: 2026-04-07 source_projects: [app-alexandrie, app-template-resto, RL799_V2]

Frontend — Risques & vigilance : Tests

Extrait de la base de connaissance Lead_tech. Voir knowledge/frontend/risques/README.md pour l'index complet.


Jest React Native — config node bloque les composants .tsx

Risques

  • SyntaxError: Cannot use import statement outside a module lors de l'import d'un barrel .ts qui réexporte des .tsx
  • Impossible d'importer des composants React Native dans les tests — JSX non transformé

Symptômes

  • Erreur de syntaxe inattendue au run des tests sur un fichier .ts qui importe un .tsx
  • Les tests de tokens passent mais tout test touchant un composant échoue

Bonnes pratiques / mitigations

  • transform: { '^.+\\.ts$': 'ts-jest' } ne transforme que .ts — pas .tsx
  • Pattern recommandé : tester la logique pure (tokens, valeurs de style) dans .spec.ts, le rendu visuel dans .spec.tsx avec une config séparée (@testing-library/react-native + babel-jest)
  • Exporter le StyleSheet de chaque composant pour le tester sans JSX (voir pattern dédié dans 10_frontend_patterns_valides.md)
  • Contexte technique : React Native / Jest / ts-jest — app-alexandrie 19-03-2026

Faux test négatif — tester le helper au lieu de tester l'exclusion

Risques

  • Un test nommé "X n'utilise pas Y" qui appelle Y en interne est un test normal mal documenté, pas un test d'exclusion
  • Donne une fausse confiance sur le comportement par défaut du helper

Symptômes

  • Test intitulé "sans fallback, la valeur EN vide n'est pas remplacée" mais qui appelle le helper avec fallback activé

Bonnes pratiques / mitigations

  • Un vrai test négatif vérifie que X n'importe pas Y, ou que le comportement par défaut empêche l'effet indésirable

  • Pour un helper à fallback optionnel : tester explicitement le cas fallbackToFr=false (défaut) avec une valeur vide

  • Contexte technique : TypeScript / Jest — app-template-resto 17-03-2026


Helpers copiés localement dans les tests (faux positif permanent)

Risques

  • La logique réellement exécutée en production peut diverger du helper copié dans le test sans casser aucun test.
  • Un refactor du module source ne casse pas le test — la couverture est illusoire.

Symptômes

  • Fonctions utilitaires redéfinies dans *.spec.ts plutôt qu'importées depuis le module de production
  • Tests verts malgré une régression dans le code source

Bonnes pratiques / mitigations

  • Les tests doivent importer le module réellement utilisé par l'écran/composant, jamais dupliquer la logique.

  • Si la logique est partagée entre écran et test, l'extraire dans un utilitaire partagé (single source of truth).

  • Checklist review : aucune fonction de production recopiée dans *.spec.ts.

  • Contexte technique : TypeScript / Jest — 30-03-2026


Test d'écran indirect — logique UI validée via helper adjacent non relié au flux

Risques

  • Le test reste vert même si la logique de décision UI dans le screen diverge du helper testé.
  • Un changement d'implémentation de l'écran ne casse aucun test.

Symptômes

  • *.spec.ts d'un écran qui n'importe pas le composant/écran mais seulement un helper utilitaire adjacent
  • Couverture affichée OK mais comportement réel de l'écran non testé

Bonnes pratiques / mitigations

  • La logique de décision UI doit être soit testée via rendu composant (@testing-library/react-native), soit extraite dans un module dédié importé par le screen ET par le test (single source of truth).

  • Règle : un test qui n'importe pas le composant écran ni son module de logique ne peut pas valider le comportement de l'écran.

  • Contexte technique : React Native / Jest — 30-03-2026


Test de façade — helpers adjacents validés à la place du flux réel

Risques

  • Une tâche d'intégration (conversion, persistance, atomicité, contrat UI) est clôturée alors que les tests ne valident que des helpers purs (chemins, labels, sanitization).
  • Le pipeline réel, la persistance DB et les transitions UI ne sont jamais exercés.

Symptômes

  • Tests *.spec.ts créés uniquement sur des fonctions utilitaires pures pour une story qui promet un pipeline
  • La tâche est cochée mais aucun test n'appelle le module de traitement de production

Bonnes pratiques / mitigations

  • Si une tâche promet conversion, statut DB, atomicité ou contrat UI/public : le test doit appeler le module de production correspondant ou un seam injecté unique.

  • Règle : un test qui ne vérifie que des helpers adjacents ne peut pas clôturer une tâche d'intégration.

  • Contexte technique : TypeScript / Jest — app-template-resto 31-03-2026


Tests de présence textuelle = faux garde-fou de non-régression

Risques

  • Des tests basés uniquement sur content.includes(...) valident du texte statique tout en laissant passer des régressions réelles sur auth/API et comportements UI
  • Les AC fonctionnels sont déclarés validés alors que le flux réel (autosave, submit, transitions d'état) n'est pas exercé

Symptômes

  • Tests qui lisent le fichier .vue et assertent uniquement des chaînes (includes) sans exécuter le composant ni ses interactions
  • Story cochée malgré des régressions fonctionnelles invisibles aux tests verts
  • Services API et guards d'accès validés par des assertions textuelles au lieu de tests comportementaux

Bonnes pratiques / mitigations

  • Exiger au moins un test comportemental par flux critique (montage composant + interaction + assertion d'effet)

  • Reléguer les tests textuels au rôle de smoke structurel non bloquant

  • Pour les services API et guards d'accès : exiger un test exécutant réellement la fonction (mock fetch/session/router) et validant statuts d'erreur + contrat d'appel

  • Pour les templates/checklists critiques : ne pas se limiter à la présence de mots-clés, valider la structure attendue (sections obligatoires, champs non vides, format minimal)

  • Règle : si un test vérifie un comportement (ex: "le menu se ferme après clic"), il doit monter le composant, pas chercher une string dans le source

  • Contexte technique : Vue 3 / node:test — RL799_V2 02-04-2026

Cas additionnel : obsolescence silencieuse après refacto structurel

Au-delà du faux garde-fou de non-régression, un test en readFileSync(path) + content.includes(...) devient obsolète sans alarme dès qu'une réorganisation structurelle déplace le code visé. Trois variantes vécues :

  1. Fichier déplacé par scoping (ex: pages/X.vuepages/<module>/X.vue) → ENOENT au runtime, le test crashe au lieu de signaler une régression métier
  2. Logique extraite dans un composable / sous-composant → la chaîne attendue ne vit plus dans le .vue mais dans composables/use<X>.ts ; le .vue existe encore mais ne contient plus le pattern, donc le test échoue sur une assertion sans rapport avec la vraie cause
  3. Variable supprimée du <script setup> mais conservée dans le template → string-match passe (le template contient toujours la string), crash JS au mount du composant

Mitigations spécifiques :

  • Centraliser le path du fichier visé dans une constante en tête de fichier de test (pas resolve(...) inline) — facilite le rerouting en cas de refacto

  • Lors d'une extraction de logique dans un composable / sous-composant, grep les tests structurels qui pointaient le fichier d'origine et les rediriger vers le nouveau chemin

  • Pour les composants interactifs (formulaires, modales, listes avec actions), compléter le string-match par au moins un test de mount via @vue/test-utils qui vérifie le render sans crash — c'est le seul moyen de valider la cohérence script ↔ template

  • Contexte technique : Vue 3 / vitest — RL799_V2 30-04-2026 (3 cas observés sur la même session)


Anti-pattern : .catch(() => false) + test.skip dans les tests E2E

Risques

  • Le pattern await locator.isVisible().catch(() => false) suivi de test.skip(true, ...) masque les erreurs réelles (sélecteur cassé, timeout, changement de structure) derrière un skip silencieux
  • Un AC peut rester perpétuellement non testé sans qu'aucun rapport ne le signale comme problème

Symptômes

  • Tests skippés en permanence dans les rapports CI
  • AC marqués [x] dans la story mais jamais réellement validés

Bonnes pratiques / mitigations

  • Pour vérifier si un élément optionnel est présent (données dépendantes du seed), utiliser await locator.count() qui retourne 0 sans lancer d'exception, puis test.skip uniquement si count === 0

  • Réserver .catch() aux cas où une exception est réellement attendue et documentée

  • Signal review : .catch(() => false) suivi de test.skip dans un test E2E

  • Contexte technique : Playwright / E2E — RL799_V2 08-04-2026


Tests E2E qui rotent — 6 causes-racines récurrentes

Risques

  • Sur une suite E2E mature, les fails ne viennent presque jamais d'un bug applicatif : ils viennent d'un désalignement test ↔ code de prod
  • Conclure à une régression métier alors que c'est du test obsolète fait perdre du temps et masque les vraies régressions

Symptômes

Les 6 patterns observés sur RL799_V2 (Playwright + Vue 3 + refactors UI fréquents) :

  1. Testid changé sans MAJ tests : getByTestId('library-entries') timeout, mais le composant expose data-testid="document-list". Cause : refactor d'un composant qui fusionne plusieurs vues en un composant générique avec un testid neutre.

  2. Labels métier qui changent : await expect(badge).toHaveText('Publiée') échoue, le badge affiche désormais 'Convoquée'. Cause : refactor lifecycle qui renomme les labels affichés sans toucher aux testids structurels.

  3. Menus / dropdowns conditionnels : getByTestId('odj-insert-menu').click() timeout aléatoire — parfois le menu s'ouvre, parfois pas. Cause : UX qui adapte le flow selon l'état (1 seul type → bouton direct, plusieurs → menu).

  4. Features supprimées : await page.goto('/secretaire?soireeId=xxx') charge la page mais ne sélectionne plus la soirée. Cause : query param retiré au profit d'une navigation par onglets + click sur card.

  5. Refactor visuel : await expect(badge).toHaveText('A∴') échoue, le badge affiche désormais une icône SVG. Cause : refactor de représentation (texte → icône) sans toucher au testid.

  6. Cleanup post-test : test métier passe en 2 s, mais le finally { await restoreEntry() } timeout à 30 s. Cause : le PATCH de cleanup tape sur une route lente (audit log, notif, validation).

Bonnes pratiques / mitigations

À chaque diagnostic E2E, vérifier d'abord ces 6 hypothèses avant de conclure à une régression métier :

  • Cause 1 : grep data-testid dans le composant cible avant de modifier le test. Ne jamais "deviner" le testid à partir du nom de la page.
  • Cause 2 : préférer asserter sur des classes CSS modifier (.badge--published) ou des testids d'état (data-testid="status-published") plutôt que sur du texte humain (cf. pattern-asserter-classe-css-modifier-vs-texte dans frontend/patterns/tests.md).
  • Cause 3 : guard conditionnel via isVisible({ timeout: 1_000 }).catch(() => false) pour gérer les deux branches.
  • Cause 4 : quand un test commence par une URL avec query param, vérifier en premier que ce param est encore consommé par la page (grep useRoute / route.query dans le composant).
  • Cause 5 : asserter la classe CSS modifier (plus stable que innerHTML qui contiendrait le SVG).
  • Cause 6 : cleanup best-effort avec timeout court (cf. pattern-cleanup-e2e-best-effort dans frontend/patterns/tests.md).

Méta-leçon

Quand on découvre N fails E2E après une période de refactor intense :

  1. Lancer la suite complète une fois pour avoir la liste exhaustive
  2. Trier par cause-racine plutôt que par fichier
  3. Fixer en lots cohérents (1 commit par cause-racine) plutôt qu'1 commit par fail
  4. Capitaliser les patterns dès qu'ils se répètent (> 2 occurrences)
  • Contexte technique : Playwright / Vue 3 — RL799_V2 25-04-2026

Tests string-match .vue — limites et compléments après extraction

Risques

  • Quand on extrait une section/onglet vers un sous-composant, les assertions readFileSync + content.includes('Ordre du jour') échouent — la string est maintenant dans le sous-composant, pas dans la page
  • Mauvaises réactions : supprimer le test (perd la garantie), .skip() (dette accumulée), inverser en toBeFalsy() (régression masquée), repointer aveuglément (peut camoufler un problème)

Symptômes

AssertionError: expected false to be truthy
  expect(tenuesPage.includes('Ordre du jour')).toBeTruthy();
                                                ^

Bonnes pratiques / mitigations

Diagnostic : lire l'assertion et identifier ce qu'elle garantit (présence d'un comportement métier, d'un data-testid critique, d'un ordre visuel).

Repointer correctement :

const here = dirname(fileURLToPath(import.meta.url));
const root = resolve(here, '../../../..');

// Page coquille (ce qui reste : layout, tabs, rendu conditionnel)
const tenuesPage = readFileSync(
  resolve(root, 'src/pages/tenues/TenuesPage.vue'),
  'utf-8',
);
// Sous-composant qui incarne désormais le markup d'un onglet
const prochaineView = readFileSync(
  resolve(root, 'src/pages/tenues/components/ProchaineTenueView.vue'),
  'utf-8',
);
// Composable qui incarne désormais la logique d'un onglet
const useProchaine = readFileSync(
  resolve(root, 'src/pages/tenues/composables/useProchaineTenue.ts'),
  'utf-8',
);

test('TenuesPage utilise le modèle de vue testable pour tab/titre', () => {
  expect(tenuesPage.includes('resolveTenuesTab')).toBeTruthy();
  expect(useProchaine.includes('getProchaineTenueTitle')).toBeTruthy();
});

Renommer aussi le test si pertinent :

// Avant
test('TenuesPage redirige vers une page dédiée en cas de 403...', () => { /* … */ });

// Après — le nom devient un index sémantique
test('usePastTenues redirige vers une page dédiée en cas de 403...', () => { /* … */ });

Anti-pattern : tests structurels qui bougent en cascade

Si tes tests doivent être systématiquement mis à jour à chaque refactor, c'est que beaucoup de garanties sont vérifiées par string-match plutôt que par comportement. Pour les composants interactifs critiques (formulaires, listes avec actions, modales), doubler avec un test de mount @vue/test-utils qui survit aux refactors.

Trois variantes vécues

  1. Fichier déplacé par scoping (pages/X.vuepages/<module>/X.vue) → ENOENT au runtime, le test crashe au lieu de signaler une régression métier
  2. Logique extraite dans un composable / sous-composant → la chaîne attendue ne vit plus dans le .vue ; le test échoue sur une assertion sans rapport avec la vraie cause
  3. Variable supprimée du <script setup> mais conservée dans le template → string-match passe (le template contient toujours la string), crash JS au mount du composant

Mitigations spécifiques :

  • Centraliser le path du fichier visé dans une constante en tête de fichier de test — facilite le rerouting en cas de refacto

  • Lors d'une extraction, grep les tests structurels qui pointaient le fichier d'origine et les rediriger vers le nouveau chemin

  • Pour les composants interactifs, compléter par au moins un test de mount via @vue/test-utils qui vérifie le render sans crash

  • Contexte technique : Vue 3 / Vitest — RL799_V2 29-04-2026