Skip to content

Commit 19e8f9c

Browse files
fix(react): announce collapsed state when dropdown closes
1 parent 3f6e920 commit 19e8f9c

3 files changed

Lines changed: 38 additions & 1 deletion

File tree

jest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { getJestProjectsAsync } = require('@nx/jest');
1+
import { getJestProjectsAsync } from '@nx/jest';
22

33
export default async () => ({
44
projects: await getJestProjectsAsync(),

packages/react/src/components/Dropdown/Dropdown.base.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Icon } from '../../Icon';
2929
import { Label } from '../../Label';
3030
import { Panel } from '../../Panel';
3131
import { ResponsiveMode, useResponsiveMode } from '../../ResponsiveMode';
32+
import { Announced } from '../../Announced';
3233
import { SelectableOptionMenuItemType, getAllSelectedOptions } from '../../SelectableOption';
3334
// import and use V7 Checkbox to ensure no breaking changes.
3435
import { Checkbox } from '../../Checkbox';
@@ -219,6 +220,7 @@ class DropdownInternal extends React.Component<IDropdownInternalProps, IDropdown
219220
private _gotMouseMove: boolean;
220221
/** Flag for tracking whether focus is triggered by click (alternatively triggered by keyboard nav) */
221222
private _isFocusedByClick: boolean;
223+
private _wasOpen: boolean;
222224

223225
constructor(props: IDropdownInternalProps) {
224226
super(props);
@@ -270,6 +272,7 @@ class DropdownInternal extends React.Component<IDropdownInternalProps, IDropdown
270272
this._hasBeenPositioned = false;
271273

272274
this._sizePosCache.updateOptions(options);
275+
this._wasOpen = false;
273276

274277
this.state = {
275278
isOpen: false,
@@ -298,10 +301,13 @@ class DropdownInternal extends React.Component<IDropdownInternalProps, IDropdown
298301
if (prevState.isOpen === true && this.state.isOpen === false) {
299302
this._gotMouseMove = false;
300303
this._hasBeenPositioned = false;
304+
this._wasOpen = false;
301305

302306
if (this.props.onDismiss) {
303307
this.props.onDismiss();
304308
}
309+
} else if (prevState.isOpen === false && this.state.isOpen === true) {
310+
this._wasOpen = true;
305311
}
306312
}
307313

@@ -418,6 +424,7 @@ class DropdownInternal extends React.Component<IDropdownInternalProps, IDropdown
418424
},
419425
this._onRenderContainer,
420426
)}
427+
{!isOpen && this._wasOpen && <Announced message="collapsed" />}
421428
{hasErrorMessage && (
422429
<div role="alert" id={errorMessageId} className={this._classNames.errorMessage}>
423430
{errorMessage}

packages/react/src/components/Dropdown/Dropdown.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,36 @@ describe('Dropdown', () => {
726726
});
727727
});
728728

729+
describe('Accessibility Announcements', () => {
730+
it('announces when the dropdown collapses', () => {
731+
jest.useFakeTimers();
732+
const { getByRole, queryByRole, queryAllByRole } = render(<Dropdown options={DEFAULT_OPTIONS} />);
733+
const dropdownRoot = getByRole('combobox');
734+
735+
// Initially, there should be no collapsed announcement
736+
expect(queryAllByRole('status').some(el => el.textContent === 'collapsed')).toBe(false);
737+
738+
// Open the dropdown
739+
fireEvent.click(dropdownRoot);
740+
expect(queryByRole('listbox')).toBeTruthy();
741+
expect(queryAllByRole('status').some(el => el.textContent === 'collapsed')).toBe(false);
742+
743+
// Close the dropdown via Escape key
744+
fireEvent.keyDown(dropdownRoot, { which: KeyCodes.escape });
745+
expect(queryByRole('listbox')).toBeFalsy();
746+
747+
// Advance timers so DelayedRender can render the message
748+
act(() => {
749+
jest.runAllTimers();
750+
});
751+
752+
// Now, the collapsed announcement should be present
753+
expect(queryAllByRole('status').some(el => el.textContent === 'collapsed')).toBe(true);
754+
755+
jest.useRealTimers();
756+
});
757+
});
758+
729759
describe('with simulated async loaded options', () => {
730760
/** See https://github.com/microsoft/fluentui/issues/7315 */
731761
const DropdownWithChangingProps = (props: { multi: boolean }) => {

0 commit comments

Comments
 (0)