From 810bbf17c3bad98152debb1fda96258ebf4471e6 Mon Sep 17 00:00:00 2001 From: Nandor Date: Thu, 19 Oct 2023 11:12:09 +0200 Subject: [PATCH] test(ui-dialog): migrate old Dialog tests Closes: INSTUI-3879 --- package-lock.json | 20 +- packages/ui-dialog/package.json | 4 +- .../src/Dialog/__new-tests__/Dialog.test.tsx | 557 +++++++++++++++ .../src/Dialog/__tests__/Dialog.test.tsx | 637 ------------------ packages/ui-dialog/tsconfig.build.json | 3 +- 5 files changed, 573 insertions(+), 648 deletions(-) create mode 100644 packages/ui-dialog/src/Dialog/__new-tests__/Dialog.test.tsx delete mode 100644 packages/ui-dialog/src/Dialog/__tests__/Dialog.test.tsx diff --git a/package-lock.json b/package-lock.json index 46d373ab41..cad3ca11f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14115,9 +14115,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.4.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", - "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", + "version": "14.5.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", + "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", "dev": true, "engines": { "node": ">=12", @@ -54182,7 +54182,9 @@ "@instructure/console": "8.46.1", "@instructure/ui-babel-preset": "8.46.1", "@instructure/ui-prop-types": "8.46.1", - "@instructure/ui-test-utils": "8.46.1" + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.5.1" }, "peerDependencies": { "react": ">=16.8 <=18" @@ -59849,8 +59851,10 @@ "@instructure/ui-dom-utils": "8.46.1", "@instructure/ui-prop-types": "8.46.1", "@instructure/ui-react-utils": "8.46.1", - "@instructure/ui-test-utils": "8.46.1", "@instructure/ui-testable": "8.46.1", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.5.1", "prop-types": "^15.8.1" } }, @@ -69299,9 +69303,9 @@ } }, "@testing-library/user-event": { - "version": "14.4.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", - "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", + "version": "14.5.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", + "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", "dev": true, "requires": {} }, diff --git a/packages/ui-dialog/package.json b/packages/ui-dialog/package.json index 168e0e85ec..48b03db02a 100644 --- a/packages/ui-dialog/package.json +++ b/packages/ui-dialog/package.json @@ -35,7 +35,9 @@ "@instructure/console": "8.46.1", "@instructure/ui-babel-preset": "8.46.1", "@instructure/ui-prop-types": "8.46.1", - "@instructure/ui-test-utils": "8.46.1" + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.5.1" }, "peerDependencies": { "react": ">=16.8 <=18" diff --git a/packages/ui-dialog/src/Dialog/__new-tests__/Dialog.test.tsx b/packages/ui-dialog/src/Dialog/__new-tests__/Dialog.test.tsx new file mode 100644 index 0000000000..e808da3b02 --- /dev/null +++ b/packages/ui-dialog/src/Dialog/__new-tests__/Dialog.test.tsx @@ -0,0 +1,557 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { + forwardRef, + useEffect, + useImperativeHandle, + useRef, + useState +} from 'react' +import { fireEvent, render, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import '@testing-library/jest-dom/extend-expect' + +import { Dialog } from '../index' +import type { DialogProps } from '../props' + +const TEST_TEXT = 'test-text' +const TEST_LABEL = 'test-label' + +const renderDialog = (props?: Partial) => { + const allProps: DialogProps = { + open: true, + label: TEST_LABEL, + ...props + } + + return render( + + + + ) +} + +const DialogExample = forwardRef((props: DialogProps, ref: React.Ref) => { + const inputRef = useRef(null) + const dialogRef = useRef(null) + + useEffect(() => { + if (!props.open) { + inputRef.current!.focus() + } + }, [props.open]) + + useImperativeHandle(ref, () => ({ + focusDialog: () => dialogRef.current!.focus(), + blurDialog: () => dialogRef.current!.blur() + })) + + return ( +
+ + + {props.children || ( +
+ + +
+ )} +
+
+ ) +}) +DialogExample.displayName = 'DialogExample' + +const NestedDialogExample = (props: DialogProps) => { + const [nestedOpen, setNestedOpen] = useState(false) + const handleTriggerClick = () => setNestedOpen(true) + + return ( +
+ +
+
+ + +
+ + {TEST_TEXT} + +
+
+
+ ) +} +NestedDialogExample.displayName = 'NestedDialogExample' + +describe('', () => { + it('should render nothing when closed', () => { + const { container } = renderDialog({ open: false }) + + expect(container.firstChild).not.toBeInTheDocument() + }) + + it('should render children when open', async () => { + const { container } = renderDialog({ open: true }) + + expect(container.firstChild).toBeInTheDocument() + expect(container.firstChild).toHaveTextContent(TEST_TEXT) + }) + + it('should apply the a11y attributes', () => { + const { getByRole, getByLabelText } = renderDialog({ label: TEST_LABEL }) + const dialog = getByRole('dialog') + const label = getByLabelText(TEST_LABEL) + + expect(dialog).toBeInTheDocument() + expect(label).toBeInTheDocument() + }) + + it('should apply the role attributes, if explicitly passed', () => { + const { getByRole, getByLabelText } = renderDialog({ + label: TEST_LABEL, + role: 'region' + }) + const regionRole = getByRole('region') + const label = getByLabelText(TEST_LABEL) + + expect(regionRole).toBeInTheDocument() + expect(label).toBeInTheDocument() + }) + + it('should call onDismiss prop when Esc key pressed', async () => { + const onDismiss = jest.fn() + const { getByRole } = renderDialog({ onDismiss }) + const dialog = getByRole('dialog') + + await waitFor(() => { + fireEvent.keyUp(dialog, { + key: 'Escape', + code: 'Escape', + keyCode: 27, + charCode: 27 + }) + expect(onDismiss).toHaveBeenCalled() + }) + }) + + it('should call onDismiss prop when the document is clicked', async () => { + const onDismiss = jest.fn() + renderDialog({ onDismiss, shouldCloseOnDocumentClick: true }) + + await waitFor(() => { + fireEvent.click(document) + expect(onDismiss).toHaveBeenCalled() + }) + }) + + describe('managed focus', () => { + it('should provide focus method', async () => { + const { getByTestId } = render( +
+ getByTestId('non-tabbable')} + > + {TEST_TEXT} + +
+ {TEST_TEXT} +
+
+ ) + const nonTabbableContent = getByTestId('non-tabbable') + + await waitFor(() => { + userEvent.tab() + expect(document.activeElement).toBe(nonTabbableContent) + }) + }) + + it('should warn when trying to focus or blur a closed dialog', () => { + const consoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}) + let ref + render( (ref = el)} />) + + ref!.focusDialog() + expect(consoleError.mock.calls[0][0]).toBe( + "Warning: [Dialog] Can't focus a Dialog that isn't open." + ) + + ref!.blurDialog() + expect(consoleError.mock.calls[1][0]).toBe( + "Warning: [Dialog] Can't blur a Dialog that isn't open." + ) + + consoleError.mockRestore() + }) + + it('should focus the first tabbable element by default', async () => { + const { getByTestId } = render() + const inputOne = getByTestId('input-one') + + await waitFor(() => { + expect(document.activeElement).toBe(inputOne) + }) + }) + + it('should focus the first tabbable element when open prop becomes true', async () => { + const { rerender, getByTestId } = render() + const inputTrigger = getByTestId('input-trigger') + + await waitFor(() => { + expect(document.activeElement).toBe(inputTrigger) + }) + + rerender() + const inputOne = getByTestId('input-one') + + await waitFor(() => { + expect(document.activeElement).toBe(inputOne) + }) + }) + + it('should take a prop for finding default focus', async () => { + const { getByTestId } = render( + getByTestId('input-two')} + /> + ) + const inputTwo = getByTestId('input-two') + + await waitFor(() => { + expect(document.activeElement).toBe(inputTwo) + }) + }) + + it('should still focus the defaultFocusElement when it is focusable but not tabbable', async () => { + const { getByTestId } = render( + getByTestId('non-tabbable')} + > +
+ {TEST_TEXT} +
+
+ ) + const nonTabbableContent = getByTestId('non-tabbable') + + await waitFor(() => { + expect(document.activeElement).toBe(nonTabbableContent) + }) + }) + + it('should focus the contentElement by default if focusable and no defaultFocusElement is provided', async () => { + const { getByTestId } = render( +
+ getByTestId('non-tabbable')} + > + {TEST_TEXT} + +
+ {TEST_TEXT} +
+
+ ) + const nonTabbableContent = getByTestId('non-tabbable') + + await waitFor(() => { + expect(document.activeElement).toBe(nonTabbableContent) + }) + }) + + it('should focus the document body if there is no defaultFocusElement, tabbable elements, or focusable contentElement', async () => { + const { rerender, getByTestId } = render( + {TEST_TEXT} + ) + const inputTrigger = getByTestId('input-trigger') + inputTrigger.focus() + + rerender({TEST_TEXT}) + + await waitFor(() => { + expect(document.activeElement).toBe(document.body) + }) + }) + + it('should return focus', async () => { + const { rerender, getByTestId } = render() + expect(document.activeElement).toBe(getByTestId('input-trigger')) + + rerender() + await waitFor(() => { + expect(document.activeElement).toBe(getByTestId('input-one')) + }) + + rerender() + await waitFor(() => { + expect(document.activeElement).toBe(getByTestId('input-trigger')) + }) + }) + + describe('when focus leaves the first and last tabbable', () => { + it(`should NOT call onBlur when shouldContainFocus=true and tab pressing last tabbable`, async () => { + const onBlur = jest.fn() + const { getByTestId } = render( + getByTestId('input-two')} + /> + ) + const inputOne = getByTestId('input-one') + const inputTwo = getByTestId('input-two') + + await waitFor(() => { + expect(document.activeElement).toBe(inputTwo) + }) + + await waitFor(() => { + userEvent.tab() + expect(onBlur).not.toHaveBeenCalled() + expect(document.activeElement).toBe(inputOne) + }) + }) + + it('should NOT call onBlur when shouldContainFocus=true and Shift+Tab pressing first tabbable', async () => { + const onBlur = jest.fn() + + const { getByTestId } = render( + getByTestId('input-one')} + onBlur={onBlur} + /> + ) + const inputOne = getByTestId('input-one') + const inputTwo = getByTestId('input-two') + + await waitFor(() => { + expect(document.activeElement).toBe(inputOne) + + fireEvent.keyDown(inputOne, { + key: 'Tab', + code: 'Tab', + keyCode: 9, + charCode: 9, + shiftKey: true + }) + + expect(onBlur).not.toHaveBeenCalled() + expect(document.activeElement).toBe(inputTwo) + }) + }) + + it('should call onBlur when shouldContainFocus=false and tab pressing last tabbable', async () => { + const onBlur = jest.fn() + + const { getByTestId } = render( + getByTestId('input-two')} + onBlur={onBlur} + /> + ) + const inputTwo = getByTestId('input-two') + inputTwo.focus() + + await waitFor(() => { + expect(document.activeElement).toBe(inputTwo) + + fireEvent.keyDown(inputTwo, { + key: 'Tab', + code: 'Tab', + keyCode: 9, + charCode: 9 + }) + expect(onBlur).toHaveBeenCalled() + }) + }) + + it('should call onBlur when shouldContainFocus=false and pressing Shift+Tab on the first tabbable', async () => { + const onBlur = jest.fn() + + const { getByTestId } = render( + getByTestId('input-one')} + onBlur={onBlur} + /> + ) + const inputOne = getByTestId('input-one') + inputOne.focus() + + await waitFor(() => { + expect(document.activeElement).toBe(inputOne) + + fireEvent.keyDown(inputOne, { + key: 'Tab', + code: 'Tab', + keyCode: 9, + charCode: 9, + shiftKey: true + }) + expect(onBlur).toHaveBeenCalled() + }) + }) + + describe('when launching a dialog w/out focusable content from another dialog', () => { + it(`should contain focus when last tabbable element triggers dialog w/out focusable content`, async () => { + const onBlur = jest.fn() + + const { getByTestId } = render( + getByTestId('nested-input-two')} + /> + ) + const inputOne = getByTestId('nested-input-one') + const inputTwo = getByTestId('nested-input-two') + + await waitFor(() => { + userEvent.click(inputTwo) + expect(document.activeElement).toBe(inputTwo) + + fireEvent.keyDown(inputTwo, { + key: 'Tab', + code: 'Tab', + keyCode: 9, + charCode: 9 + }) + expect(onBlur).not.toHaveBeenCalled() + expect(document.activeElement).toBe(inputOne) + }) + }) + + it('should contain focus when first tabbable element triggers dialog w/out focusable content', async () => { + const onBlur = jest.fn() + + const { getByTestId } = render( + getByTestId('nested-input-one')} + /> + ) + const inputOne = getByTestId('nested-input-one') + const inputTwo = getByTestId('nested-input-two') + + await waitFor(() => { + userEvent.click(inputOne) + expect(document.activeElement).toBe(inputOne) + + fireEvent.keyDown(inputOne, { + key: 'Tab', + code: 'Tab', + keyCode: 9, + charCode: 9, + shiftKey: true + }) + expect(onBlur).not.toHaveBeenCalled() + expect(document.activeElement).toBe(inputTwo) + }) + }) + + it(`should call onBlur when shouldContainFocus=false and last tabbable element triggers dialog w/out focusable content`, async () => { + const onBlur = jest.fn() + + const { getByTestId } = render( + getByTestId('nested-input-two')} + /> + ) + const inputTwo = getByTestId('nested-input-two') + + await waitFor(() => { + userEvent.click(inputTwo) + expect(document.activeElement).toBe(inputTwo) + + fireEvent.keyDown(inputTwo, { + key: 'Tab', + code: 'Tab', + keyCode: 9, + charCode: 9 + }) + expect(onBlur).toHaveBeenCalled() + }) + }) + + it(`should call onBlur when shouldContainFocus=false and first tabbable element triggers dialog w/out focusable content`, async () => { + const onBlur = jest.fn() + + const { getByTestId } = render( + getByTestId('nested-input-one')} + /> + ) + const inputOne = getByTestId('nested-input-one') + + await waitFor(() => { + userEvent.click(inputOne) + expect(document.activeElement).toBe(inputOne) + + fireEvent.keyDown(inputOne, { + key: 'Tab', + code: 'Tab', + keyCode: 9, + charCode: 9, + shiftKey: true + }) + expect(onBlur).toHaveBeenCalled() + }) + }) + }) + }) + }) +}) diff --git a/packages/ui-dialog/src/Dialog/__tests__/Dialog.test.tsx b/packages/ui-dialog/src/Dialog/__tests__/Dialog.test.tsx deleted file mode 100644 index c1584bfdc6..0000000000 --- a/packages/ui-dialog/src/Dialog/__tests__/Dialog.test.tsx +++ /dev/null @@ -1,637 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2015 - present Instructure, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { - expect, - mount, - stub, - wait, - within, - find -} from '@instructure/ui-test-utils' -import { Dialog } from '../index' -import type { DialogProps } from '../props' - -describe('', async () => { - it('should render nothing when closed', async () => { - const subject = await mount( - - - - ) - - expect(subject.getDOMNode()).to.not.exist() - }) - - it('should render children when open', async () => { - const subject = await mount( - - - - ) - - const dialog = within(subject.getDOMNode()) - expect(await dialog.find(':focusable')).to.exist() - }) - - it('should apply the a11y attributes', async () => { - const subject = await mount( - - - - ) - - const dialog = within(subject.getDOMNode()) - - expect(await dialog.find('[role="dialog"]')).to.exist() - expect(await dialog.find('[aria-label="Dialog Example"]')).to.exist() - }) - - it('should apply the role attributes, if explicitly passed', async () => { - const subject = await mount( - - - - ) - - const dialog = within(subject.getDOMNode()) - - expect(await dialog.find('[role="region"]')).to.exist() - expect(await dialog.find('[aria-label="Dialog Example"]')).to.exist() - }) - - it('should call onDismiss prop when Esc key pressed', async () => { - const onDismiss = stub() - - const subject = await mount( - - - - ) - - const dialog = within(subject.getDOMNode()) - - await wait(() => { - expect(dialog.containsFocus()).to.be.true() - }) - - await dialog.keyUp('escape') - - await wait(() => { - expect(onDismiss).to.have.been.called() - }) - }) - - it('should call onDismiss prop when the document is clicked', async () => { - const onDismiss = stub() - - const subject = await mount( - - - - ) - - const dialog = within(subject.getDOMNode()) - - await wait(() => { - expect(dialog.containsFocus()).to.be.true() - }) - - await within(dialog.getOwnerDocument().documentElement).click() - - await wait(() => { - expect(onDismiss).to.have.been.called() - }) - }) - - describe('managed focus', async () => { - class DialogExample extends React.Component { - static propTypes = { - // eslint-disable-next-line react/forbid-foreign-prop-types - ...Dialog.propTypes - } - - private _dialog?: Dialog | null - private _input?: HTMLInputElement | null - - componentDidMount() { - if (!this.props.open) { - this._input!.focus() - } - } - - focusDialog() { - this._dialog && this._dialog.focus() - } - - blurDialog() { - this._dialog && this._dialog.blur() - } - - render() { - return ( -
- { - this._input = c - }} - /> - (this._dialog = el)} - > - {this.props.children || ( -
- - -
- )} -
-
- ) - } - } - - it('should provide focus method', async () => { - let ref: DialogExample | null - await mount( -
- document.getElementById('container')} - ref={(el) => (ref = el)} - > - some content - -
- some more content -
-
- ) - ref!.focusDialog() - const container = await find('#container') - await wait(() => { - expect(container.focused()).to.be.true() - }) - }) - - it('should warn when trying to focus or blur a closed dialog', async () => { - const consoleError = stub(console, 'error') - let ref: DialogExample | null - - await mount( -
- document.getElementById('container')} - ref={(el) => (ref = el)} - > - some content - -
- some more content -
-
- ) - ref!.focusDialog() - await wait(() => { - expect(consoleError).to.have.been.calledWithMatch( - "[Dialog] Can't focus a Dialog that isn't open." - ) - }) - ref!.blurDialog() - await wait(() => { - expect(consoleError).to.have.been.calledWithMatch( - "[Dialog] Can't blur a Dialog that isn't open." - ) - }) - }) - - it('should focus the first tabbable element by default', async () => { - await mount() - const input = await find('#input-one') - await wait(() => { - expect(input.focused()).to.be.true() - }) - }) - - it('should focus the first tabbable element when open prop becomes true', async () => { - const subject = await mount() - - await subject.setProps({ open: true }) - - const input = await find('#input-one') - - await wait(() => { - expect(input.focused()).to.be.true() - }) - }) - - it('should take a prop for finding default focus', async () => { - await mount( - { - return document.getElementById('input-two') - }} - /> - ) - - const input = await find('#input-two') - - await wait(() => { - expect(input.focused()).to.be.true() - }) - }) - - it('should still focus the defaultFocusElement when it is focusable but not tabbable', async () => { - await mount( - document.getElementById('non-tabbable')} - > -
- hello world -
-
- ) - - const content = await find('#non-tabbable') - await wait(() => { - expect(content.focused()).to.be.true() - }) - }) - - it('should focus the contentElement by default if focusable and no defaultFocusElement is provided', async () => { - await mount( -
- document.getElementById('container')} - > - some content - -
- some more content -
-
- ) - - const container = await find('#container') - await wait(() => { - expect(container.focused()).to.be.true() - }) - }) - - it('should focus the document body if there is no defaultFocusElement, tabbable elements, or focusable contentElement', async () => { - const subject = await mount( - hello world - ) - - const input = await find('#input-trigger') - await input.focus() - - await subject.setProps({ open: true }) - - const body = await find('body') - - await wait(() => { - expect(body.focused()).to.be.true() - }) - }) - - it('should return focus', async () => { - const subject = await mount() - - const trigger = await find('#input-trigger') - - expect(trigger.focused()).to.be.true() - - await subject.setProps({ open: true }) - - const input = await find('#input-one') - - await wait(() => { - expect(input.focused()).to.be.true() - }) - - await subject.setProps({ open: false }) - - await wait(() => { - expect(trigger.focused()).to.be.true() - }) - }) - - describe('when focus leaves the first and last tabbable', async () => { - it(`should NOT call onBlur when shouldContainFocus=true and tab pressing last tabbable`, async () => { - const onBlur = stub() - - const subject = await mount( - { - return document.getElementById('input-two') - }} - onBlur={onBlur} - /> - ) - const main = within(subject.getDOMNode()) - const inputOne = await main.find('[id=input-one]') - const inputTwo = await main.find('[id=input-two]') - - await wait(() => { - expect(inputTwo.focused()).to.be.true() - }) - - await inputTwo.keyDown('tab') - - await wait(() => { - expect(onBlur).to.not.have.been.called() - expect(inputOne.focused()).to.be.true() - }) - }) - - it(`should NOT call onBlur when shouldContainFocus=true and tab pressing first tabbable`, async () => { - const onBlur = stub() - - const subject = await mount( - { - return document.getElementById('input-one') - }} - onBlur={onBlur} - /> - ) - const main = within(subject.getDOMNode()) - const inputOne = await main.find('[id=input-one]') - const inputTwo = await main.find('[id=input-two]') - - await wait(() => { - expect(inputOne.focused()).to.be.true() - }) - - await inputOne.keyDown('tab', { - shiftKey: true - }) - - await wait(() => { - expect(onBlur).to.not.have.been.called() - expect(inputTwo.focused()).to.be.true() - }) - }) - - it(`should call onBlur when shouldContainFocus=false and tab pressing last tabbable`, async () => { - const onBlur = stub() - - const subject = await mount( - { - return document.getElementById('input-two') - }} - onBlur={onBlur} - /> - ) - const main = within(subject.getDOMNode()) - const inputTwo = await main.find('[id=input-two]') - - await inputTwo.focus() - - await wait(() => { - expect(inputTwo.focused()).to.be.true() - }) - - await inputTwo.keyDown('tab') - - await wait(() => { - expect(onBlur).to.have.been.called() - }) - }) - - it(`should call onBlur when shouldContainFocus=false and tab pressing first tabbable`, async () => { - const onBlur = stub() - - const subject = await mount( - { - return document.getElementById('input-one') - }} - onBlur={onBlur} - /> - ) - const main = within(subject.getDOMNode()) - const inputOne = await main.find('[id=input-one]') - - await inputOne.focus() - - await wait(() => { - expect(inputOne.focused()).to.be.true() - }) - - await inputOne.keyDown('tab', { - shiftKey: true - }) - - await wait(() => { - expect(onBlur).to.have.been.called() - }) - }) - - describe('when launching a dialog w/out focusable content from another dialog', () => { - class NestedDialogExample extends React.Component { - static propTypes = { - // eslint-disable-next-line react/forbid-foreign-prop-types - ...Dialog.propTypes - } - - state = { - open: false - } - - handleTriggerClick = () => { - this.setState({ open: true }) - } - - render() { - return ( -
- -
-
- - -
- - Hello world - -
-
-
- ) - } - } - - it(`should contain focus when last tabbable element triggers dialog w/out focusable content`, async () => { - const onBlur = stub() - - const subject = await mount( - document.getElementById('input-two')} - /> - ) - - const main = within(subject.getDOMNode()) - const inputOne = await main.find('input#input-one') - const inputTwo = await main.find('input#input-two') - - await inputTwo.click() - - // Need to wait here to give new region time to activate - await wait(() => { - expect(inputTwo.focused()).to.be.true() - }) - - await inputTwo.keyDown('tab') - - await wait(() => { - expect(onBlur).to.not.have.been.called() - expect(inputOne.focused()).to.be.true() - }) - }) - - it(`should contain focus when first tabbable element triggers dialog w/out focusable content`, async () => { - const onBlur = stub() - - const subject = await mount( - document.getElementById('input-one')} - /> - ) - - const main = within(subject.getDOMNode()) - const inputOne = await main.find('input#input-one') - const inputTwo = await main.find('input#input-two') - - await inputOne.click() - - // Need to wait here to give new region time to activate - await wait(() => { - expect(inputOne.focused()).to.be.true() - }) - - await inputOne.keyDown('tab', { - shiftKey: true - }) - - await wait(() => { - expect(onBlur).to.not.have.been.called() - expect(inputTwo.focused()).to.be.true() - }) - }) - - it(`should call onBlur when shouldContainFocus=false and last tabbable element triggers dialog w/out focusable content`, async () => { - const onBlur = stub() - - const subject = await mount( - document.getElementById('input-two')} - /> - ) - - const main = within(subject.getDOMNode()) - const inputTwo = await main.find('input#input-two') - - await inputTwo.click() - - // Need to wait here to give new region time to activate - await wait(() => { - expect(inputTwo.focused()).to.be.true() - }) - - await inputTwo.keyDown('tab') - - await wait(() => { - expect(onBlur).to.have.been.called() - }) - }) - - it(`should call onBlur when shouldContainFocus=false and first tabbable element triggers dialog w/out focusable content`, async () => { - const onBlur = stub() - - const subject = await mount( - document.getElementById('input-one')} - /> - ) - - const main = within(subject.getDOMNode()) - const inputOne = await main.find('input#input-one') - - await inputOne.click() - - // Need to wait here to give new region time to activate - await wait(() => { - expect(inputOne.focused()).to.be.true() - }) - - await inputOne.keyDown('tab', { - shiftKey: true - }) - - await wait(() => { - expect(onBlur).to.have.been.called() - }) - }) - }) - }) - }) -}) diff --git a/packages/ui-dialog/tsconfig.build.json b/packages/ui-dialog/tsconfig.build.json index 3cf2ece505..a07a684874 100644 --- a/packages/ui-dialog/tsconfig.build.json +++ b/packages/ui-dialog/tsconfig.build.json @@ -14,7 +14,6 @@ { "path": "../ui-testable/tsconfig.build.json" }, { "path": "../console/tsconfig.build.json" }, { "path": "../ui-babel-preset/tsconfig.build.json" }, - { "path": "../ui-prop-types/tsconfig.build.json" }, - { "path": "../ui-test-utils/tsconfig.build.json" } + { "path": "../ui-prop-types/tsconfig.build.json" } ] }