502d1a216b
- 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
133 lines
4.8 KiB
TypeScript
133 lines
4.8 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import { defineComponent, h, ref, computed } from 'vue'
|
|
import { emptyAddress } from '~/modules/commercial/types/clientForm'
|
|
import ClientAddressBlock from '../ClientAddressBlock.vue'
|
|
|
|
// Le composable BAN est mocke : aucun appel reseau, aucune suggestion chargee.
|
|
// On reproduit ainsi l'etat « adresse persistee, mais liste de suggestions
|
|
// vide » (remontage apres validation / edition d'une adresse existante).
|
|
vi.mock('~/shared/composables/useAddressAutocomplete', () => ({
|
|
useAddressAutocomplete: () => ({
|
|
searchCity: vi.fn(),
|
|
searchAddress: vi.fn(),
|
|
}),
|
|
}))
|
|
|
|
// Auto-imports Nuxt/Vue utilises sans import explicite par le composant.
|
|
vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))
|
|
vi.stubGlobal('ref', ref)
|
|
vi.stubGlobal('computed', computed)
|
|
|
|
// Stub de MalioInputAutocomplete : expose les `value` des options recues, pour
|
|
// verifier que la rue courante figure bien dans la liste (sinon le composant
|
|
// Malio ne peut pas resoudre/afficher la valeur liee -> champ vide).
|
|
const MalioInputAutocompleteStub = defineComponent({
|
|
name: 'MalioInputAutocomplete',
|
|
props: {
|
|
modelValue: { type: [String, Number, null], default: undefined },
|
|
options: { type: Array as () => { value: string | number, label: string }[], default: () => [] },
|
|
loading: { type: Boolean, default: false },
|
|
minSearchLength: { type: Number, default: 0 },
|
|
label: { type: String, default: '' },
|
|
readonly: { type: Boolean, default: false },
|
|
},
|
|
emits: ['update:modelValue', 'search', 'select'],
|
|
setup(props) {
|
|
return () => h('div', {
|
|
'data-testid': 'addr-autocomplete',
|
|
'data-options': JSON.stringify(props.options.map(o => o.value)),
|
|
})
|
|
},
|
|
})
|
|
|
|
function mountBlock(street: string | null) {
|
|
return mount(ClientAddressBlock, {
|
|
props: {
|
|
modelValue: { ...emptyAddress(), street },
|
|
title: 'Adresse',
|
|
categoryOptions: [],
|
|
siteOptions: [],
|
|
contactOptions: [],
|
|
countryOptions: [],
|
|
},
|
|
global: {
|
|
stubs: {
|
|
MalioButtonIcon: true,
|
|
MalioCheckbox: true,
|
|
MalioSelect: true,
|
|
MalioSelectCheckbox: true,
|
|
MalioInputText: true,
|
|
MalioInputAutocomplete: MalioInputAutocompleteStub,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
describe('ClientAddressBlock — affichage de l\'adresse persistee', () => {
|
|
it('inclut la rue courante dans les options de l\'autocomplete meme sans recherche BAN', () => {
|
|
const wrapper = mountBlock('8 Boulevard du Port')
|
|
|
|
const el = wrapper.find('[data-testid="addr-autocomplete"]')
|
|
const values = JSON.parse(el.attributes('data-options') ?? '[]')
|
|
|
|
expect(values).toContain('8 Boulevard du Port')
|
|
})
|
|
})
|
|
|
|
/**
|
|
* Stub MalioInputText qui re-expose `label` + `error` recus : permet de cibler
|
|
* un champ par son libelle et de verifier l'erreur 422 propagee (ERP-101).
|
|
*/
|
|
const MalioInputTextProbe = defineComponent({
|
|
name: 'MalioInputTextProbe',
|
|
props: {
|
|
modelValue: { type: [String, Number, null], default: undefined },
|
|
error: { type: String, default: '' },
|
|
label: { type: String, default: '' },
|
|
readonly: { type: Boolean, default: false },
|
|
},
|
|
setup(props) {
|
|
return () => h('div', {
|
|
'data-testid': 'addr-text',
|
|
'data-label': props.label,
|
|
'data-error': props.error,
|
|
})
|
|
},
|
|
})
|
|
|
|
describe('ClientAddressBlock — mapping erreur par champ (ERP-101)', () => {
|
|
function mountWithErrors(errors: Record<string, string>) {
|
|
return mount(ClientAddressBlock, {
|
|
props: {
|
|
modelValue: emptyAddress(),
|
|
title: 'Adresse',
|
|
categoryOptions: [],
|
|
siteOptions: [],
|
|
contactOptions: [],
|
|
countryOptions: [],
|
|
errors,
|
|
},
|
|
global: {
|
|
stubs: {
|
|
MalioButtonIcon: true,
|
|
MalioCheckbox: true,
|
|
MalioSelect: true,
|
|
MalioSelectCheckbox: true,
|
|
MalioInputAutocomplete: MalioInputAutocompleteStub,
|
|
MalioInputText: MalioInputTextProbe,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
it('affiche l\'erreur serveur sur le champ code postal via la prop errors', () => {
|
|
const wrapper = mountWithErrors({ postalCode: 'Code postal invalide.' })
|
|
|
|
const field = wrapper.findAll('[data-testid="addr-text"]').find(
|
|
el => el.attributes('data-label') === 'commercial.clients.form.address.postalCode',
|
|
)
|
|
expect(field?.attributes('data-error')).toBe('Code postal invalide.')
|
|
})
|
|
})
|