fix(date) : borne la saisie clavier pour empêcher les dates absurdes (99/99/9999) (#79)

## Problème

Sur la famille Date editable, le masque maska n'imposait que la *forme* (`##/##/####`). Une valeur structurellement absurde comme `99/99/9999` était donc **saisissable**, puis rejetée *a posteriori* par la validation. Le métier veut que ce soit **impossible à taper**.

## Solution (masque borné + validation en filet)

- `composables/maskTemplate.ts` — `buildBoundedMask(template)` : borne le **premier chiffre de chaque champ** (jour `0-3`, mois `0-1`, heure `0-2`, minute `0-5`). Distingue le mois des minutes (même lettre `M`) selon la présence d'heures dans le gabarit, pour ne pas brider la saisie des minutes du DateTime.
- `internal/CalendarField.vue` — branche le builder dans `maskaOptions` (remplace le `replace(/[A-Za-z]/g, '#')`).

Les impossibilités plus fines (`31/02`, 29/02 non bissextile, hors `min`/`max`) restent captées par la **validation** (`invalidMessage` + `update:valid=false`).

## Tests

- `maskTemplate.test.ts` (5) — bornes par champ, structure du masque, non-confusion mois/minutes.
- `Date.test.ts` — test `invalidMessage` adapté (`32/13/2026`, typable→invalide) + garde de non-régression : `99/99/9999` ne s'inscrit jamais et n'émet aucune date.
- Suite complète : **1004/1004 verte** (DateTime 36 incluse → saisie d'heure intacte).

Doc : `COMPONENTS.md` (MalioDate) + `CHANGELOG.md` (Fixed) à jour.
Reviewed-on: #79
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #79.
This commit is contained in:
2026-06-19 13:04:11 +00:00
committed by Autin
parent 06c739cdc7
commit be3d88ed45
31 changed files with 939 additions and 125 deletions
+24 -1
View File
@@ -62,7 +62,7 @@ describe('MalioSidebar', () => {
it('renders expanded by default', () => {
const wrapper = mountComponent({sections})
const aside = wrapper.find('aside')
expect(aside.classes()).toContain('w-[280px]')
expect(aside.classes()).toContain('w-[232px]')
})
it('renders section labels with icons when expanded', () => {
@@ -89,6 +89,29 @@ describe('MalioSidebar', () => {
expect(links[2].attributes('href')).toBe('/fournisseurs')
})
it('hover : fond + couleur + semi-bold tous portés par le <li> (texte non figé sur le <a>)', () => {
const wrapper = mountComponent({sections})
const li = wrapper.find('li')
expect(li.classes()).toContain('hover:bg-m-primary/10')
expect(li.classes()).toContain('hover:text-m-primary')
expect(li.classes()).toContain('hover:font-semibold')
expect(li.classes()).toContain('text-black')
expect(li.classes()).toContain('pt-1')
expect(li.classes()).toContain('pb-1')
// Le <a> ne fige PAS sa couleur (sinon le texte resterait noir sur les bandes
// pt-1/pb-1 hors du <a> alors que le fond du <li> est bleu).
expect(wrapper.find('a').classes()).not.toContain('text-black')
expect(wrapper.find('a').classes()).not.toContain('hover:text-m-primary')
})
it('actif : texte primary + semi-bold, sans fond, via active-class', () => {
const wrapper = mountComponent({sections})
const activeClass = wrapper.find('a').attributes('active-class') ?? ''
expect(activeClass).toContain('text-m-primary')
expect(activeClass).toContain('font-semibold')
expect(activeClass).not.toContain('bg-')
})
it('renders section icons via IconifyIcon', () => {
const wrapper = mountComponent({sections})
const icons = wrapper.findAllComponents(IconifyIcon)