test(accordion): nav clavier ArrowUp + skip des sections désactivées [#MUI-37]

Ajoute aussi un commentaire sur l'hypothèse d'ordre de montage de focusSibling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 17:04:49 +02:00
parent 1cbd54a91d
commit b817df82ee
2 changed files with 34 additions and 0 deletions
@@ -191,4 +191,34 @@ describe('MalioAccordion — defaultOpen, disabled & clavier', () => {
wrapper.unmount() wrapper.unmount()
root.remove() root.remove()
}) })
it('moves focus to the previous header on ArrowUp', async () => {
const root = document.createElement('div')
document.body.appendChild(root)
const wrapper = mountAccordion({}, TWO_ITEMS, root)
const headers = wrapper.findAll('button[aria-expanded]')
;(headers[1].element as HTMLElement).focus()
await headers[1].trigger('keydown', {key: 'ArrowUp'})
expect(document.activeElement).toBe(headers[0].element)
wrapper.unmount()
root.remove()
})
it('skips disabled headers during keyboard navigation', async () => {
const root = document.createElement('div')
document.body.appendChild(root)
const slot = `
<MalioAccordionItem title="A" value="a"><p>A</p></MalioAccordionItem>
<MalioAccordionItem title="B" value="b" :disabled="true"><p>B</p></MalioAccordionItem>
<MalioAccordionItem title="C" value="c"><p>C</p></MalioAccordionItem>
`
const wrapper = mountAccordion({}, slot, root)
const headers = wrapper.findAll('button[aria-expanded]')
;(headers[0].element as HTMLElement).focus()
await headers[0].trigger('keydown', {key: 'ArrowDown'})
// saute le header désactivé (B) pour aller directement à C
expect(document.activeElement).toBe(headers[2].element)
wrapper.unmount()
root.remove()
})
}) })
@@ -81,6 +81,10 @@ function unregister(value: string) {
items.value = items.value.filter(i => i.value !== value) items.value = items.value.filter(i => i.value !== value)
} }
// `items` est ordonné par ordre de montage (= ordre du DOM pour des sections
// statiques/ajoutées en fin). Si un consommateur réordonne dynamiquement les
// items, cet ordre peut diverger de l'ordre visuel ; trier par position DOM
// serait alors nécessaire (hors périmètre v1).
function focusSibling(value: string, offset: 1 | -1) { function focusSibling(value: string, offset: 1 | -1) {
const enabled = items.value.filter(i => !i.isDisabled()) const enabled = items.value.filter(i => !i.isDisabled())
const idx = enabled.findIndex(i => i.value === value) const idx = enabled.findIndex(i => i.value === value)