Skip to content

Commit

Permalink
test(select a11y): add pageUp/pageDown tests (cypress) for <SingleSel…
Browse files Browse the repository at this point in the history
…ectA11y/>
  • Loading branch information
Mohammer5 committed Oct 22, 2024
1 parent 0ab2915 commit 1e0a40e
Show file tree
Hide file tree
Showing 7 changed files with 430 additions and 38 deletions.
4 changes: 2 additions & 2 deletions collections/forms/i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-10-16T02:11:46.033Z\n"
"PO-Revision-Date: 2024-10-16T02:11:46.034Z\n"
"POT-Creation-Date: 2024-10-22T03:25:41.899Z\n"
"PO-Revision-Date: 2024-10-22T03:25:41.902Z\n"

msgid "Upload file"
msgstr "Upload file"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
describe('<SingleSelectA11y/>', () => {
it('should highlight the first option on the currently displayed page', () => {
// Given a select with 100 options is displayed
cy.visitStory('Single Select A11y', 'Hundret Options')

// And the menu is visible
cy.findByRole('combobox').click()
cy.findByRole('option', { selected: true }).should('be.visible')

// And the 74th option is being highlighted
cy.findByRole('combobox').focus().type(`Select option 73`)

// And the 70th option is the first option on the current page
let optionOffset
cy.findAllByRole('option').eq(70).then(option => {
const { offsetTop } = option.get(0)
optionOffset = offsetTop
})

cy.findByRole('option', { selected: true })
.invoke('parent') // listbox
.invoke('parent') // scrollable div
.then((listBoxParent) => {
console.log('> listBoxParent', listBoxParent.get(0))
console.log('> optionOffset', optionOffset)
listBoxParent.get(0).scrollTop = optionOffset
})

cy.findAllByRole('option').eq(70).should('be.visible')

// When the PageUp key is pressed
cy.findByRole('combobox').trigger('keydown', { key: 'PageUp', force: true })

// Then the first option on the currently displayed page is highlighted
cy.findByRole('option', { selected: true })
.invoke('attr', 'aria-label')
.should('equal', 'Select option 70')
})

it('should highlight the first option on the previous page', () => {
// Given a select with 100 options is displayed
cy.visitStory('Single Select A11y', 'Hundret Options')

// And the menu is visible
cy.findByRole('combobox').click()
cy.findByRole('option', { selected: true }).should('be.visible')

// And the 70th option is being highlighted
// Will automatically scroll there and make it the first option on the page
cy.findByRole('combobox').focus().type(`Select option 70`)

// When the PageUp key is pressed
cy.findByRole('combobox').trigger('keydown', { key: 'PageUp', force: true })

// Then the first option on the previous page is highlighted
cy.findByRole('option', { selected: true })
.invoke('attr', 'aria-label')
.should('equal', 'Select option 61')

// And the previously highlighted option is not visible
cy.findAllByRole('option').eq(70).should('not.be.visible')
})

it('should highlight the first option', () => {
// Given a select with 100 options is displayed
cy.visitStory('Single Select A11y', 'Hundret Options')

// And the menu is visible
cy.findByRole('combobox').click()
cy.findByRole('option', { selected: true }).should('be.visible')

// And the 2nd option is being highlighted
cy.findByRole('combobox').focus().type(`Select option 1`)

// And the 2nd option is the first option on the current page
let optionOffset
cy.findAllByRole('option').eq(1).then(option => {
const { offsetTop } = option.get(0)
optionOffset = offsetTop
})

cy.findByRole('option', { selected: true })
.invoke('parent') // listbox
.invoke('parent') // scrollable div
.then((listBoxParent) => {
console.log('> listBoxParent', listBoxParent.get(0))
console.log('> optionOffset', optionOffset)
listBoxParent.get(0).scrollTop = optionOffset
})

// When the PageUp key is pressed
cy.findByRole('combobox').trigger('keydown', { key: 'PageUp', force: true })

// Then the first option is being highlighted
cy.all(
() => cy.findAllByRole('option').first().invoke('get', 0),
() => cy.findByRole('option', { selected: true }).invoke('get', 0)
).then(([firstOption, highlightedOption]) => {
expect(highlightedOption).to.equal(firstOption)
})
})

it('should highlight the last option on the currently displayed page', () => {
// Given a select with 100 options is displayed
cy.visitStory('Single Select A11y', 'Hundret Options')

// And the menu is visible
// first option will be highlighted automatically
cy.findByRole('combobox').click()
cy.findByRole('option', { selected: true }).should('be.visible')

// When the PageDown key is pressed
cy.findByRole('combobox').trigger('keydown', { key: 'PageDown', force: true })

// Then the last option on the currently displayed page is highlighted
cy.all(
() => cy.get('[role="option"]:visible').last().invoke('get', 0),
() => cy.findByRole('option', { selected: true }).invoke('get', 0)
).then(([lastVisibleOption, highlightedOption]) => {
expect(highlightedOption).to.equal(lastVisibleOption)
})
})

it(
'should highlight the last option on the next page',
// We don't want the options to scroll when we check whether they're
// visible or not (as that'd make them visible)
{ scrollBehavior: false },
() => {
// Given a select with 100 options is displayed
cy.visitStory('Single Select A11y', 'Hundret Options')

// And the menu is visible
cy.findByRole('combobox').click()
cy.findByRole('option', { selected: true }).should('be.visible')

// And the option last visible option is being highlighted
cy.get('[role="option"]:visible').then(($visibleOptions) => {
const visibleOptionsAmount = $visibleOptions.length

for (let i = 0; i < visibleOptionsAmount - 1; ++i) {
cy.findByRole('combobox').trigger('keydown', { key: 'ArrowDown', force: true })

if (i === visibleOptionsAmount - 2) {
cy.wrap(i).as('lastVisibleOptionIndex')
}
}
})

cy.get('@lastVisibleOptionIndex').then((lastVisibleOptionIndex) => {
cy
.get('[role="option"]')
.eq(lastVisibleOptionIndex + 1) // 1-based
.invoke('attr', 'aria-selected')
.should('equal', 'true')
})

// When the PageDown key is pressed
cy.findByRole('combobox').trigger('keydown', { key: 'PageDown', force: true })

// Then the next page is shown
cy.get('@lastVisibleOptionIndex').then((lastVisibleOptionIndex) => {
cy.get('[role="option"]').eq(lastVisibleOptionIndex + 1).should(
'not.be.visible'
)
cy.get('[role="option"]').eq(lastVisibleOptionIndex + 2).should(
'be.visible'
)
})

// And the last option on the next page is highlighted
cy
.get('[role="option"]:visible')
.last()
.invoke('attr', 'aria-selected')
.should('equal', 'true')

// And the previously highlighted option is not visible
cy.get('@lastVisibleOptionIndex').then((lastVisibleOptionIndex) => {
cy
.get('[role="option"]')
.eq(lastVisibleOptionIndex + 1)
.invoke('attr', 'aria-selected')
.should('equal', 'false')
})
}
)

it(
'should highlight the last option',
// We don't want the options to scroll when we check whether they're
// visible or not (as that'd make them visible)
{ scrollBehavior: false },
() => {
// Given a select with 100 options is displayed
cy.visitStory('Single Select A11y', 'Hundret Options')

// And the menu is visible
cy.findByRole('combobox').click()
cy.findByRole('option', { selected: true }).should('be.visible')

// And the 2nd-last option is being highlighted and visible
for (
let i = 0;
i < 11; // This will bring us to the second last option exactly
++i
) {
cy.findByRole('combobox').trigger('keydown', { key: 'PageDown', force: true })
}
cy.findAllByRole('option').eq(98).should('be.visible')

// And the last option is not visible
cy.findAllByRole('option').last().should('not.be.visible')

// When the PageDown key is pressed
cy.findByRole('combobox').trigger('keydown', { key: 'PageDown', force: true })

// Then the last option is highlighted
cy.all(
() => cy.findAllByRole('option').last().invoke('get', 0),
() => cy.findByRole('option', { selected: true }).invoke('get', 0)
).then(([lastOption, highlightedOption]) => {
expect(highlightedOption).to.equal(lastOption)
})

// And the last option is visible
cy.findAllByRole('option').last().should('be.visible')
}
)
})
2 changes: 1 addition & 1 deletion components/select/src/single-select-a11y/menu/option.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function Option({
data-test={dataTest}
disabled={disabled}
role="option"
aria-selected={highlighted || ''}
aria-selected={highlighted || 'false'}
aria-disabled={disabled}
aria-label={label}
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useState } from 'react'
import { SingleSelectA11y } from './single-select-a11y.js'

export default {
Expand Down Expand Up @@ -102,7 +102,7 @@ const fiveOptions = options.slice(0, 5)
export const DefaultPosition = () => (
<SingleSelectA11y
idPrefix="a11y"
value=""
value="anc_1st_visit"
onChange={() => null}
options={fiveOptions}
/>
Expand All @@ -112,7 +112,7 @@ export const FlippedPosition = () => (
<>
<SingleSelectA11y
idPrefix="a11y"
value=""
value="anc_1st_visit"
onChange={() => null}
options={options}
/>
Expand All @@ -136,7 +136,7 @@ export const ShiftedIntoView = () => (
<>
<SingleSelectA11y
idPrefix="a11y"
value=""
value="anc_1st_visit"
onChange={() => null}
options={options}
/>
Expand All @@ -155,3 +155,21 @@ export const ShiftedIntoView = () => (
`}</style>
</>
)

const hundretOptions = Array.apply(null, Array(100)).map((x, i) => ({
value: `${i}`,
label: `Select option ${i}`,
}))

export const HundretOptions = () => {
const [value, setValue] = useState('0')

return (
<SingleSelectA11y
idPrefix="a11y"
value={value}
onChange={setValue}
options={hundretOptions}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -427,35 +427,20 @@ export const WithOptionsAndLoadingText = () => {
}

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

return (
<>
<SingleSelectA11y
idPrefix="a11y"
value={value}
// valueLabel={
// value
// ? options.find((option) => option.value === value).label
// : ''
// }
onChange={(nextValue) => setValue(nextValue)}
options={selectOptions.map((i) => ({
value: i.toString(),
label: `Select option ${i + 1}`,
}))}
/>

<select onChange={(e) => console.log('> onChange', e)}>
{selectOptions.map((i) => (
<option key={i} value={i}>
Select option {i + 1}
</option>
))}
</select>
</>
<SingleSelectA11y
idPrefix="a11y"
value={value}
valueLabel={
value
? options.find((option) => option.value === value).label
: ''
}
onChange={(nextValue) => setValue(nextValue)}
options={options}
/>
)
}

Expand Down
Loading

0 comments on commit 1e0a40e

Please sign in to comment.