41010060ff
## Sélecteur d'année dans le calendrier (3ᵉ niveau de navigation) Ajoute un 3ᵉ niveau de navigation à la famille de composants date, et corrige le bornage min/max du sélecteur de mois. ### Comportement - Clic sur le champ → calendrier (vue **jours**) - Clic sur l'en-tête → **sélecteur de mois** - **Re-clic sur l'en-tête → sélecteur d'année** (grille de 12 ans, chevrons paginant par pas de 12 ans, fenêtre centrée sur l'année courante − 5) - Clic sur une année → retour au sélecteur de mois ; clic sur un mois → retour à la grille de jours - Les props `min`/`max` **grisent les mois ET les années** hors plage (corrige l'asymétrie : le `MonthPicker` affichait jusqu'ici tous les mois) En-tête contextuel : « Mai 2026 » (jours) / « 2026 » (mois) / « 2020 – 2031 » (années). ### Périmètre - Shell partagé `internal/CalendarField.vue` → bénéficie aux 4 composants publics `Date`, `DateRange`, `DateTime`, `DateWeek` - **Aucune API publique modifiée** - Nouveau composant `internal/YearPicker.vue` (calqué sur `MonthPicker`) - Helpers purs `isMonthInRange` / `isYearInRange` (comparaison par préfixe ISO, bornes inclusives) - State machine `viewMode` à 3 niveaux (`useCalendarPopover` / `useCalendarView`) ### Tests - Suite date **246/246 verte**, ESLint propre - Unitaires : helpers, `YearPicker`, `MonthPicker` (grisage), composables (pagination ±12, recentrage, `selectYear`) - e2e `Date.test.ts` : flux complet jours→mois→années→mois→jours + grisage min/max ### Process Développé en brainstorming → spec → plan → exécution TDD (un commit par étape). Spec et plan inclus sous `docs/superpowers/`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #83 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
import {nextTick, ref} from 'vue'
|
|
import {useCalendarView} from './useCalendarView'
|
|
|
|
describe('useCalendarView', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
vi.setSystemTime(new Date(2026, 4, 19)) // 19 mai 2026
|
|
})
|
|
afterEach(() => vi.useRealTimers())
|
|
|
|
it('initialises to the current month and year', () => {
|
|
const {currentMonth, currentYear} = useCalendarView(ref('days'))
|
|
expect(currentMonth.value).toBe(4)
|
|
expect(currentYear.value).toBe(2026)
|
|
})
|
|
|
|
it('goToNext advances the month in days view', () => {
|
|
const {currentMonth, goToNext} = useCalendarView(ref('days'))
|
|
goToNext()
|
|
expect(currentMonth.value).toBe(5)
|
|
})
|
|
|
|
it('rolls December to January and bumps the year', () => {
|
|
const {currentMonth, currentYear, goToNext} = useCalendarView(ref('days'))
|
|
currentMonth.value = 11
|
|
goToNext()
|
|
expect(currentMonth.value).toBe(0)
|
|
expect(currentYear.value).toBe(2027)
|
|
})
|
|
|
|
it('rolls January to December backwards', () => {
|
|
const {currentMonth, currentYear, goToPrev} = useCalendarView(ref('days'))
|
|
currentMonth.value = 0
|
|
goToPrev()
|
|
expect(currentMonth.value).toBe(11)
|
|
expect(currentYear.value).toBe(2025)
|
|
})
|
|
|
|
it('navigates the year in months view', () => {
|
|
const {currentYear, goToNext, goToPrev} = useCalendarView(ref('months'))
|
|
goToNext()
|
|
expect(currentYear.value).toBe(2027)
|
|
goToPrev()
|
|
expect(currentYear.value).toBe(2026)
|
|
})
|
|
|
|
it('selectMonth sets the current month', () => {
|
|
const {currentMonth, selectMonth} = useCalendarView(ref('days'))
|
|
selectMonth(0)
|
|
expect(currentMonth.value).toBe(0)
|
|
})
|
|
|
|
it('syncToIso sets month/year from a valid ISO', () => {
|
|
const {currentMonth, currentYear, syncToIso} = useCalendarView(ref('days'))
|
|
syncToIso('2025-12-25')
|
|
expect(currentMonth.value).toBe(11)
|
|
expect(currentYear.value).toBe(2025)
|
|
})
|
|
|
|
it('syncToIso falls back to today for null/invalid', () => {
|
|
const {currentMonth, currentYear, syncToIso} = useCalendarView(ref('days'))
|
|
syncToIso('2025-12-25')
|
|
syncToIso(null)
|
|
expect(currentMonth.value).toBe(4)
|
|
expect(currentYear.value).toBe(2026)
|
|
})
|
|
|
|
it('paginates years by 12 in years view', () => {
|
|
const {yearPageStart, goToNext, goToPrev} = useCalendarView(ref('years'))
|
|
const start = yearPageStart.value
|
|
goToNext()
|
|
expect(yearPageStart.value).toBe(start + 12)
|
|
goToPrev()
|
|
expect(yearPageStart.value).toBe(start)
|
|
})
|
|
|
|
it('selectYear sets the current year', () => {
|
|
const {currentYear, selectYear} = useCalendarView(ref('days'))
|
|
selectYear(2030)
|
|
expect(currentYear.value).toBe(2030)
|
|
})
|
|
|
|
it('recenters the year page on entering years view (current - 4)', async () => {
|
|
const mode = ref<'days' | 'months' | 'years'>('days')
|
|
const {yearPageStart} = useCalendarView(mode)
|
|
mode.value = 'years'
|
|
await nextTick()
|
|
expect(yearPageStart.value).toBe(2022) // 2026 - 4 (année courante en 2e ligne / 2e col)
|
|
})
|
|
})
|