Nychthemeron/packages/library/tests/use-focus-trap.spec.ts
2026-07-15 19:28:29 -04:00

92 lines
2.8 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest'
import { nextTick, ref } from 'vue'
import { useFocusTrap } from '../src/lib/use-focus-trap'
function appendContainer() {
const container = document.createElement('div')
const first = document.createElement('button')
first.textContent = 'first'
const last = document.createElement('button')
last.textContent = 'last'
container.append(first, last)
document.body.append(container)
return { container, first, last }
}
describe('useFocusTrap', () => {
afterEach(() => {
document.body.replaceChildren()
})
it('moves focus into the container on activation', async () => {
const { container, first } = appendContainer()
const containerRef = ref<HTMLElement | null>(container)
const active = ref(false)
useFocusTrap(containerRef, active)
active.value = true
await nextTick()
await nextTick()
expect(document.activeElement).toBe(first)
})
it('uses the initialFocus override when provided', async () => {
const { container, last } = appendContainer()
const containerRef = ref<HTMLElement | null>(container)
const active = ref(false)
useFocusTrap(containerRef, active, { initialFocus: () => last })
active.value = true
await nextTick()
await nextTick()
expect(document.activeElement).toBe(last)
})
it('wraps Tab from the last focusable back to the first', async () => {
const { container, first, last } = appendContainer()
const containerRef = ref<HTMLElement | null>(container)
const active = ref(true)
useFocusTrap(containerRef, active)
last.focus()
const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, cancelable: true })
document.dispatchEvent(event)
expect(document.activeElement).toBe(first)
})
it('wraps Shift+Tab from the first focusable back to the last', async () => {
const { container, first, last } = appendContainer()
const containerRef = ref<HTMLElement | null>(container)
const active = ref(true)
useFocusTrap(containerRef, active)
first.focus()
const event = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true, cancelable: true })
document.dispatchEvent(event)
expect(document.activeElement).toBe(last)
})
it('restores focus to the previously-focused element on deactivation', async () => {
const opener = document.createElement('button')
opener.textContent = 'opener'
document.body.append(opener)
opener.focus()
const { container } = appendContainer()
const containerRef = ref<HTMLElement | null>(container)
const active = ref(false)
useFocusTrap(containerRef, active)
active.value = true
await nextTick()
await nextTick()
active.value = false
await nextTick()
expect(document.activeElement).toBe(opener)
})
})