diff --git a/src/__tests__/helpers/utils.js b/src/__tests__/helpers/utils.js
index 5c773788..9d5e7c59 100644
--- a/src/__tests__/helpers/utils.js
+++ b/src/__tests__/helpers/utils.js
@@ -55,6 +55,32 @@ function setupSelect({
}
}
+function setupListbox() {
+ const wrapper = document.createElement('div')
+ wrapper.innerHTML = `
+
+
+ `
+ 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 [
@@ -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' ||
@@ -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('')
@@ -278,4 +313,4 @@ afterEach(() => {
document.body.innerHTML = ''
})
-export {setup, setupSelect, addEventListener, addListeners}
+export {setup, setupSelect, setupListbox, addEventListener, addListeners}
diff --git a/src/__tests__/select-options.js b/src/__tests__/select-options.js
index c28c27de..24259ba0 100644
--- a/src/__tests__/select-options.js
+++ b/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()
@@ -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'])
@@ -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)
diff --git a/src/select-options.js b/src/select-options.js
index 5ef82330..967a34b3 100644
--- a/src/select-options.js
+++ b/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) {
@@ -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 {
@@ -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)
+ }
}
}