import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useDatePickerAccessibility } from '../useDatePickerAccessibility'
import { mount } from '@vue/test-utils'
// Créer un composant vide pour servir de contexte à l'exécution des hooks
import { defineComponent, ref, nextTick } from 'vue'
import { useMonthButtonCustomization } from '../../../components/DatePicker/composables/useMonthButtonCustomization'
// Composant vide qui servira de contexte pour les hooks Vue
const TestComponent = defineComponent({
setup() {
const { updateAccessibility, cleanupGridSemantics, handleKeyDown } = useDatePickerAccessibility()
return {
updateAccessibility,
cleanupGridSemantics,
handleKeyDown,
}
},
template: '
',
})
describe('useDatePickerAccessibility', () => {
// Variables pour stocker les méthodes du composable
let updateAccessibility: ReturnType['updateAccessibility']
let cleanupGridSemantics: ReturnType['cleanupGridSemantics']
let handleKeyDown: ReturnType['handleKeyDown']
// Wrapper pour le composant de test
let wrapper: ReturnType['updateAccessibility']
cleanupGridSemantics: ReturnType['cleanupGridSemantics']
handleKeyDown: ReturnType['handleKeyDown']
}>>
beforeEach(() => {
// Monter le composant de test pour fournir un contexte aux hooks Vue
wrapper = mount(TestComponent) as unknown as ReturnType['updateAccessibility']
cleanupGridSemantics: ReturnType['cleanupGridSemantics']
handleKeyDown: ReturnType['handleKeyDown']
}>>
// Obtenir les fonctions du composable directement depuis le composant monté
updateAccessibility = wrapper.vm.updateAccessibility!
cleanupGridSemantics = wrapper.vm.cleanupGridSemantics!
handleKeyDown = wrapper.vm.handleKeyDown!
// Créer une structure DOM simulée pour les tests
document.body.innerHTML = `
`
})
it('does not override month/year control labels set by useMonthButtonCustomization', async () => {
const { customizeMonthButton } = useMonthButtonCustomization(
() => true,
ref('janvier'),
ref('2023'),
)
await customizeMonthButton()
await updateAccessibility()
// Récupérer les boutons
const prevButton = document.querySelector('[data-testid="prev-month"]')
const nextButton = document.querySelector('[data-testid="next-month"]')
const monthButton = document.querySelector('.v-date-picker-controls__month-btn')
const yearButton = document.querySelector('.v-date-picker-controls__mode-btn')
// Vérifier que les flèches de navigation ont des labels statiques
expect(prevButton?.getAttribute('aria-label')).toBe('Mois précédent')
expect(nextButton?.getAttribute('aria-label')).toBe('Mois suivant')
// Vérifier que les labels riches des boutons mois/année ne sont pas écrasés
expect(monthButton?.getAttribute('aria-label')).toContain('Sélectionner un mois')
expect(monthButton?.getAttribute('aria-label')).toContain('janvier')
expect(yearButton?.getAttribute('aria-label')).toContain('2023')
})
it('adds a dedicated hidden role="status" element', async () => {
await updateAccessibility()
const status = document.querySelector('[role="status"]')
expect(status?.getAttribute('role')).toBe('status')
expect(status?.getAttribute('aria-live')).toBe('polite')
expect(status?.getAttribute('aria-atomic')).toBe('true')
expect(status?.closest('.v-date-picker-controls')).toBeNull()
})
it('assigns a unique status region id per date picker', async () => {
document.body.innerHTML = `
`
await new Promise(resolve => setTimeout(resolve, 0))
await new Promise(resolve => setTimeout(resolve, 0))
expect(daysContainer.querySelectorAll('.v-date-picker-month__weekdays')).toHaveLength(1)
expect(daysContainer.querySelectorAll('.v-date-picker-month__week')).toHaveLength(1)
expect(daysContainer.querySelector('[data-v-date="2026-08-01"]')?.getAttribute('role')).toBe('gridcell')
expect(daysContainer.querySelector('[data-v-date="2026-08-02"]')?.getAttribute('role')).toBe('gridcell')
})
it('preserves customized month button label with different navigation icons', async () => {
// Modifier les icônes
document.body.innerHTML = `
`
const { customizeMonthButton } = useMonthButtonCustomization(
() => true,
ref('janvier'),
ref('2023'),
)
await customizeMonthButton()
await updateAccessibility()
// Récupérer les boutons
const prevButton = document.querySelector('[data-testid="prev-month"]')
const nextButton = document.querySelector('[data-testid="next-month"]')
const monthButton = document.querySelector('.v-date-picker-controls__month-btn')
// Vérifier que les flèches de navigation sont toujours étiquetées
expect(prevButton?.getAttribute('aria-label')).toBe('Mois précédent')
expect(nextButton?.getAttribute('aria-label')).toBe('Mois suivant')
// Vérifier que le label personnalisé du mois n'est pas écrasé
expect(monthButton?.getAttribute('aria-label')).toContain('janvier')
})
it('ne crée pas de bloc sr-only instructions (comportement actuel)', async () => {
await updateAccessibility()
const srOnlyEl = document.querySelector('.sr-only-instructions')
expect(srOnlyEl).toBeNull()
})
describe('handleKeyDown', () => {
it('does not intercept Enter on native interactive elements', () => {
// Créer une fonction d'espionnage autonome plutôt que d'espionner une méthode existante
const clickHandlerSpy = vi.fn()
// Créer un élément bouton avec un gestionnaire de clic espion
const button = document.createElement('button')
button.addEventListener('click', clickHandlerSpy)
document.body.appendChild(button)
button.focus()
// Créer un événement clavier pour la touche Entrée
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
bubbles: true,
cancelable: true,
})
// Espionner la méthode preventDefault de l'événement
const preventDefaultSpy = vi.spyOn(enterEvent, 'preventDefault')
// Appeler la fonction handleKeyDown avec l'événement
handleKeyDown(enterEvent)
// Vérifier que preventDefault n'a pas été appelé sur un vrai bouton
expect(preventDefaultSpy).not.toHaveBeenCalled()
// Vérifier que le gestionnaire de clic n'a pas été déclenché par le composable
expect(clickHandlerSpy).not.toHaveBeenCalled()
})
it('simulates a click event when Enter key is pressed on a gridcell', () => {
const clickHandlerSpy = vi.fn()
const cell = document.createElement('div')
cell.setAttribute('role', 'gridcell')
const button = document.createElement('button')
button.addEventListener('click', clickHandlerSpy)
cell.appendChild(button)
document.body.appendChild(cell)
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
bubbles: true,
cancelable: true,
})
Object.defineProperty(enterEvent, 'target', { value: cell, enumerable: true })
const preventDefaultSpy = vi.spyOn(enterEvent, 'preventDefault')
handleKeyDown(enterEvent)
expect(preventDefaultSpy).toHaveBeenCalled()
expect(clickHandlerSpy).toHaveBeenCalled()
})
it('does nothing when a key other than Enter is pressed', () => {
// Créer une fonction d'espionnage autonome
const clickHandlerSpy = vi.fn()
// Créer un élément bouton avec un gestionnaire de clic espion
const button = document.createElement('button')
button.addEventListener('click', clickHandlerSpy)
document.body.appendChild(button)
button.focus()
// Créer un événement clavier pour une touche autre qu'Entrée (par ex: Espace)
const spaceEvent = new KeyboardEvent('keydown', {
key: 'Space',
bubbles: true,
cancelable: true,
})
// Espionner la méthode preventDefault de l'événement
const preventDefaultSpy = vi.spyOn(spaceEvent, 'preventDefault')
// Appeler la fonction handleKeyDown avec l'événement
handleKeyDown(spaceEvent)
// Vérifier que preventDefault n'a pas été appelé
expect(preventDefaultSpy).not.toHaveBeenCalled()
// Vérifier qu'aucun événement de clic n'a été déclenché
expect(clickHandlerSpy).not.toHaveBeenCalled()
})
it('handles the case when no element is focused', () => {
// S'assurer qu'aucun élément n'est focalisé
document.body.focus()
// Créer un événement clavier pour la touche Entrée
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
bubbles: true,
cancelable: true,
})
// Vérifier que la fonction ne génère pas d'erreur
expect(() => handleKeyDown(enterEvent)).not.toThrow()
})
})
describe('RGAA 12.6 - aria-label reprend le texte visible', () => {
it('vérifie que l\'aria-label du bouton du mois contient le texte visible', async () => {
document.body.innerHTML = `
`
const { customizeMonthButton } = useMonthButtonCustomization(
() => true,
ref('janvier'),
ref('2025'),
)
await customizeMonthButton()
await nextTick()
const monthBtn = document.querySelector('.v-date-picker-controls__month-btn')!
const ariaLabel = monthBtn.getAttribute('aria-label')
const visibleText = monthBtn.textContent
// Vérifier que l'aria-label contient le texte visible (janv.)
expect(ariaLabel).toContain('janv.')
expect(visibleText).toContain('janv.')
})
it('vérifie que l\'aria-label du bouton de l\'année contient le texte visible', async () => {
document.body.innerHTML = `
`
const { customizeMonthButton } = useMonthButtonCustomization(
() => true,
ref('janvier'),
ref('2025'),
)
await customizeMonthButton()
await nextTick()
const yearBtn = document.querySelector('.v-date-picker-controls__mode-btn')!
const ariaLabel = yearBtn.getAttribute('aria-label')
const visibleText = yearBtn.textContent
// Vérifier que l'aria-label contient le texte visible (2025)
expect(ariaLabel).toContain('2025')
expect(visibleText).toContain('2025')
})
it('vérifie que l\'aria-label du bouton du mois contient le mois sélectionné', async () => {
document.body.innerHTML = `
`
const { customizeMonthButton } = useMonthButtonCustomization(
() => true,
ref('décembre'),
ref('2030'),
)
await customizeMonthButton()
await nextTick()
const monthBtn = document.querySelector('.v-date-picker-controls__month-btn')!
const ariaLabel = monthBtn.getAttribute('aria-label')
expect(ariaLabel).toContain('électionner un mois (décembre / déc. sélectionné)')
})
it('vérifie que l\'aria-label du bouton de l\'année contient l\'année sélectionnée', async () => {
document.body.innerHTML = `