fix: Date + DateTime new emit update rawValue (#75)
Release / release (push) Successful in 1m8s

| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-authored-by: matthieu <matthieu@yuno.malio.fr>
Reviewed-on: #75
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #75.
This commit is contained in:
2026-06-12 07:40:13 +00:00
committed by Autin
parent 90ed4a213f
commit 29bd6abcfe
6 changed files with 137 additions and 3 deletions
+54
View File
@@ -471,4 +471,58 @@ describe('MalioDate', () => {
expect(wrapper.emitted('update:valid')?.at(-1)).toEqual([true])
})
})
describe('saisie brute (update:rawValue)', () => {
it('émet le texte brut trimmé sur saisie malformée, sans émettre modelValue', async () => {
const wrapper = mountDate({editable: true})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('32/13/2026')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual(['32/13/2026'])
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
})
it('émet le texte brut trimmé sur saisie hors min/max', async () => {
const wrapper = mountDate({editable: true, min: '2026-05-10', max: '2026-05-20'})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('25/12/2026')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual(['25/12/2026'])
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
})
it('émet rawValue vide et l\'ISO sur saisie clavier valide', async () => {
const wrapper = mountDate({editable: true})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('19/05/2026')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['2026-05-19'])
})
it('émet rawValue vide sur saisie vidée au blur', async () => {
const wrapper = mountDate({editable: true, modelValue: '2026-05-19'})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
})
it('émet rawValue vide sur clear', async () => {
const wrapper = mountDate({modelValue: '2026-05-19'})
await wrapper.get('[data-test="clear"]').trigger('click')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
})
it('émet rawValue vide quand on sélectionne une date au calendrier', async () => {
const wrapper = mountDate({editable: true})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('32/13/2026')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual(['32/13/2026'])
await input.trigger('focus')
await wrapper.get('[data-test="day"][data-iso="2026-05-19"]').trigger('click')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
})
})
})
+9
View File
@@ -90,6 +90,10 @@ const props = withDefaults(
const emit = defineEmits<{
(e: 'update:modelValue', value: string | null): void
(e: 'update:valid', value: boolean): void
// Canal séparé pour la saisie invalide (validation back-autoritative) : texte brut
// tel que tapé sur saisie non parsable/hors plage, '' sinon. Ne JAMAIS transiter
// par modelValue, qui doit rester ISO|null pour l'affichage et le round-trip.
(e: 'update:rawValue', value: string): void
}>()
const displayValue = computed(() => formatIsoToDisplay(props.modelValue ?? null))
@@ -108,25 +112,30 @@ const onCommit = (text: string) => {
const trimmed = text.trim()
if (trimmed === '') {
setError('')
emit('update:rawValue', '')
emit('update:modelValue', null)
return
}
const iso = parseDisplayToIso(trimmed)
if (iso && isDateInRange(iso, props.min, props.max)) {
setError('')
emit('update:rawValue', '')
emit('update:modelValue', iso)
return
}
setError(props.invalidMessage)
emit('update:rawValue', trimmed)
}
const onClear = () => {
setError('')
emit('update:rawValue', '')
emit('update:modelValue', null)
}
const onSelect = (iso: string, close: () => void) => {
setError('')
emit('update:rawValue', '')
emit('update:modelValue', iso)
close()
}
@@ -283,4 +283,58 @@ describe('MalioDateTime', () => {
expect(wrapper.emitted('update:valid')?.at(-1)).toEqual([true])
})
})
describe('saisie brute (update:rawValue)', () => {
it('émet le texte brut trimmé sur saisie malformée, sans émettre modelValue', async () => {
const wrapper = mountDateTime({editable: true})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('32/13/2026 14:30')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual(['32/13/2026 14:30'])
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
})
it('émet le texte brut trimmé sur saisie hors min/max', async () => {
const wrapper = mountDateTime({editable: true, min: '2026-05-10T00:00:00', max: '2026-05-20T00:00:00'})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('25/12/2026 10:00')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual(['25/12/2026 10:00'])
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
})
it('émet rawValue vide et l\'ISO sur saisie clavier valide', async () => {
const wrapper = mountDateTime({editable: true})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('20/05/2026 14:30')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['2026-05-20T14:30:00'])
})
it('émet rawValue vide sur saisie vidée au blur', async () => {
const wrapper = mountDateTime({editable: true, modelValue: '2026-05-20T14:30:00'})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
})
it('émet rawValue vide sur clear', async () => {
const wrapper = mountDateTime({modelValue: '2026-05-20T14:30:00'})
await wrapper.get('[data-test="clear"]').trigger('click')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
})
it('émet rawValue vide quand on sélectionne une date au calendrier', async () => {
const wrapper = mountDateTime({editable: true})
const input = wrapper.get('[data-test="date-input"]')
await input.setValue('32/13/2026 14:30')
await input.trigger('blur')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual(['32/13/2026 14:30'])
await input.trigger('focus')
await wrapper.get('[data-test="day"][data-iso="2026-05-19"]').trigger('click')
expect(wrapper.emitted('update:rawValue')?.at(-1)).toEqual([''])
})
})
})
+10
View File
@@ -103,6 +103,10 @@ const props = withDefaults(
const emit = defineEmits<{
(e: 'update:modelValue', value: string | null): void
(e: 'update:valid', value: boolean): void
// Canal séparé pour la saisie invalide (validation back-autoritative) : texte brut
// tel que tapé sur saisie non parsable/hors plage, '' sinon. Ne JAMAIS transiter
// par modelValue, qui doit rester ISO|null pour l'affichage et le round-trip.
(e: 'update:rawValue', value: string): void
}>()
// pendingTime : heure réglée avant qu'un jour ne soit choisi (sinon on ne peut pas émettre).
@@ -129,6 +133,7 @@ function onSelectDay(iso: string) {
const now = new Date()
const time = parts.value.time || pendingTime.value || formatTime(now.getHours(), now.getMinutes())
setError('')
emit('update:rawValue', '')
emit('update:modelValue', composeDateTime(iso, time))
}
@@ -136,6 +141,7 @@ function onTimeChange(value: string | null) {
if (!value) return
if (datePart.value) {
setError('')
emit('update:rawValue', '')
emit('update:modelValue', composeDateTime(datePart.value, value))
}
else {
@@ -147,21 +153,25 @@ function onCommit(text: string) {
const trimmed = text.trim()
if (trimmed === '') {
setError('')
emit('update:rawValue', '')
emit('update:modelValue', null)
return
}
const iso = parseDisplayToIsoDateTime(trimmed)
if (iso && isDateInRange(iso, props.min, props.max)) {
setError('')
emit('update:rawValue', '')
emit('update:modelValue', iso)
return
}
setError(props.invalidMessage)
emit('update:rawValue', trimmed)
}
function onClear() {
setError('')
pendingTime.value = ''
emit('update:rawValue', '')
emit('update:modelValue', null)
}