refactor(front) : suites review ERP-101 — i18n libelles toast + factorisation useClientFormErrors
- libelles de toast generiques passes en i18n (errors.title/generic/unknown, success.title) dans useFormErrors, useApi et useCategoryForm - nouveau composable useClientFormErrors : factorise l'etat d'erreurs (3 useFormErrors scalaires + 3 tableaux par ligne + mapRowError) partage entre clients/new.vue et [id]/edit.vue - mapRowError retourne un booleen et ne toaste plus : chaque page garde son fallback (toast generique en creation, showError en edition)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { useFormErrors } from '~/shared/composables/useFormErrors'
|
||||
import { useClientFormErrors } from '../useClientFormErrors'
|
||||
|
||||
// useFormErrors (auto-import) expose l'implementation reelle ; elle consomme
|
||||
// useToast + useI18n, stubbes ici.
|
||||
vi.stubGlobal('useToast', () => ({ error: vi.fn(), success: vi.fn() }))
|
||||
vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))
|
||||
vi.stubGlobal('useFormErrors', useFormErrors)
|
||||
|
||||
/**
|
||||
* Tests du composable partage `useClientFormErrors` — factorisation du cablage
|
||||
* d'erreurs des ecrans client (creation/edition), suggestion de revue ERP-101.
|
||||
* `mapRowError` ne toaste plus : il retourne un booleen et chaque page garde son
|
||||
* propre fallback (toast.error en creation, showError en edition).
|
||||
*/
|
||||
describe('useClientFormErrors', () => {
|
||||
it('expose les 3 etats scalaires (vides) et les 3 tableaux d\'erreurs par ligne', () => {
|
||||
const f = useClientFormErrors()
|
||||
expect(f.mainErrors.errors).toEqual({})
|
||||
expect(f.informationErrors.errors).toEqual({})
|
||||
expect(f.accountingErrors.errors).toEqual({})
|
||||
expect(f.contactErrors.value).toEqual([])
|
||||
expect(f.addressErrors.value).toEqual([])
|
||||
expect(f.ribErrors.value).toEqual([])
|
||||
})
|
||||
|
||||
it('mapRowError mappe une 422 sur target[index] et retourne true', () => {
|
||||
const f = useClientFormErrors()
|
||||
const error = {
|
||||
response: {
|
||||
status: 422,
|
||||
_data: { violations: [{ propertyPath: 'email', message: 'Adresse invalide.' }] },
|
||||
},
|
||||
}
|
||||
const mapped = f.mapRowError(error, f.contactErrors, 0)
|
||||
expect(mapped).toBe(true)
|
||||
expect(f.contactErrors.value[0]).toEqual({ email: 'Adresse invalide.' })
|
||||
})
|
||||
|
||||
it('mapRowError retourne false et ne touche pas la cible pour une erreur non-422', () => {
|
||||
const f = useClientFormErrors()
|
||||
const error = { response: { status: 500, _data: {} } }
|
||||
expect(f.mapRowError(error, f.ribErrors, 0)).toBe(false)
|
||||
expect(f.ribErrors.value[0]).toBeUndefined()
|
||||
})
|
||||
|
||||
it('mapRowError retourne false pour une 422 sans violation exploitable', () => {
|
||||
const f = useClientFormErrors()
|
||||
const error = { response: { status: 422, _data: { 'hydra:description': 'Donnees invalides.' } } }
|
||||
expect(f.mapRowError(error, f.addressErrors, 0)).toBe(false)
|
||||
expect(f.addressErrors.value[0]).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Composable d'erreurs partage des ecrans client (creation + edition, M1
|
||||
* Commercial). Factorise le cablage identique entre `clients/new.vue` et
|
||||
* `clients/[id]/edit.vue` (suggestion de revue ERP-101) :
|
||||
* - un `useFormErrors` par groupe scalaire (Principal / Information /
|
||||
* Comptabilite) : violations 422 affichees inline sous chaque champ ;
|
||||
* - un tableau d'erreurs PAR LIGNE pour chaque collection (contacts /
|
||||
* adresses / RIB), aligne sur l'index du `v-for`.
|
||||
*
|
||||
* `mapRowError` ne toaste PAS lui-meme : il retourne un booleen (true = mappe
|
||||
* inline). Chaque page conserve ainsi son propre fallback dans le `catch`
|
||||
* (toast generique en creation, `showError` en edition) sans imposer un
|
||||
* comportement commun.
|
||||
*/
|
||||
import { ref, type Ref } from 'vue'
|
||||
import { mapViolationsToRecord } from '~/shared/utils/api'
|
||||
|
||||
export function useClientFormErrors() {
|
||||
const mainErrors = useFormErrors()
|
||||
const informationErrors = useFormErrors()
|
||||
const accountingErrors = useFormErrors()
|
||||
const contactErrors = ref<Record<string, string>[]>([])
|
||||
const addressErrors = ref<Record<string, string>[]>([])
|
||||
const ribErrors = ref<Record<string, string>[]>([])
|
||||
|
||||
/**
|
||||
* Mappe l'erreur d'une ligne de collection sur le tableau cible (par index).
|
||||
* 422 avec violations exploitables → erreurs inline sous les champs de la
|
||||
* ligne + retourne true. Sinon → ne touche pas la cible et retourne false
|
||||
* (le caller decide du fallback toast).
|
||||
*/
|
||||
function mapRowError(
|
||||
error: unknown,
|
||||
target: Ref<Record<string, string>[]>,
|
||||
index: number,
|
||||
): boolean {
|
||||
const response = (error as { response?: { status?: number, _data?: unknown } })?.response
|
||||
const mapped = response?.status === 422 ? mapViolationsToRecord(response._data) : {}
|
||||
if (Object.keys(mapped).length > 0) {
|
||||
target.value[index] = mapped
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
mainErrors,
|
||||
informationErrors,
|
||||
accountingErrors,
|
||||
contactErrors,
|
||||
addressErrors,
|
||||
ribErrors,
|
||||
mapRowError,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user