Skip to content

Commit e6e30e4

Browse files
committed
fix(deselectOptions): remove toggle and use explicit select and deselect
1 parent 076cf37 commit e6e30e4

File tree

8 files changed

+207
-231
lines changed

8 files changed

+207
-231
lines changed

src/user-event/__mocks__/utils.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ function getTrackedElementValues(element) {
88
checked: element.checked,
99
selectionStart: element.selectionStart,
1010
selectionEnd: element.selectionEnd,
11-
selectedOptions: element.selectedOptions
12-
? Array.from(element.selectedOptions).map(o => o.value)
13-
: null,
11+
12+
// unfortunately, changing a select option doesn't happen within fireEvent
13+
// but rather imperatively via `options.selected = newValue`
14+
// because of this we don't (currently) have a way to track before/after
15+
// in a given fireEvent call.
1416
}
1517
}
1618

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import {userEvent} from '../../'
2+
import {addListeners, setupSelect, setup} from './helpers/utils'
3+
4+
test('fires correct events', async () => {
5+
const {form, select, options, getEventSnapshot} = setupSelect({
6+
multiple: true,
7+
})
8+
options[0].selected = true
9+
10+
await userEvent.deselectOptions(select, '1')
11+
12+
expect(getEventSnapshot()).toMatchInlineSnapshot(`
13+
Events fired on: select[name="select"][value=[]]
14+
15+
option[value="1"][selected=true] - pointerover
16+
select[name="select"][value=["1"]] - pointerenter
17+
option[value="1"][selected=true] - mouseover: Left (0)
18+
select[name="select"][value=["1"]] - mouseenter: Left (0)
19+
option[value="1"][selected=true] - pointermove
20+
option[value="1"][selected=true] - mousemove: Left (0)
21+
option[value="1"][selected=true] - pointerdown
22+
option[value="1"][selected=true] - mousedown: Left (0)
23+
select[name="select"][value=["1"]] - focus
24+
select[name="select"][value=["1"]] - focusin
25+
option[value="1"][selected=true] - pointerup
26+
option[value="1"][selected=true] - mouseup: Left (0)
27+
select[name="select"][value=[]] - input
28+
select[name="select"][value=[]] - change
29+
option[value="1"][selected=false] - click: Left (0)
30+
`)
31+
32+
expect(form).toHaveFormValues({select: []})
33+
})
34+
35+
test('blurs previously focused element', async () => {
36+
const {form, select} = setupSelect({multiple: true})
37+
const button = document.createElement('button')
38+
form.append(button)
39+
40+
const {getEventSnapshot, clearEventCalls} = addListeners(form)
41+
button.focus()
42+
43+
clearEventCalls()
44+
await userEvent.deselectOptions(select, '1')
45+
46+
expect(getEventSnapshot()).toMatchInlineSnapshot(`
47+
Events fired on: form
48+
49+
option[value="1"][selected=false] - pointerover
50+
option[value="1"][selected=false] - mouseover: Left (0)
51+
option[value="1"][selected=false] - pointermove
52+
option[value="1"][selected=false] - mousemove: Left (0)
53+
option[value="1"][selected=false] - pointerdown
54+
option[value="1"][selected=false] - mousedown: Left (0)
55+
select[name="select"][value=[]] - focusin
56+
option[value="1"][selected=false] - pointerup
57+
option[value="1"][selected=false] - mouseup: Left (0)
58+
option[value="1"][selected=false] - click: Left (0)
59+
`)
60+
})
61+
62+
test('toggle options as expected', async () => {
63+
const {element} = setup(`
64+
<form>
65+
<select name="select" multiple>
66+
<option value="1">One</option>
67+
<optgroup label="Group Name">
68+
<option value="2">Two</option>
69+
<option value="3">Three</option>
70+
</optgroup>
71+
</select>
72+
</form>
73+
`)
74+
75+
const select = element.querySelector('select')
76+
77+
// select one
78+
await userEvent.selectOptions(select, ['1'])
79+
80+
expect(element).toHaveFormValues({select: ['1']})
81+
82+
// select two and three
83+
await userEvent.selectOptions(select, ['2', '3'])
84+
expect(element).toHaveFormValues({select: ['1', '2', '3']})
85+
86+
// deselect one and three
87+
await userEvent.deselectOptions(select, ['1', '3'])
88+
expect(element).toHaveFormValues({select: ['2']})
89+
})
90+
91+
test('sets the selected prop on the selected option using option html elements', async () => {
92+
const {select, options} = setupSelect({multiple: true})
93+
const [o1, o2, o3] = options
94+
o2.selected = true
95+
o3.selected = true
96+
97+
await userEvent.deselectOptions(select, [o3, o2])
98+
expect(o1.selected).toBe(false)
99+
expect(o2.selected).toBe(false)
100+
expect(o3.selected).toBe(false)
101+
})
102+
103+
test('throws error when provided element is not a multiple select', async () => {
104+
const {element} = setup(`<select />`)
105+
106+
const error = await userEvent.deselectOptions(element).catch(e => e)
107+
expect(error.message).toMatchInlineSnapshot(`
108+
"Unable to deselect an option in a non-multiple select. Use selectOptions to change the selection instead.
109+
110+
<select />"
111+
`)
112+
})
113+
114+
test('throws an error one selected option does not match', async () => {
115+
const {element} = setup(
116+
`<select multiple><option value="a">A</option><option value="b">B</option></select>`,
117+
)
118+
const error = await userEvent
119+
.deselectOptions(element, 'Matches nothing')
120+
.catch(e => e)
121+
expect(error.message).toMatchInlineSnapshot(`
122+
"Value "Matches nothing" not found in options
123+
124+
<select
125+
multiple=""
126+
>
127+
<option
128+
value="a"
129+
>
130+
A
131+
</option>
132+
<option
133+
value="b"
134+
>
135+
B
136+
</option>
137+
</select>"
138+
`)
139+
})

src/user-event/__tests__/helpers/utils.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ function getElementDisplayName(element) {
101101
element.htmlFor ? `[for="${element.htmlFor}"]` : null,
102102
value ? `[value=${value}]` : null,
103103
hasChecked ? `[checked=${element.checked}]` : null,
104+
element.tagName === 'OPTION' ? `[selected=${element.selected}]` : null,
104105
]
105106
.filter(Boolean)
106107
.join('')
@@ -232,13 +233,11 @@ const changeLabelGetter = {
232233
before.checked ? 'checked' : 'unchecked',
233234
after.checked ? 'checked' : 'unchecked',
234235
].join(' -> '),
235-
selectedOptions: ({before, after}) => {
236-
const beforeString = JSON.stringify(before.selectedOptions)
237-
const afterString = JSON.stringify(after.selectedOptions)
238-
return beforeString === afterString
239-
? null
240-
: [beforeString, afterString].join(' -> ')
241-
},
236+
237+
// unfortunately, changing a select option doesn't happen within fireEvent
238+
// but rather imperatively via `options.selected = newValue`
239+
// because of this we don't (currently) have a way to track before/after
240+
// in a given fireEvent call.
242241
}
243242
changeLabelGetter.selectionStart = changeLabelGetter.value
244243
changeLabelGetter.selectionEnd = changeLabelGetter.value

src/user-event/__tests__/select-options.js

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,34 @@ test('fires correct events on multi-selects', async () => {
3535
expect(getEventSnapshot()).toMatchInlineSnapshot(`
3636
Events fired on: select[name="select"][value=["1","3"]]
3737
38-
option[value="1"] - pointerover
38+
option[value="1"][selected=false] - pointerover
3939
select[name="select"][value=[]] - pointerenter
40-
option[value="1"] - mouseover: Left (0)
40+
option[value="1"][selected=false] - mouseover: Left (0)
4141
select[name="select"][value=[]] - mouseenter: Left (0)
42-
option[value="1"] - pointermove
43-
option[value="1"] - mousemove: Left (0)
44-
option[value="1"] - pointerdown
45-
option[value="1"] - mousedown: Left (0)
42+
option[value="1"][selected=false] - pointermove
43+
option[value="1"][selected=false] - mousemove: Left (0)
44+
option[value="1"][selected=false] - pointerdown
45+
option[value="1"][selected=false] - mousedown: Left (0)
4646
select[name="select"][value=[]] - focus
4747
select[name="select"][value=[]] - focusin
48-
option[value="1"] - pointerup
49-
option[value="1"] - mouseup: Left (0)
48+
option[value="1"][selected=false] - pointerup
49+
option[value="1"][selected=false] - mouseup: Left (0)
5050
select[name="select"][value=["1"]] - input
5151
select[name="select"][value=["1"]] - change
52-
option[value="1"] - click: Left (0)
53-
option[value="3"] - pointerover
52+
option[value="1"][selected=true] - click: Left (0)
53+
option[value="3"][selected=false] - pointerover
5454
select[name="select"][value=["1"]] - pointerenter
55-
option[value="3"] - mouseover: Left (0)
55+
option[value="3"][selected=false] - mouseover: Left (0)
5656
select[name="select"][value=["1"]] - mouseenter: Left (0)
57-
option[value="3"] - pointermove
58-
option[value="3"] - mousemove: Left (0)
59-
option[value="3"] - pointerdown
60-
option[value="3"] - mousedown: Left (0)
61-
option[value="3"] - pointerup
62-
option[value="3"] - mouseup: Left (0)
57+
option[value="3"][selected=false] - pointermove
58+
option[value="3"][selected=false] - mousemove: Left (0)
59+
option[value="3"][selected=false] - pointerdown
60+
option[value="3"][selected=false] - mousedown: Left (0)
61+
option[value="3"][selected=false] - pointerup
62+
option[value="3"][selected=false] - mouseup: Left (0)
6363
select[name="select"][value=["1","3"]] - input
6464
select[name="select"][value=["1","3"]] - change
65-
option[value="3"] - click: Left (0)
65+
option[value="3"][selected=true] - click: Left (0)
6666
`)
6767
const [o1, o2, o3] = options
6868
expect(o1.selected).toBe(true)

src/user-event/__tests__/toggle-selectoptions.js

Lines changed: 0 additions & 110 deletions
This file was deleted.

src/user-event/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
export {toggleSelectOptions} from './toggle-select-options'
21
export {click, dblClick} from './click'
32
export {type} from './type'
43
export {clear} from './clear'
54
export {tab} from './tab'
65
export {hover, unhover} from './hover'
76
export {upload} from './upload'
8-
export {selectOptions} from './select-options'
7+
export {selectOptions, deselectOptions} from './select-options'
98
export {focus} from './focus'
109
export {blur} from './blur'

0 commit comments

Comments
 (0)