diff --git a/src/Form/FormAutosuggest.jsx b/src/Form/FormAutosuggest.jsx index 5fb689b2e8..499f8444ab 100644 --- a/src/Form/FormAutosuggest.jsx +++ b/src/Form/FormAutosuggest.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useState, } from 'react'; import PropTypes from 'prop-types'; +import { v4 as uuidv4 } from 'uuid'; import { useIntl } from 'react-intl'; import { KeyboardArrowUp, KeyboardArrowDown } from '../../icons'; import Icon from '../Icon'; @@ -38,6 +39,11 @@ function FormAutosuggest({ errorMessage: '', dropDownItems: [], }); + const [activeMenuItemId, setActiveMenuItemId] = useState(null); + + const handleMenuItemFocus = (menuItemId) => { + setActiveMenuItemId(menuItemId); + }; const handleItemClick = (e, onClick) => { const clickedValue = e.currentTarget.getAttribute('data-value'); @@ -63,12 +69,15 @@ function FormAutosuggest({ let childrenOpt = React.Children.map(children, (child) => { // eslint-disable-next-line no-shadow const { children, onClick, ...rest } = child.props; + const menuItemId = uuidv4(); return React.cloneElement(child, { ...rest, children, 'data-value': children, onClick: (e) => handleItemClick(e, onClick), + id: menuItemId, + onFocus: () => handleMenuItemFocus(menuItemId), }); }); @@ -219,6 +228,9 @@ function FormAutosuggest({ return (
+
+ {`${state.dropDownItems.length} options found`} +
0).toString()} @@ -228,6 +240,7 @@ function FormAutosuggest({ autoComplete="off" value={state.displayValue} aria-invalid={state.errorMessage} + aria-activedescendant={activeMenuItemId} onChange={handleOnChange} onClick={handleClick} trailingElement={iconToggle} diff --git a/src/Form/tests/FormAutosuggest.test.jsx b/src/Form/tests/FormAutosuggest.test.jsx index 8eaafb0e8d..27548704a3 100644 --- a/src/Form/tests/FormAutosuggest.test.jsx +++ b/src/Form/tests/FormAutosuggest.test.jsx @@ -85,6 +85,33 @@ describe('render behavior', () => { expect(formControlFeedback).toBeInTheDocument(); }); + + it('renders component with options that all have IDs', () => { + const { getByTestId, getAllByTestId } = render(); + const input = getByTestId('autosuggest-textbox-input'); + + userEvent.click(input); + const optionItemIds = getAllByTestId('autosuggest-optionitem').map(item => item.id); + + expect(optionItemIds).not.toContain(null); + expect(optionItemIds).not.toContain(undefined); + }); + + it('confirms that the value of the aria-live attribute on the wrapper component is assertive', () => { + const { getByTestId } = render(); + + expect(getByTestId('autosuggest-screen-reader-options-count').getAttribute('aria-live')).toEqual('assertive'); + }); + + it('displays correct amount of options found to screen readers', () => { + const { getByText, getByTestId } = render(); + const input = getByTestId('autosuggest-textbox-input'); + + expect(getByText('0 options found')).toBeInTheDocument(); + userEvent.click(input); + + expect(getByText('3 options found')).toBeInTheDocument(); + }); }); describe('controlled behavior', () => { @@ -136,6 +163,17 @@ describe('controlled behavior', () => { expect(onClick).toHaveBeenCalledTimes(0); }); + it('should set the correct activedescendant', () => { + const { getByTestId, getAllByTestId } = render(); + const input = getByTestId('autosuggest-textbox-input'); + + userEvent.click(input); + const expectedOptionId = getAllByTestId('autosuggest-optionitem')[0].id; + userEvent.keyboard('{arrowdown}'); + + expect(input.getAttribute('aria-activedescendant')).toEqual(expectedOptionId); + }); + it('filters dropdown based on typed field value with one match', () => { const { getByTestId, queryAllByTestId } = render(); const input = getByTestId('autosuggest-textbox-input'); @@ -187,4 +225,19 @@ describe('controlled behavior', () => { const updatedList = queryAllByTestId('autosuggest-optionitem'); expect(updatedList.length).toBe(0); }); + + it('updates screen reader option count based on typed field value with multiple matches', () => { + const { getByText, getByTestId } = render(); + const input = getByTestId('autosuggest-textbox-input'); + + expect(getByText('0 options found')).toBeInTheDocument(); + userEvent.click(input); + + expect(getByText('3 options found')).toBeInTheDocument(); + + userEvent.click(input); + userEvent.type(input, '1'); + + expect(getByText('2 options found')).toBeInTheDocument(); + }); });