@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
244 lines (199 loc) • 9.89 kB
text/typescript
import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { fireEvent } from '@testing-library/dom'
expect.extend(toHaveNoViolations)
import './combobox'
import { PktCombobox } from './combobox'
import type { IPktComboboxOption } from './combobox'
const waitForCustomElements = async () => {
await customElements.whenDefined('pkt-combobox')
}
const createCombobox = async (comboboxProps = '') => {
const container = document.createElement('div')
container.innerHTML = `
<pkt-combobox ${comboboxProps}></pkt-combobox>
`
document.body.appendChild(container)
await waitForCustomElements()
return container
}
const defaultOptions: IPktComboboxOption[] = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'cherry', label: 'Cherry' },
]
afterEach(() => {
document.body.innerHTML = ''
})
describe('PktCombobox', () => {
describe('Accessibility (axe)', () => {
test('basic combobox has no accessibility violations', async () => {
const container = await createCombobox('id="accessible" name="test" label="Choose a fruit"')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const results = await axe(combobox)
expect(results).toHaveNoViolations()
})
test('combobox with options has no accessibility violations', async () => {
const container = await createCombobox('id="accessible" name="test" label="Choose a fruit"')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
combobox.options = [...defaultOptions]
await combobox.updateComplete
const results = await axe(combobox)
expect(results).toHaveNoViolations()
})
test('combobox with text input has no accessibility violations', async () => {
const container = await createCombobox(
'id="accessible" name="test" label="Choose a fruit" allow-user-input',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
combobox.options = [...defaultOptions]
await combobox.updateComplete
const results = await axe(combobox)
expect(results).toHaveNoViolations()
})
test('combobox with typeahead has no accessibility violations', async () => {
const container = await createCombobox(
'id="accessible" name="test" label="Choose a fruit" typeahead',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
combobox.options = [...defaultOptions]
await combobox.updateComplete
const results = await axe(combobox)
expect(results).toHaveNoViolations()
})
test('multiple combobox has no accessibility violations', async () => {
const container = await createCombobox(
'id="accessible" name="test" label="Choose fruits" multiple',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
combobox.options = [...defaultOptions]
await combobox.updateComplete
const results = await axe(combobox)
expect(results).toHaveNoViolations()
})
test('disabled combobox has no accessibility violations', async () => {
const container = await createCombobox(
'id="accessible" name="test" label="Choose a fruit" disabled',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const results = await axe(combobox)
expect(results).toHaveNoViolations()
})
test('combobox with error state has no accessibility violations', async () => {
const container = await createCombobox(
'id="accessible" name="test" label="Choose a fruit" error-message="Required field"',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
combobox.hasError = true
await combobox.updateComplete
const results = await axe(combobox)
expect(results).toHaveNoViolations()
})
})
describe('ARIA attributes', () => {
test('select-only combobox has correct ARIA attributes', async () => {
const container = await createCombobox('id="test-aria" name="test" label="Test Label"')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const comboboxInput = combobox.querySelector('.pkt-combobox__input')
expect(comboboxInput?.getAttribute('role')).toBe('combobox')
expect(comboboxInput?.getAttribute('aria-controls')).toBe('test-aria-listbox')
expect(comboboxInput?.getAttribute('aria-haspopup')).toBe('listbox')
expect(comboboxInput?.getAttribute('aria-expanded')).toBe('false')
expect(comboboxInput?.getAttribute('aria-labelledby')).toBe('test-aria-combobox-label')
})
test('arrow button aria-expanded updates when dropdown opens', async () => {
const container = await createCombobox('id="test" name="test" label="Test"')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const arrowButton = combobox.querySelector('.pkt-combobox__input')
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
fireEvent.click(arrowButton!)
await combobox.updateComplete
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
})
test('text input has correct ARIA attributes for allowUserInput', async () => {
const container = await createCombobox(
'id="test-input" name="test" label="Test Label" allow-user-input',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const textInput = combobox.querySelector('input[type="text"]')
expect(textInput?.getAttribute('role')).toBe('combobox')
expect(textInput?.getAttribute('aria-controls')).toBe('test-input-listbox')
expect(textInput?.getAttribute('aria-label')).toBe('Test Label')
expect(textInput?.getAttribute('aria-autocomplete')).toBe('list')
})
test('text input has correct ARIA attributes for typeahead', async () => {
const container = await createCombobox(
'id="test-input" name="test" label="Test Label" typeahead',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const textInput = combobox.querySelector('input[type="text"]')
expect(textInput?.getAttribute('aria-autocomplete')).toBe('both')
})
test('text input sets aria-activedescendant when value is selected', async () => {
const container = await createCombobox(
'id="test" name="test" label="Test" allow-user-input value="apple"',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
combobox.options = [...defaultOptions]
await combobox.updateComplete
const textInput = combobox.querySelector('input[type="text"]')
expect(textInput?.getAttribute('aria-activedescendant')).toBeTruthy()
})
test('listbox has correct id for aria-controls reference', async () => {
const container = await createCombobox('id="test-combo" name="test" label="Test"')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const listbox = combobox.querySelector('pkt-listbox')
expect(listbox?.getAttribute('id')).toBe('test-combo-listbox')
})
})
describe('Keyboard accessibility', () => {
test('arrow button is focusable when not disabled', async () => {
const container = await createCombobox('id="test" name="test" label="Test"')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const arrowButton = combobox.querySelector('.pkt-combobox__input') as HTMLElement
expect(arrowButton.getAttribute('tabindex')).toBe('0')
})
test('arrow button is not focusable when disabled', async () => {
const container = await createCombobox('id="test" name="test" label="Test" disabled')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const arrowButton = combobox.querySelector('.pkt-combobox__input') as HTMLElement
expect(arrowButton.getAttribute('tabindex')).toBe('-1')
})
test('text input is part of tab order', async () => {
const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const textInput = combobox.querySelector('input[type="text"]') as HTMLElement
expect(textInput).toBeInTheDocument()
// Text inputs are naturally tabbable (no tabindex needed)
expect(textInput.hasAttribute('tabindex')).toBe(false)
})
})
describe('Label association', () => {
test('input wrapper has correct forId for text input', async () => {
const container = await createCombobox(
'id="test-id" name="test" label="Test Label" allow-user-input',
)
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const wrapper = combobox.querySelector('pkt-input-wrapper') as any
expect(wrapper?.forId).toBe('test-id-input')
})
test('input wrapper has correct forId for combobox (no text input)', async () => {
const container = await createCombobox('id="test-id" name="test" label="Test Label"')
const combobox = container.querySelector('pkt-combobox') as PktCombobox
await combobox.updateComplete
const wrapper = combobox.querySelector('pkt-input-wrapper') as any
expect(wrapper?.forId).toBe('test-id-combobox')
})
})
})