[ERP-51] Écrire les tests Vitest des composables Catalog #26
Reference in New Issue
Block a user
Delete Branch "feature/ERP-51-0-9-frontend-s-ecrire-les-tests-vitest-des-composa"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Résumé
Couvre les deux composables Catalog extraits du refactor ERP-50 avec 42 tests Vitest unitaires (happy-dom, sans dépendance backend).
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
Reviewer
@matthieu
À tester en local
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 () => {[low] Asymetrie non couverte :
fetchAllremeterror.value = nullen debut (l.62 source),fetchTypesnon. Consequence : unfetchAllen echec suivi d'unfetchTypesOK laisseerrorpollue 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', () => ({[low] Hygiene mocks : les
vi.stubGlobal(...)ne sont jamais restaures. Risque reel faible (Vitest isole par fichier par defaut) mais ajouterafterEach(() => 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)', () => {[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 <= 2oulength >= 120) passerait inapercu, et le test 'passe quand valide' utilise 'Vis' (3 car) qui ne touche aucune borne.Ajouter :
name='AB'-> ok=true, etname='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"')[low] Fragilite :
toContain('"name":"Vis"')couple l'assertion au formatJSON.stringifydu stub i18n. Si le stub change de serialisation, le test casse sans regression reelle. Preferer un mock i18n qui capture(key, params)separement et asserterparams.name === 'Vis'.@@ -0,0 +280,4 @@await form.submitCreate()expect(form.errors.value.categoryType).toBe('Type invalide.')})[medium] Trou de couverture sur le mapping 422. Les 3 cas 422 testes mappent tous
nameoucategoryType, doncmapServerViolationsrenvoie toujourstrue. La branchestatus === 422 && mapServerViolations === false(propertyPath inconnu, ouviolations: []) n'est jamais exercee : c'est pourtant elle qui doit retomber surextractErrorMessage->_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) -> attendreerrors._globalrempli +mockToastErrorappele 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 () => {[low] extractErrorMessage partiellement couvert. On teste
hydra:description(ici) etdetail(submitDelete), mais pas la branchedescriptionseule 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-circuiteraitdescription— comportement non verifie. Un test avec_datasans 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"')})[medium] submitUpdate sous-couvert sur les erreurs. Seul le 409 update est teste. Manquent :
attemptedName(l.281-283 source) : quand seulcategoryTypechange,payload.nameest absent etattemptedNameretombe surname.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.
091ccb6f28to4cefd6f6eb