Skip to content

Commit 63417d5

Browse files
authored
Fix missing aria-expanded for ComboboxInput component (#1605)
* add test to verify `Combobox.Input` state * incorrect missing `aria-expanded` on `ComboboxInput` * update changelog
1 parent f2fc6d8 commit 63417d5

File tree

6 files changed

+114
-1
lines changed

6 files changed

+114
-1
lines changed

packages/@headlessui-react/src/components/combobox/combobox.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
assertCombobox,
4040
ComboboxMode,
4141
assertNotActiveComboboxOption,
42+
assertComboboxInput,
4243
} from '../../test-utils/accessibility-assertions'
4344
import { Transition } from '../transitions/transition'
4445

@@ -296,8 +297,11 @@ describe('Rendering', () => {
296297

297298
render(<Example />)
298299

300+
assertComboboxInput({ state: ComboboxState.InvisibleUnmounted })
301+
299302
await click(getComboboxButton())
300303

304+
assertComboboxInput({ state: ComboboxState.Visible })
301305
assertComboboxList({ state: ComboboxState.Visible })
302306

303307
await click(getComboboxOptions()[1])

packages/@headlessui-react/src/test-utils/accessibility-assertions.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,57 @@ export function assertCombobox(
330330
}
331331
}
332332

333+
export function assertComboboxInput(
334+
options: {
335+
attributes?: Record<string, string | null>
336+
state: ComboboxState
337+
},
338+
input = getComboboxInput()
339+
) {
340+
try {
341+
if (input === null) return expect(input).not.toBe(null)
342+
343+
// Ensure combobox input has these properties
344+
expect(input).toHaveAttribute('id')
345+
346+
switch (options.state) {
347+
case ComboboxState.Visible:
348+
expect(input).toHaveAttribute('aria-controls')
349+
expect(input).toHaveAttribute('aria-expanded', 'true')
350+
break
351+
352+
case ComboboxState.InvisibleHidden:
353+
expect(input).toHaveAttribute('aria-controls')
354+
if (input.hasAttribute('disabled')) {
355+
expect(input).not.toHaveAttribute('aria-expanded')
356+
} else {
357+
expect(input).toHaveAttribute('aria-expanded', 'false')
358+
}
359+
break
360+
361+
case ComboboxState.InvisibleUnmounted:
362+
expect(input).not.toHaveAttribute('aria-controls')
363+
if (input.hasAttribute('disabled')) {
364+
expect(input).not.toHaveAttribute('aria-expanded')
365+
} else {
366+
expect(input).toHaveAttribute('aria-expanded', 'false')
367+
}
368+
break
369+
370+
default:
371+
assertNever(options.state)
372+
}
373+
374+
// Ensure combobox input has the following attributes
375+
for (let attributeName in options.attributes) {
376+
expect(input).toHaveAttribute(attributeName, options.attributes[attributeName])
377+
}
378+
} catch (err) {
379+
if (err instanceof Error) Error.captureStackTrace(err, assertComboboxInput)
380+
throw err
381+
}
382+
}
383+
333384
export function assertComboboxList(
334385
options: {
335386
attributes?: Record<string, string | null>

packages/@headlessui-vue/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- Only render the `Dialog` on the client ([#1566](https://github.com/tailwindlabs/headlessui/pull/1566))
2121
- Improve Combobox input cursor position ([#1574](https://github.com/tailwindlabs/headlessui/pull/1574))
2222
- Fix scrolling issue in `Tab` component when using arrow keys ([#1584](https://github.com/tailwindlabs/headlessui/pull/1584))
23+
- Fix missing `aria-expanded` for `ComboboxInput` component ([#1605](https://github.com/tailwindlabs/headlessui/pull/1605))
2324

2425
## [1.6.4] - 2022-05-29
2526

packages/@headlessui-vue/src/components/combobox/combobox.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
assertCombobox,
4646
ComboboxMode,
4747
assertNotActiveComboboxOption,
48+
assertComboboxInput,
4849
} from '../../test-utils/accessibility-assertions'
4950
import { html } from '../../test-utils/html'
5051
import { useOpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
@@ -332,8 +333,11 @@ describe('Rendering', () => {
332333
// TODO: Rendering Example directly reveals a vue bug — I think it's been fixed for a while but I can't find the commit
333334
renderTemplate(Example)
334335

336+
assertComboboxInput({ state: ComboboxState.InvisibleUnmounted })
337+
335338
await click(getComboboxButton())
336339

340+
assertComboboxInput({ state: ComboboxState.Visible })
337341
assertComboboxList({ state: ComboboxState.Visible })
338342

339343
await click(getComboboxOptions()[1])

packages/@headlessui-vue/src/components/combobox/combobox.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,9 @@ export let ComboboxInput = defineComponent({
738738
let slot = { open: api.comboboxState.value === ComboboxStates.Open }
739739
let ourProps = {
740740
'aria-controls': api.optionsRef.value?.id,
741-
'aria-expanded': api.disabled ? undefined : api.comboboxState.value === ComboboxStates.Open,
741+
'aria-expanded': api.disabled.value
742+
? undefined
743+
: api.comboboxState.value === ComboboxStates.Open,
742744
'aria-activedescendant':
743745
api.activeOptionIndex.value === null
744746
? undefined

packages/@headlessui-vue/src/test-utils/accessibility-assertions.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,57 @@ export function assertCombobox(
330330
}
331331
}
332332

333+
export function assertComboboxInput(
334+
options: {
335+
attributes?: Record<string, string | null>
336+
state: ComboboxState
337+
},
338+
input = getComboboxInput()
339+
) {
340+
try {
341+
if (input === null) return expect(input).not.toBe(null)
342+
343+
// Ensure combobox input has these properties
344+
expect(input).toHaveAttribute('id')
345+
346+
switch (options.state) {
347+
case ComboboxState.Visible:
348+
expect(input).toHaveAttribute('aria-controls')
349+
expect(input).toHaveAttribute('aria-expanded', 'true')
350+
break
351+
352+
case ComboboxState.InvisibleHidden:
353+
expect(input).toHaveAttribute('aria-controls')
354+
if (input.hasAttribute('disabled')) {
355+
expect(input).not.toHaveAttribute('aria-expanded')
356+
} else {
357+
expect(input).toHaveAttribute('aria-expanded', 'false')
358+
}
359+
break
360+
361+
case ComboboxState.InvisibleUnmounted:
362+
expect(input).not.toHaveAttribute('aria-controls')
363+
if (input.hasAttribute('disabled')) {
364+
expect(input).not.toHaveAttribute('aria-expanded')
365+
} else {
366+
expect(input).toHaveAttribute('aria-expanded', 'false')
367+
}
368+
break
369+
370+
default:
371+
assertNever(options.state)
372+
}
373+
374+
// Ensure combobox input has the following attributes
375+
for (let attributeName in options.attributes) {
376+
expect(input).toHaveAttribute(attributeName, options.attributes[attributeName])
377+
}
378+
} catch (err) {
379+
if (err instanceof Error) Error.captureStackTrace(err, assertComboboxInput)
380+
throw err
381+
}
382+
}
383+
333384
export function assertComboboxList(
334385
options: {
335386
attributes?: Record<string, string | null>

0 commit comments

Comments
 (0)