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

Le masque maska n'imposait que la forme (##/##/####), donc 99/99/9999 était
saisissable puis rejeté a posteriori par la validation. Le métier veut que ce
soit impossible à taper.

buildBoundedMask(template) borne le premier chiffre de chaque champ (jour 0-3,
mois 0-1, heure 0-2, minute 0-5) ; il distingue le mois des minutes (même lettre
M) selon la présence d'heures. Les impossibilités fines (31/02, 29/02 non
bissextile, hors min/max) restent captées par la validation, en filet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 16:18:54 +02:00
parent 06c739cdc7
commit e8ee70e7fc
6 changed files with 126 additions and 7 deletions
@@ -0,0 +1,45 @@
import {describe, it, expect} from 'vitest'
import {buildBoundedMask} from './maskTemplate'
describe('buildBoundedMask', () => {
it('borne la dizaine du jour à 0-3 (bloque la saisie de 9 → 99 impossible)', () => {
const {mask, tokens} = buildBoundedMask('JJ/MM/AAAA')
const dayTens = tokens[mask[0]]
expect(dayTens.pattern.test('9')).toBe(false)
expect(dayTens.pattern.test('3')).toBe(true)
expect(dayTens.pattern.test('0')).toBe(true)
})
it('borne la dizaine du mois à 0-1 (bloque 9x)', () => {
const {mask, tokens} = buildBoundedMask('JJ/MM/AAAA')
const monthTens = tokens[mask[3]]
expect(monthTens.pattern.test('9')).toBe(false)
expect(monthTens.pattern.test('1')).toBe(true)
expect(monthTens.pattern.test('0')).toBe(true)
})
it('laisse lannée libre et conserve les séparateurs', () => {
const {mask} = buildBoundedMask('JJ/MM/AAAA')
expect(mask).toBe('d#/m#/####')
})
it('borne lheure à 0-2 et la minute à 0-5 (datetime), sans confondre minute et mois', () => {
const {mask, tokens} = buildBoundedMask('JJ/MM/AAAA HH:MM')
expect(mask).toBe('d#/m#/#### h#:n#')
const hourTens = tokens[mask[11]]
expect(hourTens.pattern.test('2')).toBe(true)
expect(hourTens.pattern.test('9')).toBe(false)
const minuteTens = tokens[mask[14]]
// La minute doit monter jusqu’à 5 — surtout PAS bornée comme un mois (0-1).
expect(minuteTens.pattern.test('5')).toBe(true)
expect(minuteTens.pattern.test('2')).toBe(true)
expect(minuteTens.pattern.test('9')).toBe(false)
})
it('ne borne que le premier chiffre de chaque champ (les unités restent 0-9)', () => {
const {mask} = buildBoundedMask('JJ/MM/AAAA')
// unités jour (index 1) et mois (index 4) = chiffre libre '#'
expect(mask[1]).toBe('#')
expect(mask[4]).toBe('#')
})
})