Skip to content

Commit 25b4a65

Browse files
committed
test(select a11y): add pageUp/pageDown tests (cypress) for <SingleSelectA11y/>
1 parent 0ab2915 commit 25b4a65

File tree

7 files changed

+454
-42
lines changed

7 files changed

+454
-42
lines changed

collections/forms/i18n/en.pot

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ msgstr ""
55
"Content-Type: text/plain; charset=utf-8\n"
66
"Content-Transfer-Encoding: 8bit\n"
77
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
8-
"POT-Creation-Date: 2024-10-16T02:11:46.033Z\n"
9-
"PO-Revision-Date: 2024-10-16T02:11:46.034Z\n"
8+
"POT-Creation-Date: 2024-10-22T03:25:41.899Z\n"
9+
"PO-Revision-Date: 2024-10-22T03:25:41.902Z\n"
1010

1111
msgid "Upload file"
1212
msgstr "Upload file"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
describe('<SingleSelectA11y/>', () => {
2+
it('should highlight the first option on the currently displayed page', () => {
3+
// Given a select with 100 options is displayed
4+
cy.visitStory('Single Select A11y', 'Hundret Options')
5+
6+
// And the menu is visible
7+
cy.findByRole('combobox').click()
8+
cy.findByRole('option', { selected: true }).should('be.visible')
9+
10+
// And the 74th option is being highlighted
11+
cy.findByRole('combobox').focus().type(`Select option 73`)
12+
13+
// And the 70th option is the first option on the current page
14+
let optionOffset
15+
cy.findAllByRole('option')
16+
.eq(70)
17+
.then((option) => {
18+
const { offsetTop } = option.get(0)
19+
optionOffset = offsetTop
20+
})
21+
22+
cy.findByRole('option', { selected: true })
23+
.invoke('parent') // listbox
24+
.invoke('parent') // scrollable div
25+
.then((listBoxParent) => {
26+
console.log('> listBoxParent', listBoxParent.get(0))
27+
console.log('> optionOffset', optionOffset)
28+
listBoxParent.get(0).scrollTop = optionOffset
29+
})
30+
31+
cy.findAllByRole('option').eq(70).should('be.visible')
32+
33+
// When the PageUp key is pressed
34+
cy.findByRole('combobox').trigger('keydown', {
35+
key: 'PageUp',
36+
force: true,
37+
})
38+
39+
// Then the first option on the currently displayed page is highlighted
40+
cy.findByRole('option', { selected: true })
41+
.invoke('attr', 'aria-label')
42+
.should('equal', 'Select option 70')
43+
})
44+
45+
it('should highlight the first option on the previous page', () => {
46+
// Given a select with 100 options is displayed
47+
cy.visitStory('Single Select A11y', 'Hundret Options')
48+
49+
// And the menu is visible
50+
cy.findByRole('combobox').click()
51+
cy.findByRole('option', { selected: true }).should('be.visible')
52+
53+
// And the 70th option is being highlighted
54+
// Will automatically scroll there and make it the first option on the page
55+
cy.findByRole('combobox').focus().type(`Select option 70`)
56+
57+
// When the PageUp key is pressed
58+
cy.findByRole('combobox').trigger('keydown', {
59+
key: 'PageUp',
60+
force: true,
61+
})
62+
63+
// Then the first option on the previous page is highlighted
64+
cy.findByRole('option', { selected: true })
65+
.invoke('attr', 'aria-label')
66+
.should('equal', 'Select option 61')
67+
68+
// And the previously highlighted option is not visible
69+
cy.findAllByRole('option').eq(70).should('not.be.visible')
70+
})
71+
72+
it('should highlight the first option', () => {
73+
// Given a select with 100 options is displayed
74+
cy.visitStory('Single Select A11y', 'Hundret Options')
75+
76+
// And the menu is visible
77+
cy.findByRole('combobox').click()
78+
cy.findByRole('option', { selected: true }).should('be.visible')
79+
80+
// And the 2nd option is being highlighted
81+
cy.findByRole('combobox').focus().type(`Select option 1`)
82+
83+
// And the 2nd option is the first option on the current page
84+
let optionOffset
85+
cy.findAllByRole('option')
86+
.eq(1)
87+
.then((option) => {
88+
const { offsetTop } = option.get(0)
89+
optionOffset = offsetTop
90+
})
91+
92+
cy.findByRole('option', { selected: true })
93+
.invoke('parent') // listbox
94+
.invoke('parent') // scrollable div
95+
.then((listBoxParent) => {
96+
console.log('> listBoxParent', listBoxParent.get(0))
97+
console.log('> optionOffset', optionOffset)
98+
listBoxParent.get(0).scrollTop = optionOffset
99+
})
100+
101+
// When the PageUp key is pressed
102+
cy.findByRole('combobox').trigger('keydown', {
103+
key: 'PageUp',
104+
force: true,
105+
})
106+
107+
// Then the first option is being highlighted
108+
cy.all(
109+
() => cy.findAllByRole('option').first().invoke('get', 0),
110+
() => cy.findByRole('option', { selected: true }).invoke('get', 0)
111+
).then(([firstOption, highlightedOption]) => {
112+
expect(highlightedOption).to.equal(firstOption)
113+
})
114+
})
115+
116+
it('should highlight the last option on the currently displayed page', () => {
117+
// Given a select with 100 options is displayed
118+
cy.visitStory('Single Select A11y', 'Hundret Options')
119+
120+
// And the menu is visible
121+
// first option will be highlighted automatically
122+
cy.findByRole('combobox').click()
123+
cy.findByRole('option', { selected: true }).should('be.visible')
124+
125+
// When the PageDown key is pressed
126+
cy.findByRole('combobox').trigger('keydown', {
127+
key: 'PageDown',
128+
force: true,
129+
})
130+
131+
// Then the last option on the currently displayed page is highlighted
132+
cy.all(
133+
() => cy.get('[role="option"]:visible').last().invoke('get', 0),
134+
() => cy.findByRole('option', { selected: true }).invoke('get', 0)
135+
).then(([lastVisibleOption, highlightedOption]) => {
136+
expect(highlightedOption).to.equal(lastVisibleOption)
137+
})
138+
})
139+
140+
it(
141+
'should highlight the last option on the next page',
142+
// We don't want the options to scroll when we check whether they're
143+
// visible or not (as that'd make them visible)
144+
{ scrollBehavior: false },
145+
() => {
146+
// Given a select with 100 options is displayed
147+
cy.visitStory('Single Select A11y', 'Hundret Options')
148+
149+
// And the menu is visible
150+
cy.findByRole('combobox').click()
151+
cy.findByRole('option', { selected: true }).should('be.visible')
152+
153+
// And the option last visible option is being highlighted
154+
cy.get('[role="option"]:visible').then(($visibleOptions) => {
155+
const visibleOptionsAmount = $visibleOptions.length
156+
157+
for (let i = 0; i < visibleOptionsAmount - 1; ++i) {
158+
cy.findByRole('combobox').trigger('keydown', {
159+
key: 'ArrowDown',
160+
force: true,
161+
})
162+
163+
if (i === visibleOptionsAmount - 2) {
164+
cy.wrap(i).as('lastVisibleOptionIndex')
165+
}
166+
}
167+
})
168+
169+
cy.get('@lastVisibleOptionIndex').then((lastVisibleOptionIndex) => {
170+
cy.get('[role="option"]')
171+
.eq(lastVisibleOptionIndex + 1) // 1-based
172+
.invoke('attr', 'aria-selected')
173+
.should('equal', 'true')
174+
})
175+
176+
// When the PageDown key is pressed
177+
cy.findByRole('combobox').trigger('keydown', {
178+
key: 'PageDown',
179+
force: true,
180+
})
181+
182+
// Then the next page is shown
183+
cy.get('@lastVisibleOptionIndex').then((lastVisibleOptionIndex) => {
184+
cy.get('[role="option"]')
185+
.eq(lastVisibleOptionIndex + 1)
186+
.should('not.be.visible')
187+
cy.get('[role="option"]')
188+
.eq(lastVisibleOptionIndex + 2)
189+
.should('be.visible')
190+
})
191+
192+
// And the last option on the next page is highlighted
193+
cy.get('[role="option"]:visible')
194+
.last()
195+
.invoke('attr', 'aria-selected')
196+
.should('equal', 'true')
197+
198+
// And the previously highlighted option is not visible
199+
cy.get('@lastVisibleOptionIndex').then((lastVisibleOptionIndex) => {
200+
cy.get('[role="option"]')
201+
.eq(lastVisibleOptionIndex + 1)
202+
.invoke('attr', 'aria-selected')
203+
.should('equal', 'false')
204+
})
205+
}
206+
)
207+
208+
it(
209+
'should highlight the last option',
210+
// We don't want the options to scroll when we check whether they're
211+
// visible or not (as that'd make them visible)
212+
{ scrollBehavior: false },
213+
() => {
214+
// Given a select with 100 options is displayed
215+
cy.visitStory('Single Select A11y', 'Hundret Options')
216+
217+
// And the menu is visible
218+
cy.findByRole('combobox').click()
219+
cy.findByRole('option', { selected: true }).should('be.visible')
220+
221+
// And the 2nd-last option is being highlighted and visible
222+
for (
223+
let i = 0;
224+
i < 11; // This will bring us to the second last option exactly
225+
++i
226+
) {
227+
cy.findByRole('combobox').trigger('keydown', {
228+
key: 'PageDown',
229+
force: true,
230+
})
231+
}
232+
cy.findAllByRole('option').eq(98).should('be.visible')
233+
234+
// And the last option is not visible
235+
cy.findAllByRole('option').last().should('not.be.visible')
236+
237+
// When the PageDown key is pressed
238+
cy.findByRole('combobox').trigger('keydown', {
239+
key: 'PageDown',
240+
force: true,
241+
})
242+
243+
// Then the last option is highlighted
244+
cy.all(
245+
() => cy.findAllByRole('option').last().invoke('get', 0),
246+
() =>
247+
cy.findByRole('option', { selected: true }).invoke('get', 0)
248+
).then(([lastOption, highlightedOption]) => {
249+
expect(highlightedOption).to.equal(lastOption)
250+
})
251+
252+
// And the last option is visible
253+
cy.findAllByRole('option').last().should('be.visible')
254+
}
255+
)
256+
})

components/select/src/single-select-a11y/menu/option.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function Option({
7474
data-test={dataTest}
7575
disabled={disabled}
7676
role="option"
77-
aria-selected={highlighted || ''}
77+
aria-selected={highlighted || 'false'}
7878
aria-disabled={disabled}
7979
aria-label={label}
8080
onClick={() => {

components/select/src/single-select-a11y/single-select-a11y.e2e.stories.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, { useState } from 'react'
22
import { SingleSelectA11y } from './single-select-a11y.js'
33

44
export default {
@@ -102,7 +102,7 @@ const fiveOptions = options.slice(0, 5)
102102
export const DefaultPosition = () => (
103103
<SingleSelectA11y
104104
idPrefix="a11y"
105-
value=""
105+
value="anc_1st_visit"
106106
onChange={() => null}
107107
options={fiveOptions}
108108
/>
@@ -112,7 +112,7 @@ export const FlippedPosition = () => (
112112
<>
113113
<SingleSelectA11y
114114
idPrefix="a11y"
115-
value=""
115+
value="anc_1st_visit"
116116
onChange={() => null}
117117
options={options}
118118
/>
@@ -136,7 +136,7 @@ export const ShiftedIntoView = () => (
136136
<>
137137
<SingleSelectA11y
138138
idPrefix="a11y"
139-
value=""
139+
value="anc_1st_visit"
140140
onChange={() => null}
141141
options={options}
142142
/>
@@ -155,3 +155,21 @@ export const ShiftedIntoView = () => (
155155
`}</style>
156156
</>
157157
)
158+
159+
const hundretOptions = Array.apply(null, Array(100)).map((x, i) => ({
160+
value: `${i}`,
161+
label: `Select option ${i}`,
162+
}))
163+
164+
export const HundretOptions = () => {
165+
const [value, setValue] = useState('0')
166+
167+
return (
168+
<SingleSelectA11y
169+
idPrefix="a11y"
170+
value={value}
171+
onChange={setValue}
172+
options={hundretOptions}
173+
/>
174+
)
175+
}

components/select/src/single-select-a11y/single-select-a11y.prod.stories.js

+12-27
Original file line numberDiff line numberDiff line change
@@ -427,35 +427,20 @@ export const WithOptionsAndLoadingText = () => {
427427
}
428428

429429
export const WithManyOptions = () => {
430-
// const [value, setValue] = useState('art_entry_point:_no_pmtct')
431-
const [value, setValue] = useState('10')
432-
const selectOptions = Array.apply(null, Array(100)).map((x, i) => i)
430+
const [value, setValue] = useState('art_entry_point:_no_pmtct')
433431

434432
return (
435-
<>
436-
<SingleSelectA11y
437-
idPrefix="a11y"
438-
value={value}
439-
// valueLabel={
440-
// value
441-
// ? options.find((option) => option.value === value).label
442-
// : ''
443-
// }
444-
onChange={(nextValue) => setValue(nextValue)}
445-
options={selectOptions.map((i) => ({
446-
value: i.toString(),
447-
label: `Select option ${i + 1}`,
448-
}))}
449-
/>
450-
451-
<select onChange={(e) => console.log('> onChange', e)}>
452-
{selectOptions.map((i) => (
453-
<option key={i} value={i}>
454-
Select option {i + 1}
455-
</option>
456-
))}
457-
</select>
458-
</>
433+
<SingleSelectA11y
434+
idPrefix="a11y"
435+
value={value}
436+
valueLabel={
437+
value
438+
? options.find((option) => option.value === value).label
439+
: ''
440+
}
441+
onChange={(nextValue) => setValue(nextValue)}
442+
options={options}
443+
/>
459444
)
460445
}
461446

0 commit comments

Comments
 (0)