feat(front) : mapping des erreurs de validation 422 par champ (ERP-101)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m40s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m10s

- composable useFormErrors + util mapViolationsToRecord (shared)
- formulaire Client (new + edit) : erreurs inline par champ (scalaires)
  et par ligne pour les collections (contacts / adresses / RIB)
- blocs ClientContactBlock / ClientAddressBlock : prop errors
- migration de useCategoryForm sur useFormErrors
- convention documentee dans .claude/rules/frontend.md
This commit is contained in:
2026-06-04 08:24:39 +02:00
parent e85d46a17b
commit 502d1a216b
15 changed files with 910 additions and 212 deletions
@@ -0,0 +1,58 @@
import { describe, it, expect } from 'vitest'
import { mapViolationsToRecord } from '../api'
/**
* Tests de `mapViolationsToRecord` — fondation du mapping erreur→champ des
* formulaires (ERP-101). Transforme un payload 422 API Platform en
* `Record<propertyPath, message>` directement consommable par la prop `:error`
* des composants `Malio*`.
*/
describe('mapViolationsToRecord', () => {
it('mappe chaque violation par son propertyPath (format `violations`)', () => {
const data = {
violations: [
{ propertyPath: 'companyName', message: 'Obligatoire.' },
{ propertyPath: 'siren', message: 'SIREN deja utilise.' },
],
}
expect(mapViolationsToRecord(data)).toEqual({
companyName: 'Obligatoire.',
siren: 'SIREN deja utilise.',
})
})
it('supporte le format negocie `hydra:violations`', () => {
const data = {
'hydra:violations': [
{ propertyPath: 'email', message: 'Adresse invalide.' },
],
}
expect(mapViolationsToRecord(data)).toEqual({ email: 'Adresse invalide.' })
})
it('renvoie un objet vide quand il n\'y a pas de violation exploitable', () => {
expect(mapViolationsToRecord({})).toEqual({})
expect(mapViolationsToRecord(null)).toEqual({})
expect(mapViolationsToRecord({ violations: [] })).toEqual({})
})
it('ignore les violations sans propertyPath', () => {
const data = {
violations: [
{ propertyPath: '', message: 'Erreur globale.' },
{ propertyPath: 'iban', message: 'IBAN invalide.' },
],
}
expect(mapViolationsToRecord(data)).toEqual({ iban: 'IBAN invalide.' })
})
it('en cas de doublon de propertyPath, la derniere violation gagne', () => {
const data = {
violations: [
{ propertyPath: 'name', message: 'Premier message.' },
{ propertyPath: 'name', message: 'Second message.' },
],
}
expect(mapViolationsToRecord(data)).toEqual({ name: 'Second message.' })
})
})
+19
View File
@@ -66,6 +66,25 @@ export function extractApiViolations(data: unknown): ApiViolation[] {
return out
}
/**
* Transforme un payload d'erreur 422 d'API Platform en dictionnaire
* `{ propertyPath: message }`, directement consommable par la prop `:error`
* des composants `Malio*` (le nom du champ cote front = le `propertyPath`
* renvoye par le back). Fondation du mapping erreur→champ des formulaires :
* utilise par `useFormErrors` (champs scalaires) et par les boucles de submit
* de collections (erreur par ligne).
*
* Les violations sans `propertyPath` (erreur globale) sont ignorees ; en cas
* de doublon de `propertyPath`, la derniere violation l'emporte.
*/
export function mapViolationsToRecord(data: unknown): Record<string, string> {
const out: Record<string, string> = {}
for (const v of extractApiViolations(data)) {
if (v.propertyPath) out[v.propertyPath] = v.message
}
return out
}
/**
* Extrait un message d'erreur lisible depuis un payload Hydra / JSON
* d'erreur API Platform. Essaie les champs courants dans l'ordre :