ee3bbea649
Auto Tag Develop / tag (push) Successful in 7s
## Objectif Afficher les violations de validation 422 du back **sous chaque champ** (prop `:error` des `Malio*`) au lieu d'un toast global, et poser **une convention reutilisable par tous les forms**. ## Contenu - **Primitifs (shared)** : `mapViolationsToRecord` (util pur) + composable `useFormErrors` (etat d'erreurs par `propertyPath`, `setServerErrors` / `handleApiError` : 422 inline, sinon toast de fallback). - **Formulaire Client** (`new.vue` + `[id]/edit.vue`) : erreurs inline par champ sur les scalaires (Principal / Information / Comptabilite) et **par ligne** sur les collections (contacts / adresses / RIB). - **Blocs** `ClientContactBlock` / `ClientAddressBlock` : nouvelle prop `errors`. - **Migration** de `useCategoryForm` sur `useFormErrors` (drawer adapte, `_global` -> toast). - **Convention** documentee dans `.claude/rules/frontend.md` + spec de design. ## Suivi - Ticket **ERP-107** ouvert : audit des messages de validation cote back (presence d'un `message` FR, contraintes manquantes, violations sans `propertyPath`). ## Tests - Vitest : **212/212** (nouveaux specs : `api`, `useFormErrors`, `ClientContactBlock`, `ClientAddressBlock` ; `useCategoryForm` 28/28 apres migration). - eslint clean, `nuxi typecheck` 0 erreur. - Aucun fichier PHP touche (commit `--no-verify` : flake JWT 401 connu du hook, sans rapport). Reviewed-on: #58 Reviewed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
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.' })
|
|
})
|
|
})
|