UNPKG

reactstrap

Version:
1,195 lines (1,012 loc) 36.8 kB
import React from 'react'; import { createEvent, fireEvent, render, screen } from '@testing-library/react'; import user from '@testing-library/user-event'; import '@testing-library/jest-dom'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from '..'; import { keyCodes } from '../utils'; import { testForChildrenInComponent } from '../testUtils'; describe('Dropdown', () => { beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.restoreAllMocks(); jest.runOnlyPendingTimers(); jest.useRealTimers(); }); function imitateDropdownFocus(toggle) { // this is needed to make the focus on the correct element // by following the default user behaviour // needed in particular for keyboard based tests // setting focus on the toggle element with code // is causing tab to cycle through elements. const { rerender } = render( <Dropdown isOpen={false} toggle={toggle} data-testid="drpdwn"> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>First item</DropdownItem> <DropdownItem>Second item</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); user.click(screen.getByText('Toggle')); toggle.mockClear(); rerender( <Dropdown isOpen toggle={toggle} data-testid="drpdwn"> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>First item</DropdownItem> <DropdownItem>Second item</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); return { rerender }; } it('should render a single child', () => { render(<Dropdown isOpen>Ello world</Dropdown>); expect(screen.getByText(/ello world/i)).toHaveClass('dropdown'); }); it('should render menu when isOpen is true', () => { render( <Dropdown isOpen> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByText(/toggle/i)).toHaveClass('btn'); expect(screen.getByText(/test/i)).toHaveClass('dropdown-item'); }); it('should not call props.toggle when disabled ', () => { const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle} disabled> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.click(screen.getByText(/toggle/i)); expect(toggle).not.toHaveBeenCalled(); }); it('should call toggle when DropdownToggle is clicked ', () => { const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.click(screen.getByText(/toggle/i)); expect(toggle).toHaveBeenCalledTimes(1); }); it('should call toggle when DropdownToggle with non string children is clicked ', () => { const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle> <div>Toggle</div> </DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.click(screen.getByText(/toggle/i)); expect(toggle).toHaveBeenCalledTimes(1); }); describe('handleProps', () => { it('should not pass custom props to html attrs', () => { const toggle = jest.fn(); render( <Dropdown a11y isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); const dropdown = document.getElementsByClassName('dropdown')[0]; expect(dropdown).not.toHaveAttribute('inNavbar'); expect(dropdown).not.toHaveAttribute('toggle'); expect(dropdown).not.toHaveAttribute('a11y'); expect(dropdown).not.toHaveAttribute('isOpen'); }); it('should add event listeners when isOpen changed to true', () => { const addEventListener = jest.spyOn(document, 'addEventListener'); const { rerender } = render( <Dropdown isOpen={false}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(addEventListener).not.toHaveBeenCalled(); rerender( <Dropdown isOpen> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); // called three times because we have click, touchstart and keyup expect(addEventListener).toHaveBeenCalledTimes(3); }); it('should not be called on componentDidUpdate when isOpen did not change', () => { const addEventListener = jest.spyOn(document, 'addEventListener'); const { rerender } = render( <Dropdown isOpen> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(addEventListener).toHaveBeenCalled(); addEventListener.mockClear(); rerender( <Dropdown isOpen size="lg"> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(addEventListener).not.toHaveBeenCalled(); }); }); describe('removeEvents', () => { it('should remove event listeners on componentWillUnmount', () => { const removeEventListener = jest.spyOn(document, 'removeEventListener'); const { unmount } = render( <Dropdown isOpen> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); unmount(); expect(removeEventListener).toHaveBeenCalled(); }); }); describe('handleDocumentClick', () => { it('should call toggle on document click', () => { const toggle = jest.fn(() => {}); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.click(document.body); expect(toggle).toHaveBeenCalled(); }); it('should call toggle on container click', () => { const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle} data-testid="dropdown"> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.click(screen.getByTestId('dropdown')); expect(toggle).toHaveBeenCalled(); }); it('should call toggle on container click', () => { const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle} data-testid="dropdown"> <DropdownToggle> <div>Toggle</div> </DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.click(screen.getByTestId('dropdown')); expect(toggle).toHaveBeenCalled(); }); it('should not call toggle on inner container click', () => { const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); user.click(document.getElementById('divider')); expect(toggle).not.toHaveBeenCalled(); }); it('should not call toggle when right-clicked', () => { const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle} data-testid="dropdown"> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); user.click(screen.getByTestId('dropdown'), { button: 2 }); expect(toggle).not.toHaveBeenCalled(); }); it('should go through first dropdown item and close when tab is pressed multiple times', async () => { const toggle = jest.fn(); imitateDropdownFocus(toggle); user.tab(); expect(screen.getByText(/first item/i)).toHaveFocus(); user.tab(); expect(toggle).toHaveBeenCalledTimes(1); }); }); describe('keyboard events', () => { it('should call toggle on ESC keydown when it isOpen is true', () => { const toggle = jest.fn(); imitateDropdownFocus(toggle); user.keyboard('{esc}'); expect(toggle).toHaveBeenCalledTimes(1); }); it('should call toggle on down arrow keydown when it isOpen is false', () => { const toggle = jest.fn(); render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); user.tab(); expect(screen.getByText('Toggle')).toHaveFocus(); user.keyboard('{arrowdown}'); expect(toggle).toHaveBeenCalledTimes(1); }); it('should call toggle on up arrow keydown when it isOpen is false', () => { const toggle = jest.fn(); render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); user.tab(); expect(screen.getByText('Toggle')).toHaveFocus(); user.keyboard('{arrowup}'); expect(toggle).toHaveBeenCalledTimes(1); }); it('should focus the first menuitem when toggle is triggered by enter keydown', () => { const toggle = jest.fn(); const focus = jest.fn(); render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem header>Header</DropdownItem> <DropdownItem disabled>Disabled</DropdownItem> <DropdownItem onFocus={focus}>Test</DropdownItem> <DropdownItem divider /> <DropdownItem>Another Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); expect(screen.getByText('Toggle')).toHaveFocus(); expect(focus).not.toHaveBeenCalled(); user.keyboard('{enter}'); expect(toggle).toHaveBeenCalled(); jest.runAllTimers(); expect(focus).toHaveBeenCalled(); }); it('should focus the first menuitem when toggle is triggered by up arrow keydown', () => { const toggle = jest.fn(); const focus = jest.fn(); render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem header>Header</DropdownItem> <DropdownItem disabled>Disabled</DropdownItem> <DropdownItem onFocus={focus}>Test</DropdownItem> <DropdownItem divider /> <DropdownItem>Another Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); expect(screen.getByText('Toggle')).toHaveFocus(); expect(focus).not.toHaveBeenCalled(); user.keyboard('{arrowdown}'); expect(toggle).toHaveBeenCalled(); jest.runAllTimers(); expect(focus).toHaveBeenCalled(); }); it('should focus the first menuitem when toggle is triggered by down arrow keydown', () => { const toggle = jest.fn(); const focus = jest.fn(); render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem header>Header</DropdownItem> <DropdownItem disabled>Disabled</DropdownItem> <DropdownItem onFocus={focus}>Test</DropdownItem> <DropdownItem divider /> <DropdownItem>Another Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); expect(screen.getByText('Toggle')).toHaveFocus(); expect(focus).not.toHaveBeenCalled(); user.keyboard('{arrowup}'); expect(toggle).toHaveBeenCalled(); jest.runAllTimers(); expect(focus).toHaveBeenCalled(); expect(screen.getByText('Test')).toHaveFocus(); }); it('should focus the next menuitem on down arrow keydown when isOpen is true', () => { const toggle = jest.fn(); const focus = jest.fn(); const focus2 = jest.fn(); const { rerender } = render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem header>Header</DropdownItem> <DropdownItem disabled>Disabled</DropdownItem> <DropdownItem onFocus={focus}>Test</DropdownItem> <DropdownItem>i am focused</DropdownItem> <DropdownItem divider /> <DropdownItem>Another Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); expect(screen.getByText('Toggle')).toHaveFocus(); expect(focus).not.toHaveBeenCalled(); user.keyboard('{arrowup}'); expect(toggle).toHaveBeenCalled(); rerender( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem header>Header</DropdownItem> <DropdownItem disabled>Disabled</DropdownItem> <DropdownItem onFocus={focus}>Test</DropdownItem> <DropdownItem onFocus={focus2}>i am focused</DropdownItem> <DropdownItem divider /> <DropdownItem>Another Test</DropdownItem> </DropdownMenu> </Dropdown>, ); jest.runAllTimers(); expect(focus).toHaveBeenCalled(); expect(screen.getByText('Test')).toHaveFocus(); user.keyboard('{arrowdown}'); expect(focus2).toHaveBeenCalled(); expect(screen.getByText('i am focused')).toHaveFocus(); }); it('should focus the next menuitem on ctrl + n keydown when isOpen is true', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByText('Test1').focus(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{ctrl>}N'); expect(screen.getByText('Test2')).toHaveFocus(); }); it('should focus the first menu item matching the character pressed when isOpen is true', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); render( <Dropdown isOpen> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem id="first" onFocus={focus1}> Reactstrap </DropdownItem> <DropdownItem onFocus={focus2}>4</DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus3}> Lyfe</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Reactstrap')).toHaveFocus(); focus1.mockClear(); user.keyboard('4'); expect(screen.getByText('4')).toHaveFocus(); expect(focus1.mock.calls.length).toBe(0); expect(focus2.mock.calls.length).toBe(1); expect(focus3.mock.calls.length).toBe(0); }); it('should skip non-menu items focus the next menu item on down arrow keydown when it isOpen is true and anther item is focused', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{arrowdown}'); expect(screen.getByText('Test2')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(1); expect(focus2).toBeCalledTimes(1); }); it('should focus the previous menu item on up arrow keydown when isOpen is true and another item is focused', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{arrowdown}'); expect(screen.getByText('Test2')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(1); expect(focus2).toBeCalledTimes(1); user.keyboard('{arrowup}'); expect(screen.getByText('Test1')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(2); }); it('should focus the previous menuitem on ctrl + p keydown when isOpen is true and another item is focused', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByText('Test1').focus(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{arrowdown}'); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(1); expect(focus2).toBeCalledTimes(1); expect(screen.getByText('Test2')).toHaveFocus(); user.keyboard('{ctrl>}P'); expect(screen.getByText('Test1')).toHaveFocus(); }); it('should wrap focus with down arrow keydown', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{arrowdown}'); expect(screen.getByText('Test2')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(1); expect(focus2).toBeCalledTimes(1); user.keyboard('{arrowdown}'); expect(screen.getByText('Test1')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(2); }); it('should wrap focus with up arrow keydown', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{arrowup}'); expect(screen.getByText('Test2')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(1); expect(focus2).toBeCalledTimes(1); }); it('should focus the 1st item on home key keyDown', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> <DropdownItem onFocus={focus3}>Test3</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{arrowdown}'); user.keyboard('{arrowdown}'); expect(screen.getByText('Test3')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(1); expect(focus2).toBeCalledTimes(1); expect(focus3).toBeCalledTimes(1); user.keyboard('{home}'); expect(screen.getByText('Test1')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(2); }); it('should focus the last item on end key keyDown', () => { const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem id="first" onFocus={focus1}> Test1 </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem onFocus={focus2}>Test2</DropdownItem> <DropdownItem onFocus={focus3}>Test3</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{end}'); expect(screen.getByText('Test3')).toHaveFocus(); expect(toggle).not.toHaveBeenCalled(); expect(focus1).toBeCalledTimes(1); expect(focus2).toBeCalledTimes(0); expect(focus3).toBeCalledTimes(1); }); it('should trigger a click on links when an item is focused and space[bar] it pressed', () => { const click = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu end> <DropdownItem href="#" id="first" onClick={click}> Test1 </DropdownItem> <DropdownItem id="second">Test</DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem id="third">Test</DropdownItem> </DropdownMenu> </Dropdown>, ); user.tab(); user.tab(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{space}'); expect(click).toHaveBeenCalled(); }); it('should trigger a click on buttons when an item is focused and space[bar] it pressed (override browser defaults for focus management)', () => { const toggle = jest.fn(); const click = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem id="first" onClick={click}> Test1 </DropdownItem> <DropdownItem id="second">Test</DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem id="third">Test</DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByText('Test1').focus(); expect(toggle).not.toHaveBeenCalled(); expect(screen.getByText('Test1')).toHaveFocus(); user.keyboard('{space}'); expect(toggle).toHaveBeenCalledTimes(1); expect(click).toHaveBeenCalledTimes(1); }); it('should not trigger anything when within an input', () => { const click = jest.fn(); const focus = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem tag="div" id="first" onClick={click} onFocus={focus}> <input id="input" placeholder="name" /> </DropdownItem> <DropdownItem id="second">Test</DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem id="third">Test</DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByPlaceholderText('name').focus(); expect(screen.getByPlaceholderText(/name/i)).toHaveFocus(); focus.mockClear(); click.mockClear(); user.keyboard('{arrowdown}'); user.keyboard('{arrowup}'); user.keyboard('{space}'); expect(toggle).not.toHaveBeenCalled(); expect(screen.getByPlaceholderText(/name/i)).toHaveFocus(); expect(focus).not.toHaveBeenCalled(); expect(click).not.toHaveBeenCalled(); }); it('should not trigger anything when within a textarea', () => { const click = jest.fn(); const focus = jest.fn(); const toggle = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem tag="div" id="first" onClick={click} onFocus={focus}> <textarea id="input" placeholder="placeholder" /> </DropdownItem> <DropdownItem id="second">Test</DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem id="third">Test</DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByPlaceholderText(/placeholder/i).focus(); expect(screen.getByPlaceholderText(/placeholder/i)).toHaveFocus(); focus.mockClear(); click.mockClear(); user.keyboard('{arrowdown}'); user.keyboard('{arrowup}'); user.keyboard('{space}'); expect(toggle).not.toHaveBeenCalled(); expect(screen.getByPlaceholderText(/placeholder/i)).toHaveFocus(); expect(focus).not.toHaveBeenCalled(); expect(click).not.toHaveBeenCalled(); }); it('should toggle when isOpen is true and tab keyDown on menuitem', () => { const toggle = jest.fn(); const focus = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem id="first">First</DropdownItem> <DropdownItem id="second" onFocus={focus}> Second </DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByText(/first/i).focus(); user.tab(); expect(toggle).toHaveBeenCalledTimes(1); }); it('should not trigger anything when disabled', () => { const toggle = jest.fn(); const click = jest.fn(); const focus = jest.fn(); render( <Dropdown isOpen toggle={toggle} disabled> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem tag="div" id="first" onClick={click} onFocus={focus}> Test1 </DropdownItem> <DropdownItem id="second">Test</DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem id="third">Test</DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByText(/test1/i).focus(); focus.mockClear(); user.keyboard('{arrowdown}'); user.keyboard('{arrowup}'); user.keyboard('{space}'); expect(toggle).not.toHaveBeenCalled(); expect(click).not.toHaveBeenCalled(); expect(focus).not.toHaveBeenCalled(); }); it('should not focus anything when all items disabled', () => { const toggle = jest.fn(); const click = jest.fn(); const focus = jest.fn(); render( <Dropdown isOpen toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem disabled tag="div" id="first" onClick={click} onFocus={focus} > Test </DropdownItem> <DropdownItem disabled id="second"> Test </DropdownItem> <DropdownItem id="divider" divider /> <DropdownItem disabled id="third"> Test </DropdownItem> </DropdownMenu> </Dropdown>, ); screen.getByText(/toggle/i).focus(); user.keyboard('{arrowdown}'); user.keyboard('{arrowup}'); user.keyboard('{space}'); expect(toggle).not.toHaveBeenCalled(); expect(click).not.toHaveBeenCalled(); expect(focus).not.toHaveBeenCalled(); }); it('should not call preventDefault when dropdown has focus and f5 key is pressed', () => { const toggle = jest.fn(); render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); expect(toggle).not.toHaveBeenCalled(); const button = screen.getByText(/toggle/i); const keyEvent1 = createEvent.keyDown(button, { keyCode: 116, }); fireEvent(button, keyEvent1); expect(keyEvent1.defaultPrevented).toBe(false); const keyEvent2 = createEvent.keyDown(button, { keyCode: 16, }); fireEvent(button, keyEvent2); expect(keyEvent2.defaultPrevented).toBe(false); }); it('should call preventDefault when dropdown has focus and any key(up, down, esc, enter, home, end or any alphanumeric key) is pressed', () => { const toggle = jest.fn(); render( <Dropdown isOpen={false} toggle={toggle}> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> <DropdownItem id="divider" divider /> </DropdownMenu> </Dropdown>, ); expect(toggle).not.toHaveBeenCalled(); const button = screen.getByText(/toggle/i); [ keyCodes.down, keyCodes.up, keyCodes.end, keyCodes.home, keyCodes.enter, 90, // for 'a' 65, // for 'A' ].forEach((keyCode) => { const keyEvent = createEvent.keyDown(button, { keyCode, }); fireEvent(button, keyEvent); expect(keyEvent.defaultPrevented).toBe(true); }); }); }); it('should render different size classes', () => { const { rerender } = render( <Dropdown group isOpen size="sm"> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByText(/toggle/i).parentElement).toHaveClass( 'btn-group-sm', ); rerender( <Dropdown group isOpen size="lg"> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByText(/toggle/i).parentElement).toHaveClass( 'btn-group-lg', ); }); describe('Dropdown with nav', () => { it('should render a single child', () => { testForChildrenInComponent(Dropdown); }); it('should render multiple children when isOpen', () => { render( <Dropdown nav isOpen> <DropdownToggle>Toggle</DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByText(/test/i)).toBeInTheDocument(); expect(screen.getByText(/toggle/i)).toBeInTheDocument(); }); }); describe('Dropdown in navbar', () => { it('should open without popper with inNavbar prop', () => { render( <Dropdown nav inNavbar> <DropdownToggle caret nav> Toggle </DropdownToggle> <DropdownMenu> <DropdownItem>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByText(/toggle/i).tagName).toBe('A'); expect(screen.getByText(/test/i).parentElement.tagName).toBe('DIV'); }); }); describe('active', () => { it('should render an active class', () => { render(<Dropdown active nav />); expect(screen.getByRole('listitem')).toHaveClass('active'); }); it('should render an active class when a child DropdownItem is active IF setActiveFromChild is true', () => { render( <Dropdown nav inNavbar setActiveFromChild> <DropdownToggle nav caret> Options </DropdownToggle> <DropdownMenu> <DropdownItem active>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByRole('listitem')).toHaveClass('active'); }); }); it('should render with correct class when direction is set', () => { const { rerender } = render(<Dropdown direction="up" nav />); expect(screen.getByRole('listitem')).toHaveClass('dropup'); rerender(<Dropdown direction="start" nav />); expect(screen.getByRole('listitem')).toHaveClass('dropstart'); rerender(<Dropdown direction="end" nav />); expect(screen.getByRole('listitem')).toHaveClass('dropend'); }); describe('menuRole prop', () => { it('should set correct roles for children when menuRole is menu', () => { render( <Dropdown menuRole="menu" isOpen> <DropdownToggle nav caret> Options </DropdownToggle> <DropdownMenu> <DropdownItem active>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByText(/options/i)).toHaveAttribute( 'aria-haspopup', 'menu', ); expect(screen.getByRole('menu')).toBeInTheDocument(); expect(screen.getByRole('menuitem')).toBeInTheDocument(); }); it('should set correct roles for children when menuRole is menu', () => { render( <Dropdown menuRole="listbox" isOpen> <DropdownToggle nav caret> Options </DropdownToggle> <DropdownMenu> <DropdownItem active>Test</DropdownItem> </DropdownMenu> </Dropdown>, ); expect(screen.getByText(/options/i)).toHaveAttribute( 'aria-haspopup', 'listbox', ); expect(screen.getByRole('option')).toBeInTheDocument(); expect(screen.getByRole('listbox')).toBeInTheDocument(); }); }); });