Skip to content

Commit

Permalink
fix: support listbox options in selectOptions (#473)
Browse files Browse the repository at this point in the history
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
  • Loading branch information
luistak and kentcdodds committed Oct 22, 2020
1 parent 2d19a65 commit 9d622f0
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 14 deletions.
37 changes: 36 additions & 1 deletion src/__tests__/helpers/utils.js
Expand Up @@ -55,6 +55,32 @@ function setupSelect({
}
}

function setupListbox() {
const wrapper = document.createElement('div')
wrapper.innerHTML = `
<button id="button" aria-haspopup="listbox">
Some label
</button>
<ul
role="listbox"
name="listbox"
aria-labelledby="button"
>
<li id="1" role="option" aria-selected="false">1</li>
<li id="2" role="option" aria-selected="false">2</li>
<li id="3" role="option" aria-selected="false">3</li>
</ul>
`
document.body.append(wrapper)
const listbox = wrapper.querySelector('[role="listbox"]')
const options = Array.from(wrapper.querySelectorAll('[role="option"]'))
return {
...addListeners(listbox),
listbox,
options,
}
}

const eventLabelGetters = {
KeyboardEvent(event) {
return [
Expand Down Expand Up @@ -91,6 +117,12 @@ function addEventListener(el, type, listener, options) {
function getElementValue(element) {
if (element.tagName === 'SELECT' && element.multiple) {
return JSON.stringify(Array.from(element.selectedOptions).map(o => o.value))
} else if (element.getAttribute('role') === 'listbox') {
return JSON.stringify(
element.querySelector('[aria-selected="true"]')?.innerHTML,
)
} else if (element.getAttribute('role') === 'option') {
return JSON.stringify(element.innerHTML)
} else if (
element.type === 'checkbox' ||
element.type === 'radio' ||
Expand All @@ -114,6 +146,9 @@ function getElementDisplayName(element) {
value ? `[value=${value}]` : null,
hasChecked ? `[checked=${element.checked}]` : null,
element.tagName === 'OPTION' ? `[selected=${element.selected}]` : null,
element.getAttribute('role') === 'option'
? `[aria-selected=${element.getAttribute('aria-selected')}]`
: null,
]
.filter(Boolean)
.join('')
Expand Down Expand Up @@ -278,4 +313,4 @@ afterEach(() => {
document.body.innerHTML = ''
})

export {setup, setupSelect, addEventListener, addListeners}
export {setup, setupSelect, setupListbox, addEventListener, addListeners}
58 changes: 57 additions & 1 deletion src/__tests__/select-options.js
@@ -1,5 +1,5 @@
import userEvent from '../'
import {setupSelect, addListeners} from './helpers/utils'
import {setupSelect, addListeners, setupListbox} from './helpers/utils'

test('fires correct events', () => {
const {select, options, getEventSnapshot} = setupSelect()
Expand Down Expand Up @@ -29,6 +29,53 @@ test('fires correct events', () => {
expect(o3.selected).toBe(false)
})

test('fires correct events on listBox select', () => {
const {listbox, options, getEventSnapshot} = setupListbox()
userEvent.selectOptions(listbox, '2')
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: ul[value="2"]
ul - pointerover
ul - pointerenter
ul - mouseover: Left (0)
ul - mouseenter: Left (0)
ul - pointermove
ul - mousemove: Left (0)
ul - pointerdown
ul - mousedown: Left (0)
ul - pointerup
ul - mouseup: Left (0)
ul - click: Left (0)
li#2[value="2"][aria-selected=true] - pointerover
ul[value="2"] - pointerenter
li#2[value="2"][aria-selected=true] - mouseover: Left (0)
ul[value="2"] - mouseenter: Left (0)
li#2[value="2"][aria-selected=true] - pointermove
li#2[value="2"][aria-selected=true] - mousemove: Left (0)
li#2[value="2"][aria-selected=true] - pointerover
ul[value="2"] - pointerenter
li#2[value="2"][aria-selected=true] - mouseover: Left (0)
ul[value="2"] - mouseenter: Left (0)
li#2[value="2"][aria-selected=true] - pointermove
li#2[value="2"][aria-selected=true] - mousemove: Left (0)
li#2[value="2"][aria-selected=true] - pointerdown
li#2[value="2"][aria-selected=true] - mousedown: Left (0)
li#2[value="2"][aria-selected=true] - pointerup
li#2[value="2"][aria-selected=true] - mouseup: Left (0)
li#2[value="2"][aria-selected=true] - click: Left (0)
li#2[value="2"][aria-selected=true] - pointermove
li#2[value="2"][aria-selected=true] - mousemove: Left (0)
li#2[value="2"][aria-selected=true] - pointerout
ul[value="2"] - pointerleave
li#2[value="2"][aria-selected=true] - mouseout: Left (0)
ul[value="2"] - mouseleave: Left (0)
`)
const [o1, o2, o3] = options
expect(o1).toHaveAttribute('aria-selected', 'false')
expect(o2).toHaveAttribute('aria-selected', 'true')
expect(o3).toHaveAttribute('aria-selected', 'false')
})

test('fires correct events on multi-selects', () => {
const {select, options, getEventSnapshot} = setupSelect({multiple: true})
userEvent.selectOptions(select, ['1', '3'])
Expand Down Expand Up @@ -79,6 +126,15 @@ test('sets the selected prop on the selected option using option html elements',
expect(o3.selected).toBe(false)
})

test('sets the selected prop on the selected listbox option using option html elements', () => {
const {listbox, options} = setupListbox()
const [o1, o2, o3] = options
userEvent.selectOptions(listbox, o1)
expect(o1).toHaveAttribute('aria-selected', 'true')
expect(o2).toHaveAttribute('aria-selected', 'false')
expect(o3).toHaveAttribute('aria-selected', 'false')
})

test('a previously focused input gets blurred', () => {
const button = document.createElement('button')
document.body.append(button)
Expand Down
35 changes: 23 additions & 12 deletions src/select-options.js
@@ -1,6 +1,7 @@
import {createEvent, getConfig, fireEvent} from '@testing-library/dom'
import {click} from './click'
import {focus} from './focus'
import {hover, unhover} from './hover'

function selectOptionsBase(newValue, select, values, init) {
if (!newValue && !select.multiple) {
Expand All @@ -18,7 +19,9 @@ function selectOptionsBase(newValue, select, values, init) {
if (allOptions.includes(val)) {
return val
} else {
const matchingOption = allOptions.find(o => o.value === val)
const matchingOption = allOptions.find(
o => o.value === val || o.innerHTML === val,
)
if (matchingOption) {
return matchingOption
} else {
Expand Down Expand Up @@ -61,17 +64,25 @@ function selectOptionsBase(newValue, select, values, init) {
}

function selectOption(option) {
option.selected = newValue
fireEvent(
select,
createEvent('input', select, {
bubbles: true,
cancelable: false,
composed: true,
...init,
}),
)
fireEvent.change(select, init)
if (option.getAttribute('role') === 'option') {
option?.setAttribute?.('aria-selected', newValue)

hover(option, init)
click(option, init)
unhover(option, init)
} else {
option.selected = newValue
fireEvent(
select,
createEvent('input', select, {
bubbles: true,
cancelable: false,
composed: true,
...init,
}),
)
fireEvent.change(select, init)
}
}
}

Expand Down

0 comments on commit 9d622f0

Please sign in to comment.