[ERP-51] Écrire les tests Vitest des composables Catalog #26

Merged
tristan merged 1 commits from feature/ERP-51-0-9-frontend-s-ecrire-les-tests-vitest-des-composa into develop 2026-05-29 09:23:41 +00:00
Owner

Résumé

Couvre les deux composables Catalog extraits du refactor ERP-50 avec 42 tests Vitest unitaires (happy-dom, sans dépendance backend).

  • 14 tests sur `useCategoriesAdmin` (fetchAll/fetchTypes, includeDeleted, loading, error, reset, singleton)
  • 28 tests sur `useCategoryForm` (validation RG-1.02/1.04/1.05 + trim, POST/PATCH/DELETE, mapping 409 RG-1.07 + 422 violations, isDirty, loadFrom, reset, isolation)

Mocks via `vi.stubGlobal` (useApi / useI18n / useToast) et `vi.mock` (`~/shared/stores/auth` pour neutraliser l'auto-enregistrement `onAuthSessionCleared`). La suite tourne en ~1.2s.

Ticket Lesstime : #51

Tests automatisés

  • `make nuxt-test` ✓ 85 tests (dont 42 nouveaux), 0 échec, 1.2s

Reviewer

@matthieu

À tester en local

  • `make nuxt-test` passe
  • Mock `useApi` reste stable si le pattern d'auto-import Nuxt évolue
  • Couverture jugée suffisante des cas back miroir
## Résumé Couvre les deux composables Catalog extraits du refactor ERP-50 avec **42 tests Vitest unitaires** (happy-dom, sans dépendance backend). - 14 tests sur \`useCategoriesAdmin\` (fetchAll/fetchTypes, includeDeleted, loading, error, reset, singleton) - 28 tests sur \`useCategoryForm\` (validation RG-1.02/1.04/1.05 + trim, POST/PATCH/DELETE, mapping 409 RG-1.07 + 422 violations, isDirty, loadFrom, reset, isolation) Mocks via \`vi.stubGlobal\` (useApi / useI18n / useToast) et \`vi.mock\` (\`~/shared/stores/auth\` pour neutraliser l'auto-enregistrement \`onAuthSessionCleared\`). La suite tourne en **~1.2s**. Ticket Lesstime : #51 ## Tests automatisés - \`make nuxt-test\` ✓ 85 tests (dont 42 nouveaux), 0 échec, 1.2s ## Reviewer @matthieu ## À tester en local - [ ] \`make nuxt-test\` passe - [ ] Mock \`useApi\` reste stable si le pattern d'auto-import Nuxt évolue - [ ] Couverture jugée suffisante des cas back miroir
matthieu reviewed 2026-05-29 08:20:26 +00:00
matthieu left a comment
Owner

Review des tests Vitest (delta vs ERP-50). Les mocks refletent bien la realite ofetch (response.status/response._data) et useApi (body parse), pas de faux-vert ni d'await manquant detecte. 7 remarques ci-dessous, surtout des trous de couverture sur le mapping d'erreurs et les bornes de validation.

Review des tests Vitest (delta vs ERP-50). Les mocks refletent bien la realite ofetch (`response.status`/`response._data`) et useApi (body parse), pas de faux-vert ni d'await manquant detecte. 7 remarques ci-dessous, surtout des trous de couverture sur le mapping d'erreurs et les bornes de validation.
@@ -0,0 +176,4 @@
expect(types.value).toEqual([TYPE_VENTE, TYPE_ACHAT])
})
it('peuple error.value et vide types en cas d echec', async () => {
Owner

[low] Asymetrie non couverte : fetchAll remet error.value = null en debut (l.62 source), fetchTypes non. Consequence : un fetchAll en echec suivi d'un fetchTypes OK laisse error pollue par l'echec precedent — or la page admin charge les deux. Soit c'est un bug source a corriger (aligner fetchTypes), soit c'est voulu et il faut un test qui documente l'intention.

[low] Asymetrie non couverte : `fetchAll` remet `error.value = null` en debut (l.62 source), `fetchTypes` non. Consequence : un `fetchAll` en echec suivi d'un `fetchTypes` OK laisse `error` pollue par l'echec precedent — or la page admin charge les deux. Soit c'est un bug source a corriger (aligner fetchTypes), soit c'est voulu et il faut un test qui documente l'intention.
@@ -0,0 +10,4 @@
const mockToastSuccess = vi.hoisted(() => vi.fn())
const mockToastError = vi.hoisted(() => vi.fn())
vi.stubGlobal('useApi', () => ({
Owner

[low] Hygiene mocks : les vi.stubGlobal(...) ne sont jamais restaures. Risque reel faible (Vitest isole par fichier par defaut) mais ajouter afterEach(() => vi.unstubAllGlobals()) evite toute fuite si l'isolation change ou si un autre spec du meme worker depend du vrai useApi.

[low] Hygiene mocks : les `vi.stubGlobal(...)` ne sont jamais restaures. Risque reel faible (Vitest isole par fichier par defaut) mais ajouter `afterEach(() => vi.unstubAllGlobals())` evite toute fuite si l'isolation change ou si un autre spec du meme worker depend du vrai useApi.
@@ -0,0 +130,4 @@
expect(form.errors.value.name).toBe('admin.categories.validation.nameLength')
})
it('signale erreur si name fait 121 caracteres (> 120, RG-1.04)', () => {
Owner

[medium] Bornes valides exactes non testees. On verifie 1 caractere ('A') et 121, mais jamais 2 ni 120 — les bornes acceptees. Un off-by-one dans la condition (length <= 2 ou length >= 120) passerait inapercu, et le test 'passe quand valide' utilise 'Vis' (3 car) qui ne touche aucune borne.

Ajouter : name='AB' -> ok=true, et name='A'.repeat(120) -> ok=true.

[medium] Bornes valides exactes non testees. On verifie 1 caractere ('A') et 121, mais jamais 2 ni 120 — les bornes acceptees. Un off-by-one dans la condition (`length <= 2` ou `length >= 120`) passerait inapercu, et le test 'passe quand valide' utilise 'Vis' (3 car) qui ne touche aucune borne. Ajouter : `name='AB'` -> ok=true, et `name='A'.repeat(120)` -> ok=true.
@@ -0,0 +232,4 @@
// La cle est interpolee avec le nom soumis : on retrouve "Vis" dans
// les params i18n (stub serialise les params).
expect(form.errors.value.name).toContain('admin.categories.toast.duplicate')
expect(form.errors.value.name).toContain('"name":"Vis"')
Owner

[low] Fragilite : toContain('"name":"Vis"') couple l'assertion au format JSON.stringify du stub i18n. Si le stub change de serialisation, le test casse sans regression reelle. Preferer un mock i18n qui capture (key, params) separement et asserter params.name === 'Vis'.

[low] Fragilite : `toContain('"name":"Vis"')` couple l'assertion au format `JSON.stringify` du stub i18n. Si le stub change de serialisation, le test casse sans regression reelle. Preferer un mock i18n qui capture `(key, params)` separement et asserter `params.name === 'Vis'`.
@@ -0,0 +280,4 @@
await form.submitCreate()
expect(form.errors.value.categoryType).toBe('Type invalide.')
})
Owner

[medium] Trou de couverture sur le mapping 422. Les 3 cas 422 testes mappent tous name ou categoryType, donc mapServerViolations renvoie toujours true. La branche status === 422 && mapServerViolations === false (propertyPath inconnu, ou violations: []) n'est jamais exercee : c'est pourtant elle qui doit retomber sur extractErrorMessage -> _global + toast.error. C'est le chemin le plus a risque (violation serveur sur un champ qu'on ne mappe pas = erreur silencieuse en prod).

Ajouter un test : 422 avec violations:[{propertyPath:'someField',message:'x'}] (ou tableau vide) -> attendre errors._global rempli + mockToastError appele 1x.

Au passage : sur les deux 422 deja testes (l.262, l.282), asserter aussi errors._global === '' pour verrouiller que le mapping n'a pas en plus declenche le fallback global.

[medium] Trou de couverture sur le mapping 422. Les 3 cas 422 testes mappent tous `name` ou `categoryType`, donc `mapServerViolations` renvoie toujours `true`. La branche `status === 422 && mapServerViolations === false` (propertyPath inconnu, ou `violations: []`) n'est jamais exercee : c'est pourtant elle qui doit retomber sur `extractErrorMessage` -> `_global` + `toast.error`. C'est le chemin le plus a risque (violation serveur sur un champ qu'on ne mappe pas = erreur silencieuse en prod). Ajouter un test : 422 avec `violations:[{propertyPath:'someField',message:'x'}]` (ou tableau vide) -> attendre `errors._global` rempli + `mockToastError` appele 1x. Au passage : sur les deux 422 deja testes (l.262, l.282), asserter aussi `errors._global === ''` pour verrouiller que le mapping n'a pas en plus declenche le fallback global.
@@ -0,0 +282,4 @@
expect(form.errors.value.categoryType).toBe('Type invalide.')
})
it('fallback en erreur globale + toast si le status n est ni 409 ni 422', async () => {
Owner

[low] extractErrorMessage partiellement couvert. On teste hydra:description (ici) et detail (submitDelete), mais pas la branche description seule ni le fallback final 'Une erreur est survenue.' (data vide / non-objet). A noter : ?? ne saute PAS une chaine vide, donc un payload {detail:''} court-circuiterait description — comportement non verifie. Un test avec _data sans aucun de ces champs (ou null) verrouillerait le message par defaut.

[low] extractErrorMessage partiellement couvert. On teste `hydra:description` (ici) et `detail` (submitDelete), mais pas la branche `description` seule ni le fallback final `'Une erreur est survenue.'` (data vide / non-objet). A noter : `??` ne saute PAS une chaine vide, donc un payload `{detail:''}` court-circuiterait `description` — comportement non verifie. Un test avec `_data` sans aucun de ces champs (ou null) verrouillerait le message par defaut.
@@ -0,0 +388,4 @@
expect(result).toBeNull()
expect(form.errors.value.name).toContain('admin.categories.toast.duplicate')
expect(form.errors.value.name).toContain('"name":"Doublon"')
})
Owner

[medium] submitUpdate sous-couvert sur les erreurs. Seul le 409 update est teste. Manquent :

  • le 422 en mode update (le payload est construit differemment de create) ;
  • le fallback attemptedName (l.281-283 source) : quand seul categoryType change, payload.name est absent et attemptedName retombe sur name.value.trim(). Cette branche est morte en couverture.

Ajouter un test 'seul categoryType modifie puis 409/422' verifiant que le nom remonte bien via le fallback.

[medium] submitUpdate sous-couvert sur les erreurs. Seul le 409 update est teste. Manquent : - le 422 en mode update (le payload est construit differemment de create) ; - le fallback `attemptedName` (l.281-283 source) : quand seul `categoryType` change, `payload.name` est absent et `attemptedName` retombe sur `name.value.trim()`. Cette branche est morte en couverture. Ajouter un test 'seul categoryType modifie puis 409/422' verifiant que le nom remonte bien via le fallback.
tristan added 1 commit 2026-05-29 09:20:49 +00:00
test(catalog) : cover useCategoriesAdmin and useCategoryForm composables
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m29s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m0s
4cefd6f6eb
Adds 42 Vitest unit tests for the two composables extracted from the
admin categories page in ERP-50.

- useCategoriesAdmin (14 tests): fetchAll/fetchTypes, includeDeleted toggle,
  loading flags, error handling, reset, singleton sharing.
- useCategoryForm (28 tests): validation rules RG-1.02/1.04/1.05, trim,
  POST/PATCH/DELETE wiring, 409 (RG-1.07) and 422 violation mapping,
  isDirty, loadFrom, reset, instance isolation.

Mocks useApi/useI18n/useToast via vi.stubGlobal and ~/shared/stores/auth
to keep the suite hermetic (no backend required).
tristan force-pushed feature/ERP-51-0-9-frontend-s-ecrire-les-tests-vitest-des-composa from 091ccb6f28 to 4cefd6f6eb 2026-05-29 09:20:49 +00:00 Compare
tristan merged commit 53e19d61ac into develop 2026-05-29 09:23:41 +00:00
tristan deleted branch feature/ERP-51-0-9-frontend-s-ecrire-les-tests-vitest-des-composa 2026-05-29 09:23:41 +00:00
Sign in to join this conversation.