Skip to content

Commit

Permalink
Fix missing aria-expanded for ComboboxInput component (#1605)
Browse files Browse the repository at this point in the history
* add test to verify `Combobox.Input` state

* incorrect missing `aria-expanded` on `ComboboxInput`

* update changelog
  • Loading branch information
RobinMalfait committed Jun 20, 2022
1 parent f2fc6d8 commit 63417d5
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 1 deletion.
Expand Up @@ -39,6 +39,7 @@ import {
assertCombobox,
ComboboxMode,
assertNotActiveComboboxOption,
assertComboboxInput,
} from '../../test-utils/accessibility-assertions'
import { Transition } from '../transitions/transition'

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

render(<Example />)

assertComboboxInput({ state: ComboboxState.InvisibleUnmounted })

await click(getComboboxButton())

assertComboboxInput({ state: ComboboxState.Visible })
assertComboboxList({ state: ComboboxState.Visible })

await click(getComboboxOptions()[1])
Expand Down
Expand Up @@ -330,6 +330,57 @@ export function assertCombobox(
}
}

export function assertComboboxInput(
options: {
attributes?: Record<string, string | null>
state: ComboboxState
},
input = getComboboxInput()
) {
try {
if (input === null) return expect(input).not.toBe(null)

// Ensure combobox input has these properties
expect(input).toHaveAttribute('id')

switch (options.state) {
case ComboboxState.Visible:
expect(input).toHaveAttribute('aria-controls')
expect(input).toHaveAttribute('aria-expanded', 'true')
break

case ComboboxState.InvisibleHidden:
expect(input).toHaveAttribute('aria-controls')
if (input.hasAttribute('disabled')) {
expect(input).not.toHaveAttribute('aria-expanded')
} else {
expect(input).toHaveAttribute('aria-expanded', 'false')
}
break

case ComboboxState.InvisibleUnmounted:
expect(input).not.toHaveAttribute('aria-controls')
if (input.hasAttribute('disabled')) {
expect(input).not.toHaveAttribute('aria-expanded')
} else {
expect(input).toHaveAttribute('aria-expanded', 'false')
}
break

default:
assertNever(options.state)
}

// Ensure combobox input has the following attributes
for (let attributeName in options.attributes) {
expect(input).toHaveAttribute(attributeName, options.attributes[attributeName])
}
} catch (err) {
if (err instanceof Error) Error.captureStackTrace(err, assertComboboxInput)
throw err
}
}

export function assertComboboxList(
options: {
attributes?: Record<string, string | null>
Expand Down
1 change: 1 addition & 0 deletions packages/@headlessui-vue/CHANGELOG.md
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Only render the `Dialog` on the client ([#1566](https://github.com/tailwindlabs/headlessui/pull/1566))
- Improve Combobox input cursor position ([#1574](https://github.com/tailwindlabs/headlessui/pull/1574))
- Fix scrolling issue in `Tab` component when using arrow keys ([#1584](https://github.com/tailwindlabs/headlessui/pull/1584))
- Fix missing `aria-expanded` for `ComboboxInput` component ([#1605](https://github.com/tailwindlabs/headlessui/pull/1605))

## [1.6.4] - 2022-05-29

Expand Down
Expand Up @@ -45,6 +45,7 @@ import {
assertCombobox,
ComboboxMode,
assertNotActiveComboboxOption,
assertComboboxInput,
} from '../../test-utils/accessibility-assertions'
import { html } from '../../test-utils/html'
import { useOpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
Expand Down Expand Up @@ -332,8 +333,11 @@ describe('Rendering', () => {
// TODO: Rendering Example directly reveals a vue bug — I think it's been fixed for a while but I can't find the commit
renderTemplate(Example)

assertComboboxInput({ state: ComboboxState.InvisibleUnmounted })

await click(getComboboxButton())

assertComboboxInput({ state: ComboboxState.Visible })
assertComboboxList({ state: ComboboxState.Visible })

await click(getComboboxOptions()[1])
Expand Down
4 changes: 3 additions & 1 deletion packages/@headlessui-vue/src/components/combobox/combobox.ts
Expand Up @@ -738,7 +738,9 @@ export let ComboboxInput = defineComponent({
let slot = { open: api.comboboxState.value === ComboboxStates.Open }
let ourProps = {
'aria-controls': api.optionsRef.value?.id,
'aria-expanded': api.disabled ? undefined : api.comboboxState.value === ComboboxStates.Open,
'aria-expanded': api.disabled.value
? undefined
: api.comboboxState.value === ComboboxStates.Open,
'aria-activedescendant':
api.activeOptionIndex.value === null
? undefined
Expand Down
Expand Up @@ -330,6 +330,57 @@ export function assertCombobox(
}
}

export function assertComboboxInput(
options: {
attributes?: Record<string, string | null>
state: ComboboxState
},
input = getComboboxInput()
) {
try {
if (input === null) return expect(input).not.toBe(null)

// Ensure combobox input has these properties
expect(input).toHaveAttribute('id')

switch (options.state) {
case ComboboxState.Visible:
expect(input).toHaveAttribute('aria-controls')
expect(input).toHaveAttribute('aria-expanded', 'true')
break

case ComboboxState.InvisibleHidden:
expect(input).toHaveAttribute('aria-controls')
if (input.hasAttribute('disabled')) {
expect(input).not.toHaveAttribute('aria-expanded')
} else {
expect(input).toHaveAttribute('aria-expanded', 'false')
}
break

case ComboboxState.InvisibleUnmounted:
expect(input).not.toHaveAttribute('aria-controls')
if (input.hasAttribute('disabled')) {
expect(input).not.toHaveAttribute('aria-expanded')
} else {
expect(input).toHaveAttribute('aria-expanded', 'false')
}
break

default:
assertNever(options.state)
}

// Ensure combobox input has the following attributes
for (let attributeName in options.attributes) {
expect(input).toHaveAttribute(attributeName, options.attributes[attributeName])
}
} catch (err) {
if (err instanceof Error) Error.captureStackTrace(err, assertComboboxInput)
throw err
}
}

export function assertComboboxList(
options: {
attributes?: Record<string, string | null>
Expand Down

0 comments on commit 63417d5

Please sign in to comment.