import { describe, it, expect, vi, beforeEach } from 'vitest' import { useFormErrors } from '../useFormErrors' const mockToastError = vi.hoisted(() => vi.fn()) vi.stubGlobal('useToast', () => ({ error: mockToastError, success: vi.fn() })) // useI18n stub : renvoie la cle telle quelle (pour asserter dessus). vi.stubGlobal('useI18n', () => ({ t: (key: string) => key })) /** * Tests du composable `useFormErrors` — pendant front de la regle « le back * renvoie toutes les violations 422 d'un coup » (ERP-101). Centralise l'etat * d'erreurs par champ (`Record`) et la dispatch d'une * erreur API : 422 mappee inline, sinon toast de fallback. */ describe('useFormErrors', () => { beforeEach(() => { mockToastError.mockReset() }) /** Fabrique une erreur ofetch avec status + payload. */ function fetchError(status: number, data: unknown) { return { response: { status, _data: data } } } it('demarre sans erreur', () => { const { errors, hasErrors } = useFormErrors() expect(errors).toEqual({}) expect(hasErrors.value).toBe(false) }) it('setServerErrors mappe les violations par champ et retourne true', () => { const { errors, hasErrors, setServerErrors } = useFormErrors() const mapped = setServerErrors({ violations: [ { propertyPath: 'companyName', message: 'Obligatoire.' }, { propertyPath: 'siren', message: 'Deja utilise.' }, ], }) expect(mapped).toBe(true) expect(errors).toEqual({ companyName: 'Obligatoire.', siren: 'Deja utilise.' }) expect(hasErrors.value).toBe(true) }) it('setServerErrors retourne false et ne touche rien sans violation', () => { const { errors, setServerErrors } = useFormErrors() expect(setServerErrors({})).toBe(false) expect(errors).toEqual({}) }) it('setError / clearError / clearErrors manipulent l\'etat finement', () => { const { errors, setError, clearError, clearErrors } = useFormErrors() setError('iban', 'IBAN invalide.') expect(errors.iban).toBe('IBAN invalide.') clearError('iban') expect(errors.iban).toBeUndefined() setError('a', 'x') setError('b', 'y') clearErrors() expect(errors).toEqual({}) }) it('handleApiError : 422 avec violations → mappe inline, pas de toast, retourne true', () => { const { errors, handleApiError } = useFormErrors() const handled = handleApiError( fetchError(422, { violations: [{ propertyPath: 'email', message: 'Invalide.' }] }), ) expect(handled).toBe(true) expect(errors.email).toBe('Invalide.') expect(mockToastError).not.toHaveBeenCalled() }) it('handleApiError : erreur non-422 → toast de fallback, retourne false', () => { const { errors, handleApiError } = useFormErrors() const handled = handleApiError( fetchError(500, { 'hydra:description': 'Erreur serveur.' }), { fallbackMessage: 'Oups.' }, ) expect(handled).toBe(false) expect(errors).toEqual({}) expect(mockToastError).toHaveBeenCalledTimes(1) // Titre via i18n (cle renvoyee telle quelle par le stub). expect(mockToastError.mock.calls[0][0]).toMatchObject({ title: 'errors.title', message: 'Erreur serveur.' }) }) it('handleApiError : 422 sans violation mappable → toast de fallback, retourne false', () => { const { handleApiError } = useFormErrors() const handled = handleApiError(fetchError(422, { 'hydra:description': 'Donnees invalides.' })) expect(handled).toBe(false) expect(mockToastError).toHaveBeenCalledTimes(1) }) })