UNPKG

reactstrap

Version:
1,119 lines (952 loc) 30 kB
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ import React from 'react'; import { render, screen } from '@testing-library/react'; import user from '@testing-library/user-event'; import '@testing-library/jest-dom'; import { Modal, ModalBody, ModalHeader, ModalFooter, Button } from '..'; describe('Modal', () => { const toggle = () => {}; beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.clearAllTimers(); // rtl doesn't clear attributes added to body, so manually clearing them document.body.removeAttribute('style'); document.body.removeAttribute('class'); }); it('should render modal portal into DOM', () => { render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect(screen.getByText(/yo/i)).toBeInTheDocument(); }); it('should render with the class "modal-dialog"', () => { render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect(screen.getByText(/yo/i).parentElement).toHaveClass('modal-dialog'); }); it('should render external content when present', () => { render( <Modal isOpen toggle={toggle} external={<button className="cool-close-button">crazy button</button>} > Yo! </Modal>, ); expect(screen.getByText(/crazy button/i)).toBeInTheDocument(); expect( screen .getByText(/crazy button/i) .nextElementSibling.className.split(' ') .indexOf('modal-dialog') > -1, ).toBe(true); }); it('should render with the backdrop with the class "modal-backdrop" by default', () => { render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-backdrop').length).toBe(1); }); it('should render with the backdrop with the class "modal-backdrop" when backdrop is "static"', () => { render( <Modal isOpen toggle={toggle} backdrop="static"> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-backdrop').length).toBe(1); }); it('should not render with the backdrop with the class "modal-backdrop" when backdrop is "false"', () => { render( <Modal isOpen toggle={toggle} backdrop={false}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect(document.getElementsByClassName('modal-backdrop').length).toBe(0); }); it('should render with the class "modal-dialog-scrollable" when scrollable is "true"', () => { render( <Modal isOpen toggle={toggle} scrollable> Yo! </Modal>, ); expect( document.getElementsByClassName('modal-dialog-scrollable').length, ).toBe(1); }); it('should render with class "modal-dialog" and have custom class name if provided', () => { render( <Modal isOpen toggle={toggle} className="my-custom-modal"> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect(document.getElementsByClassName('my-custom-modal').length).toBe(1); }); it('should render with class "modal-dialog" w/o centered class if not provided', () => { render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); jest.advanceTimersByTime(300); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect( document.getElementsByClassName('modal-dialog-centered').length, ).toBe(0); }); it('should render with class "modal-dialog" and centered class if provided', () => { render( <Modal isOpen toggle={toggle} centered> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect( document.getElementsByClassName('modal-dialog-centered').length, ).toBe(1); }); describe('fullscreen', () => { it('should render non fullscreen by default', () => { render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect(document.getElementsByClassName('modal-fullscreen').length).toBe( 0, ); }); it('should always render fullscreen if true', () => { render( <Modal isOpen toggle={toggle} fullscreen> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect(document.getElementsByClassName('modal-fullscreen').length).toBe( 1, ); }); it('should render fullscreen below breakpoint if breakpoint is provided', () => { render( <Modal isOpen toggle={toggle} fullscreen="lg"> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect(document.getElementsByClassName('modal-fullscreen').length).toBe( 0, ); expect( document.getElementsByClassName('modal-fullscreen-lg-down').length, ).toBe(1); }); }); it('should render with additional props if provided', () => { render( <Modal isOpen toggle={toggle} style={{ maxWidth: '95%' }}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect( document.getElementsByClassName('modal-dialog')[0].style.maxWidth, ).toBe('95%'); }); it('should render without fade transition if provided with fade={false}', () => { render( <Modal isOpen toggle={toggle} fade={false} modalClassName="fadeless-modal" > Howdy! </Modal>, ); const matchedModals = document.getElementsByClassName('fadeless-modal'); const matchedModal = matchedModals[0]; expect(matchedModals.length).toBe(1); // Modal should not have the 'fade' class expect(matchedModal.className.split(' ').indexOf('fade') < 0).toBe(true); }); it('should render when expected when passed modalTransition and backdropTransition props', () => { render( <Modal isOpen toggle={toggle} modalTransition={{ timeout: 2 }} backdropTransition={{ timeout: 10 }} modalClassName="custom-timeout-modal" > Hello, world! </Modal>, ); expect(document.getElementsByClassName('custom-timeout-modal').length).toBe( 1, ); }); it('should render with class "modal" and have custom class name if provided with modalClassName', () => { render( <Modal isOpen toggle={toggle} modalClassName="my-custom-modal"> Yo! </Modal>, ); expect(document.querySelectorAll('.modal.my-custom-modal').length).toBe(1); }); it('should render with custom class name if provided with wrapClassName', () => { render( <Modal isOpen toggle={toggle} wrapClassName="my-custom-modal"> Yo! </Modal>, ); expect(document.getElementsByClassName('my-custom-modal').length).toBe(1); }); it('should render with class "modal-content" and have custom class name if provided with contentClassName', () => { render( <Modal isOpen toggle={toggle} contentClassName="my-custom-modal"> Yo! </Modal>, ); expect( document.querySelectorAll('.modal-content.my-custom-modal').length, ).toBe(1); }); it('should render with class "modal-backdrop" and have custom class name if provided with backdropClassName', () => { render( <Modal isOpen toggle={toggle} backdropClassName="my-custom-modal"> Yo! </Modal>, ); expect( document.querySelectorAll('.modal-backdrop.my-custom-modal').length, ).toBe(1); }); it('should render with the class "modal-${size}" when size is passed', () => { render( <Modal isOpen toggle={toggle} size="crazy"> Yo! </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(1); expect(document.getElementsByClassName('modal-crazy').length).toBe(1); }); it('should render modal when isOpen is true', () => { render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal').length).toBe(1); expect(document.getElementsByClassName('modal-backdrop').length).toBe(1); }); it('should render modal with default role of "dialog"', () => { render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect( document.getElementsByClassName('modal')[0].getAttribute('role'), ).toBe('dialog'); }); it('should render modal with provided role', () => { render( <Modal isOpen toggle={toggle} role="alert"> Yo! </Modal>, ); expect( document.getElementsByClassName('modal')[0].getAttribute('role'), ).toBe('alert'); }); it('should render modal with aria-labelledby provided labelledBy', () => { render( <Modal isOpen toggle={toggle} labelledBy="myModalTitle"> Yo! </Modal>, ); expect( document .getElementsByClassName('modal')[0] .getAttribute('aria-labelledby'), ).toBe('myModalTitle'); }); it('should not render modal when isOpen is false', () => { render( <Modal isOpen={false} toggle={toggle}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal').length).toBe(0); expect(document.getElementsByClassName('modal-backdrop').length).toBe(0); }); it('should toggle modal', () => { const { rerender } = render( <Modal isOpen={false} toggle={toggle}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal').length).toBe(0); expect(document.getElementsByClassName('modal-backdrop').length).toBe(0); rerender( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect(document.getElementsByClassName('modal').length).toBe(1); expect(document.getElementsByClassName('modal-backdrop').length).toBe(1); }); it('should call onClosed & onOpened', () => { const onOpened = jest.fn(); const onClosed = jest.fn(); const { rerender } = render( <Modal isOpen={false} onOpened={onOpened} onClosed={onClosed} toggle={toggle} > Yo! </Modal>, ); expect(onOpened).not.toHaveBeenCalled(); expect(onClosed).not.toHaveBeenCalled(); rerender( <Modal isOpen onOpened={onOpened} onClosed={onClosed} toggle={toggle}> Yo! </Modal>, ); jest.advanceTimersByTime(300); expect(onOpened).toHaveBeenCalledTimes(1); rerender( <Modal isOpen={false} onOpened={onOpened} onClosed={onClosed} toggle={toggle} > Yo! </Modal>, ); jest.advanceTimersByTime(300); expect(onOpened).toHaveBeenCalledTimes(1); expect(onClosed).toHaveBeenCalledTimes(1); }); it('should call onClosed & onOpened when fade={false}', () => { const onOpened = jest.fn(); const onClosed = jest.fn(); const { rerender } = render( <Modal isOpen={false} onOpened={onOpened} onClosed={onClosed} toggle={toggle} fade={false} > Yo! </Modal>, ); expect(onOpened).not.toHaveBeenCalled(); expect(onClosed).not.toHaveBeenCalled(); rerender( <Modal isOpen onOpened={onOpened} onClosed={onClosed} toggle={toggle} fade={false} > Yo! </Modal>, ); jest.advanceTimersByTime(1); expect(onOpened).toHaveBeenCalledTimes(1); expect(onClosed).not.toHaveBeenCalled(); rerender( <Modal isOpen={false} onOpened={onOpened} onClosed={onClosed} toggle={toggle} fade={false} > Yo! </Modal>, ); jest.advanceTimersByTime(1); expect(onClosed).toHaveBeenCalledTimes(1); expect(onOpened).toHaveBeenCalledTimes(1); }); it('should call toggle when escape key pressed and not for enter key', () => { const toggle = jest.fn(); render( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); user.keyboard('{enter}'); expect(toggle).not.toHaveBeenCalled(); user.keyboard('{esc}'); expect(toggle).toHaveBeenCalled(); }); it('should not call toggle when escape key pressed when keyboard is false', () => { const toggle = jest.fn(); render( <Modal isOpen toggle={toggle} keyboard={false}> Yo! </Modal>, ); user.keyboard('{esc}'); expect(toggle).not.toHaveBeenCalled(); }); it('should call toggle when clicking backdrop', () => { const toggle = jest.fn(); render( <Modal isOpen toggle={toggle}> <button id="clicker">Does Nothing</button> </Modal>, ); user.click(screen.getByText(/does nothing/i)); expect(toggle).not.toHaveBeenCalled(); user.click(document.body.getElementsByClassName('modal')[0]); expect(toggle).toHaveBeenCalled(); }); it('should not call toggle when clicking backdrop and backdrop is "static"', () => { const toggle = jest.fn(); render( <Modal isOpen toggle={toggle} backdrop="static"> <button id="clicker">Does Nothing</button> </Modal>, ); user.click(document.getElementsByClassName('modal-backdrop')[0]); expect(toggle).not.toHaveBeenCalled(); }); it('should not call toggle when escape key pressed and backdrop is "static" and keyboard=false', () => { const toggle = jest.fn(); const { debug } = render( <Modal isOpen toggle={toggle} backdrop="static" keyboard={false}> Yo! </Modal>, ); user.keyboard('{esc}'); expect(toggle).not.toHaveBeenCalled(); }); it('should call toggle when escape key pressed and backdrop is "static" and keyboard=true', () => { const toggle = jest.fn(); render( <Modal isOpen toggle={toggle} backdrop="static" keyboard> <button id="clicker">Does Nothing</button> </Modal>, ); user.keyboard('{esc}'); expect(toggle).toHaveBeenCalled(); }); it('should animate when backdrop is "static" and escape key pressed and keyboard=false', () => { render( <Modal isOpen toggle={toggle} backdrop="static" keyboard={false} data-testid="mandalorian" > <button id="clicker">Does Nothing</button> </Modal>, ); user.keyboard('{esc}'); expect(screen.getByTestId('mandalorian').parentElement).toHaveClass( 'modal-static', ); jest.advanceTimersByTime(300); expect(screen.getByTestId('mandalorian').parentElement).not.toHaveClass( 'modal-static', ); }); it('should animate when backdrop is "static" and backdrop is clicked', () => { render( <Modal isOpen toggle={toggle} backdrop="static" data-testid="mandalorian"> <button id="clicker">Does Nothing</button> </Modal>, ); user.click(document.getElementsByClassName('modal')[0]); expect(screen.getByTestId('mandalorian').parentElement).toHaveClass( 'modal-static', ); jest.advanceTimersByTime(300); expect(screen.getByTestId('mandalorian').parentElement).not.toHaveClass( 'modal-static', ); }); it('should not animate when backdrop is "static" and modal is clicked', () => { render( <Modal isOpen toggle={toggle} backdrop="static" data-testid="mandalorian"> <button id="clicker">Does Nothing</button> </Modal>, ); user.click(document.getElementsByClassName('modal-dialog')[0]); expect(screen.getByTestId('mandalorian').parentElement).not.toHaveClass( 'modal-static', ); }); it('should destroy this._element', () => { const { rerender, debug } = render( <Modal isOpen toggle={toggle} wrapClassName="weird-class"> <button id="clicker">Does Nothing</button> </Modal>, ); const element = document.getElementsByClassName('weird-class')[0].parentElement; expect(element).toBeInTheDocument(); rerender( <Modal isOpen={false} toggle={toggle} wrapClassName="weird-class"> <button id="clicker">Does Nothing</button> </Modal>, ); jest.advanceTimersByTime(300); expect(element).not.toBeInTheDocument(); }); it('should destroy this._element when unmountOnClose prop set to true', () => { const { rerender, debug } = render( <Modal isOpen toggle={toggle} unmountOnClose wrapClassName="weird-class"> <button id="clicker">Does Nothing</button> </Modal>, ); const element = document.getElementsByClassName('weird-class')[0].parentElement; expect(element).toBeInTheDocument(); rerender( <Modal isOpen={false} toggle={toggle} unmountOnClose wrapClassName="weird-class" > <button id="clicker">Does Nothing</button> </Modal>, ); jest.advanceTimersByTime(300); expect(element).not.toBeInTheDocument(); }); it('should not destroy this._element when unmountOnClose prop set to false', () => { const { rerender } = render( <Modal isOpen toggle={toggle} unmountOnClose={false} wrapClassName="weird-class" > <button id="clicker">Does Nothing</button> </Modal>, ); const element = document.getElementsByClassName('weird-class')[0].parentElement; expect(element).toBeInTheDocument(); rerender( <Modal isOpen={false} toggle={toggle} unmountOnClose={false} wrapClassName="weird-class" > <button id="clicker">Does Nothing</button> </Modal>, ); expect(element).toBeInTheDocument(); }); it('should destroy this._element on unmount', () => { const { unmount } = render( <Modal isOpen toggle={toggle} wrapClassName="weird-class"> <button id="clicker">Does Nothing</button> </Modal>, ); unmount(); jest.advanceTimersByTime(300); expect(document.getElementsByClassName('modal').length).toBe(0); }); it('should render nested modals', () => { const { unmount } = render( <Modal isOpen toggle={toggle}> <ModalBody> <Modal isOpen toggle={() => {}}> Yo! </Modal> </ModalBody> </Modal>, ); expect(document.getElementsByClassName('modal-dialog').length).toBe(2); expect(document.body.className).toBe('modal-open'); unmount(); expect(document.getElementsByClassName('modal-dialog').length).toBe(0); }); it('should remove exactly modal-open class from body', () => { // set a body class which includes modal-open document.body.className = 'my-modal-opened'; const { rerender } = render( <Modal isOpen={false} toggle={toggle}> Yo! </Modal>, ); expect(document.body.className).toBe('my-modal-opened'); rerender( <Modal isOpen toggle={toggle}> Yo! </Modal>, ); expect(document.body.className).toBe('my-modal-opened modal-open'); // using this to test if replace will leave a space when removing modal-open document.body.className += ' modal-opened'; expect(document.body.className).toBe( 'my-modal-opened modal-open modal-opened', ); rerender( <Modal isOpen={false} toggle={toggle}> Yo! </Modal>, ); jest.advanceTimersByTime(300); expect(document.body.className).toBe('my-modal-opened modal-opened'); }); it('should call onEnter & onExit props if provided', () => { const onEnter = jest.fn(); const onExit = jest.fn(); const { rerender, unmount } = render( <Modal isOpen={false} onEnter={onEnter} onExit={onExit} toggle={toggle}> Yo! </Modal>, ); expect(onEnter).toHaveBeenCalled(); expect(onExit).not.toHaveBeenCalled(); onEnter.mockReset(); onExit.mockReset(); rerender( <Modal isOpen onEnter={onEnter} onExit={onExit} toggle={toggle}> Yo! </Modal>, ); expect(onEnter).not.toHaveBeenCalled(); expect(onExit).not.toHaveBeenCalled(); onEnter.mockReset(); onExit.mockReset(); rerender( <Modal isOpen={false} onEnter={onEnter} onExit={onExit} toggle={toggle}> Yo! </Modal>, ); unmount(); expect(onEnter).not.toHaveBeenCalled(); expect(onExit).toHaveBeenCalled(); }); it('should update element z index when prop changes', () => { const { debug, rerender } = render( <Modal isOpen zIndex={0} wrapClassName="sandman"> Yo! </Modal>, ); expect( document.getElementsByClassName('sandman')[0].parentElement.style.zIndex, ).toBe('0'); rerender( <Modal isOpen zIndex={1} wrapClassName="sandman"> Yo! </Modal>, ); expect( document.getElementsByClassName('sandman')[0].parentElement.style.zIndex, ).toBe('1'); }); it('should allow focus on only focusable elements and tab through them', () => { render( <Modal isOpen toggle={toggle}> <ModalHeader toggle={toggle}>Modal title</ModalHeader> <ModalBody> <a alt="test" href="/"> First Test </a> <map name="test"> <area alt="test" href="/" coords="200,5,200,30" /> </map> <input type="text" aria-label="test text input" /> <input type="hidden" /> <input type="text" disabled value="Test" /> <select name="test" id="select_test"> <option>Second item</option> </select> <select name="test" id="select_test_disabled" disabled> <option>Third item</option> </select> <textarea name="textarea_test" id="textarea_test" cols="30" rows="10" aria-label="test text area" /> <textarea name="textarea_test_disabled" id="textarea_test_disabled" cols="30" rows="10" disabled /> <object>Test</object> <span tabIndex="0">test tab index</span> </ModalBody> <ModalFooter> <Button disabled color="primary" onClick={toggle}> Do Something </Button>{' '} <Button color="secondary" onClick={toggle}> Cancel </Button> </ModalFooter> </Modal>, ); user.tab(); expect(screen.getByLabelText(/close/i)).toHaveFocus(); user.tab(); expect(screen.getByText(/first test/i)).toHaveFocus(); user.tab(); expect(screen.getByLabelText(/test text input/i)).toHaveFocus(); user.tab(); expect(screen.getByText(/second item/i).parentElement).toHaveFocus(); user.tab(); expect(screen.getByLabelText(/test text area/i)).toHaveFocus(); user.tab(); expect(screen.getByText(/test tab index/i)).toHaveFocus(); user.tab(); expect(screen.getByText(/cancel/i)).toHaveFocus(); user.tab(); expect(screen.getByLabelText(/close/i)).toHaveFocus(); }); it('should return the focus to the last focused element before the modal has opened', () => { const { rerender } = render( <> <button className="focus">Focused</button> <Modal isOpen={false}> <ModalBody>Whatever</ModalBody> </Modal> </>, ); user.tab(); expect(screen.getByText(/focused/i)).toHaveFocus(); rerender( <> <button className="focus">Focused</button> <Modal isOpen> <ModalBody>Whatever</ModalBody> </Modal> </>, ); rerender( <> <button className="focus">Focused</button> <Modal isOpen={false}> <ModalBody>Whatever</ModalBody> </Modal> </>, ); jest.runAllTimers(); expect(screen.getByText(/focused/i)).toHaveFocus(); }); it('should not return the focus to the last focused element before the modal has opened when "returnFocusAfterClose" is false', () => { const { rerender } = render( <> <button className="focus">Focused</button> <Modal returnFocusAfterClose={false} isOpen={false}> <ModalBody>Whatever</ModalBody> </Modal> </>, ); user.tab(); expect(screen.getByText(/focused/i)).toHaveFocus(); rerender( <> <button className="focus">Focused</button> <Modal returnFocusAfterClose={false} isOpen> <ModalBody>Whatever</ModalBody> </Modal> </>, ); rerender( <> <button className="focus">Focused</button> <Modal returnFocusAfterClose={false} isOpen={false}> <ModalBody>Whatever</ModalBody> </Modal> </>, ); jest.runAllTimers(); expect(screen.getByText(/focused/i)).not.toHaveFocus(); }); it('should return the focus to the last focused element before the modal has opened when "unmountOnClose" is false', () => { const { rerender } = render( <> <button className="focus">Focused</button> <Modal unmountOnClose={false} isOpen={false}> <ModalBody>Whatever</ModalBody> </Modal> </>, ); user.tab(); expect(screen.getByText(/focused/i)).toHaveFocus(); rerender( <> <button className="focus">Focused</button> <Modal unmountOnClose={false} isOpen> <ModalBody>Whatever</ModalBody> </Modal> </>, ); rerender( <> <button className="focus">Focused</button> <Modal unmountOnClose={false} isOpen={false}> <ModalBody>Whatever</ModalBody> </Modal> </>, ); jest.runAllTimers(); expect(screen.getByText(/focused/i)).toHaveFocus(); }); it('should not return the focus to the last focused element before the modal has opened when "returnFocusAfterClose" is false and "unmountOnClose" is false', () => { const { rerender } = render( <> <button className="focus">Focused</button> <Modal unmountOnClose={false} returnFocusAfterClose={false} isOpen={false} > <ModalBody>Whatever</ModalBody> </Modal> </>, ); user.tab(); expect(screen.getByText(/focused/i)).toHaveFocus(); rerender( <> <button className="focus">Focused</button> <Modal unmountOnClose={false} returnFocusAfterClose={false} isOpen> <ModalBody>Whatever</ModalBody> </Modal> </>, ); rerender( <> <button className="focus">Focused</button> <Modal unmountOnClose={false} returnFocusAfterClose={false} isOpen={false} > <ModalBody>Whatever</ModalBody> </Modal> </>, ); jest.runAllTimers(); expect(screen.getByText(/focused/i)).not.toHaveFocus(); }); it('should attach/detach trapFocus for dialogs', () => { const addEventListener = jest.spyOn(document, 'addEventListener'); const removeEventListener = jest.spyOn(document, 'removeEventListener'); const { unmount } = render( <Modal isOpen> <ModalBody> <Button className="focus">focusable element</Button> </ModalBody> </Modal>, ); expect(addEventListener).toHaveBeenCalledTimes(1); expect(addEventListener).toHaveBeenCalledWith( 'focus', expect.any(Function), true, ); unmount(); expect(removeEventListener).toHaveBeenCalledTimes(1); expect(removeEventListener).toHaveBeenCalledWith( 'focus', expect.any(Function), true, ); addEventListener.mockRestore(); removeEventListener.mockRestore(); }); it('should trap focus inside the open dialog', () => { const { rerender } = render( <> <Button className="first">Focused</Button> <Modal isOpen={false} trapFocus> <ModalBody> Something else to see <Button className="focus">focusable element</Button> </ModalBody> </Modal> </>, ); screen.getByText(/focused/i).focus(); expect(screen.getByText(/focused/i)).toHaveFocus(); rerender( <> <Button className="first">Focused</Button> <Modal isOpen trapFocus data-testid="modal"> <ModalBody> Something else to see <Button className="focus">focusable element</Button> </ModalBody> </Modal> </>, ); jest.runAllTimers(); expect(screen.getByText(/focused/i)).not.toHaveFocus(); expect(screen.getByTestId('modal').parentElement).toHaveFocus(); // pressing tab shouldn't move focus outside the modal user.tab(); expect(screen.getByText(/focusable element/i)).toHaveFocus(); user.tab(); expect(screen.getByText(/focusable element/i)).toHaveFocus(); }); it('tab should focus on inside modal children for nested modal', () => { render( <Modal isOpen toggle={toggle}> <ModalBody> <Button className="b0" onClick={toggle}> Cancel </Button> <Modal isOpen> <ModalBody> <Button className="b1">Click 1</Button> </ModalBody> </Modal> </ModalBody> </Modal>, ); user.tab(); expect(screen.getByText(/click 1/i)).toHaveFocus(); // pressing tab doesn't take focus out of inside modal user.tab(); expect(screen.getByText(/click 1/i)).toHaveFocus(); }); it('works with strict mode', () => { const spy = jest.spyOn(console, 'error'); render( <React.StrictMode> <Modal isOpen>Hello</Modal> </React.StrictMode>, ); expect(spy).not.toHaveBeenCalled(); }); });