From e81620081c85eaf0224b3bc290bf15f589ca7281 Mon Sep 17 00:00:00 2001 From: Tringa Krasniqi Date: Mon, 7 Jun 2021 18:46:03 +0200 Subject: [PATCH] Added isAlert prop so blocking modal/dialog don't need to have the 'alertdialog' role (#18298) --- ...-ac8491bb-f335-48e2-be2c-458dd83f06d9.json | 7 ++ packages/react/etc/react.api.md | 1 + .../react/src/components/Modal/Modal.base.tsx | 4 +- .../react/src/components/Modal/Modal.test.tsx | 95 +++++++++++++++++++ .../react/src/components/Modal/Modal.types.ts | 8 ++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 change/@fluentui-react-ac8491bb-f335-48e2-be2c-458dd83f06d9.json diff --git a/change/@fluentui-react-ac8491bb-f335-48e2-be2c-458dd83f06d9.json b/change/@fluentui-react-ac8491bb-f335-48e2-be2c-458dd83f06d9.json new file mode 100644 index 00000000000000..99936f775fe62c --- /dev/null +++ b/change/@fluentui-react-ac8491bb-f335-48e2-be2c-458dd83f06d9.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Blocking Dialog/Modal always gets assigned role \"alertdialog\" when IsBlocking is true", + "packageName": "@fluentui/react", + "email": "tkrasniqi@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react/etc/react.api.md b/packages/react/etc/react.api.md index 340a59ef1a0853..94b7faf0827a06 100644 --- a/packages/react/etc/react.api.md +++ b/packages/react/etc/react.api.md @@ -5979,6 +5979,7 @@ export interface IModalProps extends React_2.RefAttributes, IAcc containerClassName?: string; dragOptions?: IDragOptions; enableAriaHiddenSiblings?: boolean; + isAlert?: boolean; isBlocking?: boolean; isDarkOverlay?: boolean; isModeless?: boolean; diff --git a/packages/react/src/components/Modal/Modal.base.tsx b/packages/react/src/components/Modal/Modal.base.tsx index 3fd2e09f81d7c0..9c428368efdf4d 100644 --- a/packages/react/src/components/Modal/Modal.base.tsx +++ b/packages/react/src/components/Modal/Modal.base.tsx @@ -101,6 +101,7 @@ export const ModalBase: React.FunctionComponent = React.forwardRef< forceFocusInsideTrap, ignoreExternalFocusing, isBlocking, + isAlert, isClickableOutsideFocusTrap, isDarkOverlay, onDismiss, @@ -150,6 +151,7 @@ export const ModalBase: React.FunctionComponent = React.forwardRef< })); const { keepInBounds } = dragOptions || ({} as IDragOptions); + const isAlertRole = isAlert ?? (isBlocking && !isModeless); const layerClassName = layerProps === undefined ? '' : layerProps.className; const classNames = getClassNames(styles, { @@ -446,7 +448,7 @@ export const ModalBase: React.FunctionComponent = React.forwardRef< (isModalOpen && modalResponsiveMode! >= (responsiveMode || ResponsiveMode.small) && ( { beforeEach(() => { @@ -89,4 +90,98 @@ describe('Modal', () => { }, ); }); + + it('renders a Modal with ARIA role alertDialog when isAlert is true ', () => { + // Mock createPortal to capture its component hierarchy in snapshot output. + const ReactDOM = require('react-dom'); + ReactDOM.createPortal = jest.fn(element => { + return element; + }); + + safeCreate( + + Test Content + , + component => { + const componentInstance = component.root; + expect(componentInstance.findByType(Popup).props.role).toBe('alertdialog'); + ReactDOM.createPortal.mockClear(); + }, + ); + }); + + it('renders Modal with ARIA role dialog when isModeless and isBlocking are set to true', () => { + // Mock createPortal to capture its component hierarchy in snapshot output. + const ReactDOM = require('react-dom'); + ReactDOM.createPortal = jest.fn(element => { + return element; + }); + + safeCreate( + + Test Content + , + component => { + const componentInstance = component.root; + expect(componentInstance.findByType(Popup).props.role).toBe('dialog'); + ReactDOM.createPortal.mockClear(); + }, + ); + }); + + it('renders Modal with ARIA role dialog when isAlert is false', () => { + // Mock createPortal to capture its component hierarchy in snapshot output. + const ReactDOM = require('react-dom'); + ReactDOM.createPortal = jest.fn(element => { + return element; + }); + + safeCreate( + + Test Content + , + component => { + const componentInstance = component.root; + expect(componentInstance.findByType(Popup).props.role).toBe('dialog'); + ReactDOM.createPortal.mockClear(); + }, + ); + }); + + it('renders Modal with ARIA role alertdialog when isBlocking is true', () => { + // Mock createPortal to capture its component hierarchy in snapshot output. + const ReactDOM = require('react-dom'); + ReactDOM.createPortal = jest.fn(element => { + return element; + }); + + safeCreate( + + Test Content + , + component => { + const componentInstance = component.root; + expect(componentInstance.findByType(Popup).props.role).toBe('alertdialog'); + ReactDOM.createPortal.mockClear(); + }, + ); + }); }); diff --git a/packages/react/src/components/Modal/Modal.types.ts b/packages/react/src/components/Modal/Modal.types.ts index 15e3f4bd56b73a..830bde14c06076 100644 --- a/packages/react/src/components/Modal/Modal.types.ts +++ b/packages/react/src/components/Modal/Modal.types.ts @@ -124,6 +124,14 @@ export interface IModalProps extends React.RefAttributes, IAcces */ isModeless?: boolean; + /** + * Determines the ARIA role of the dialog (alertdialog/dialog) + * If this is set, it will override the ARIA role determined by isBlocking and isModeless + * + * For more information regarding dialogs please see https://w3c.github.io/aria-practices/#alertdialog + */ + isAlert?: boolean; + /** * Optional class name to be added to the root class */