mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-05-18 08:18:15 +02:00
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>
316 lines
16 KiB
Markdown
316 lines
16 KiB
Markdown
---
|
|
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.
|
|
|
|
---
|
|
|
|
<a id="risque-jest-rn-config-node"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-faux-test-negatif"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-helpers-copies-tests"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-test-ecran-indirect"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-test-facade-flux-reel"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-tests-presence-textuelle-faux-gardefou"></a>
|
|
## 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.vue` → `pages/<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)
|
|
|
|
---
|
|
|
|
<a id="risque-catch-false-test-skip-e2e"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-tests-e2e-6-causes-racines"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-tests-string-match-repointer-composant"></a>
|
|
## 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** :
|
|
|
|
```typescript
|
|
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** :
|
|
|
|
```typescript
|
|
// 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.vue` → `pages/<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
|