import React from 'react'; import { mount, shallow } from 'enzyme'; import { Popper, Reference } from 'react-popper'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from '../'; import { keyCodes } from '../utils'; describe('Dropdown', () => { let isOpen; let toggle; let element; beforeEach(() => { isOpen = false; toggle = () => { isOpen = !isOpen; }; element = document.createElement('div'); document.body.appendChild(element); }); afterEach(() => { if (jest.isMockFunction(Dropdown.prototype.componentDidUpdate)) Dropdown.prototype.componentDidUpdate.mockRestore(); if (jest.isMockFunction(Dropdown.prototype.handleProps)) Dropdown.prototype.handleProps.mockRestore(); if (jest.isMockFunction(Dropdown.prototype.toggle)) Dropdown.prototype.toggle.mockRestore(); if (jest.isMockFunction(Dropdown.prototype.handleDocumentClick)) Dropdown.prototype.handleDocumentClick.mockRestore(); document.body.removeChild(element); document.body.innerHTML = ''; element = null; }); it('should render a single child', () => { const wrapper = mount(Ello world); expect(wrapper.text()).toBe('Ello world'); expect(wrapper.find('.dropdown').hostNodes().length).toBe(1); }); it('should render multiple children when isOpen is true', () => { isOpen = true; const wrapper = mount( Toggle Test ); expect(wrapper.find('.btn').hostNodes().text()).toBe('Toggle'); expect(wrapper.find('.dropdown').hostNodes().length).toBe(1); expect(wrapper.find('.dropdown-item').hostNodes().length).toBe(1); expect(wrapper.childAt(0).childAt(0).children().length).toBe(2); }); it('should not call props.toggle when disabled ', () => { isOpen = true; let props = createSpyObj('props', ['toggle']); const wrapper = mount( Toggle Test ); const instance = wrapper.instance(); instance.toggle({ preventDefault: () => { } }); expect(props.toggle).not.toHaveBeenCalled(); }); describe('handleProps', () => { it('should not pass custom props to html attrs', () => { const wrapper = mount( Toggle Test ); expect(wrapper.find('.dropdown').prop('inNavbar')).toBe(undefined); expect(wrapper.find('.dropdown').prop('toggle')).toBe(undefined); expect(wrapper.find('.dropdown').prop('a11y')).toBe(undefined); expect(wrapper.find('.dropdown').prop('isOpen')).toBe(undefined); }); it('should be called on componentDidUpdate when isOpen changed', () => { jest.spyOn(Dropdown.prototype, 'componentDidUpdate'); jest.spyOn(Dropdown.prototype, 'handleProps'); const wrapper = mount( Toggle Test ); const instance = wrapper.instance(); expect(Dropdown.prototype.componentDidUpdate.mock.calls.length).toBe(0); expect(Dropdown.prototype.handleProps.mock.calls.length).toBe(1); expect(instance.props.isOpen).toBe(false); isOpen = true; wrapper.setProps({ isOpen: isOpen }); expect(Dropdown.prototype.componentDidUpdate.mock.calls.length).toBe(1); expect(Dropdown.prototype.handleProps.mock.calls.length).toBe(2); expect(instance.props.isOpen).toBe(true); }); it('should not be called on componentDidUpdate when isOpen did not change', () => { jest.spyOn(Dropdown.prototype, 'componentDidUpdate'); jest.spyOn(Dropdown.prototype, 'handleProps'); const wrapper = mount( Toggle Test ); const instance = wrapper.instance(); expect(Dropdown.prototype.componentDidUpdate.mock.calls.length).toBe(0); expect(Dropdown.prototype.handleProps.mock.calls.length).toBe(1); expect(instance.props.isOpen).toBe(false); wrapper.setProps({ 'data-foo': 'bar' }); expect(Dropdown.prototype.componentDidUpdate.mock.calls.length).toBe(1); expect(Dropdown.prototype.handleProps.mock.calls.length).toBe(1); expect(instance.props.isOpen).toBe(false); }); }); describe('removeEvents', () => { it('should be called on componentWillUnmount', () => { jest.spyOn(Dropdown.prototype, 'componentWillUnmount'); jest.spyOn(Dropdown.prototype, 'removeEvents'); isOpen = true; const wrapper = mount( Toggle Test ); expect(Dropdown.prototype.componentWillUnmount.mock.calls.length).toBe(0); expect(Dropdown.prototype.removeEvents.mock.calls.length).toBe(0); wrapper.unmount(); expect(Dropdown.prototype.componentWillUnmount.mock.calls.length).toBe(1); expect(Dropdown.prototype.removeEvents.mock.calls.length).toBe(1); }); }); describe('handleDocumentClick', () => { it('should call toggle on document click', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'handleDocumentClick'); jest.spyOn(Dropdown.prototype, 'toggle'); mount( Toggle Test ); expect(Dropdown.prototype.handleDocumentClick.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); document.body.click(); expect(Dropdown.prototype.handleDocumentClick.mock.calls.length).toBe(1); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); }); it('should call toggle on container click', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'handleDocumentClick'); jest.spyOn(Dropdown.prototype, 'toggle'); const wrapper = mount( Toggle Test , { attachTo: element }); expect(Dropdown.prototype.handleDocumentClick.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); document.getElementById('test').click(); expect(Dropdown.prototype.handleDocumentClick.mock.calls.length).toBe(1); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); it('should not call toggle on inner container click', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'handleDocumentClick'); jest.spyOn(Dropdown.prototype, 'toggle'); const wrapper = mount( Toggle Test , { attachTo: element }); expect(Dropdown.prototype.handleDocumentClick.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); document.getElementById('divider').click(); expect(Dropdown.prototype.handleDocumentClick.mock.calls.length).toBe(1); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.detach(); }); it('should not call toggle when right-clicked', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const wrapper = mount( Toggle Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.instance().handleDocumentClick({ type: 'click', which: 3 }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.detach(); }); it('should call toggle when key is tab', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const wrapper = mount( Toggle Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.instance().handleDocumentClick({ type: 'keyup', which: keyCodes.tab }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); }); describe('keyboard events', () => { it('should call toggle on ESC keydown when it isOpen is true', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const wrapper = mount( Toggle Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#test').hostNodes().simulate('keydown', { which: keyCodes.esc }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); it('should call toggle on down arrow keydown when it isOpen is false', () => { isOpen = false; jest.spyOn(Dropdown.prototype, 'toggle'); const wrapper = mount( Toggle Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.down }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); it('should call toggle on up arrow keydown when it isOpen is false', () => { isOpen = false; jest.spyOn(Dropdown.prototype, 'toggle'); const wrapper = mount( Toggle Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.up }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); it('should focus the first menuitem when toggle is triggered by enter keydown', () => { jest.useFakeTimers(); jest.spyOn(Dropdown.prototype, 'toggle'); const focus = jest.fn(); const wrapper = mount( Toggle Header Disabled Test Another Test , { attachTo: element } ); expect(focus.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.enter }); jest.runAllTimers(); expect(focus.mock.calls.length).toBe(1); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); it('should focus the first menuitem when toggle is triggered by up arrow keydown', () => { jest.useFakeTimers(); jest.spyOn(Dropdown.prototype, 'toggle'); const focus = jest.fn(); const wrapper = mount( Toggle Header Disabled Test Another Test , { attachTo: element } ); expect(focus.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.up }); jest.runAllTimers(); expect(focus.mock.calls.length).toBe(1); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); it('should focus the first menuitem when toggle is triggered by down arrow keydown', () => { jest.useFakeTimers(); jest.spyOn(Dropdown.prototype, 'toggle'); const focus = jest.fn(); const wrapper = mount( Toggle Header Disabled Test Another Test , { attachTo: element } ); expect(focus.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.down }); jest.runAllTimers(); expect(focus.mock.calls.length).toBe(1); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); it('should focus the next menuitem on down arrow keydown when isOpen is true', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.down }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(0); expect(focus2.mock.calls.length).toBe(1); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); it('should focus the next menuitem on ctrl + n keydown when isOpen is true', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.n, ctrlKey: true }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(0); expect(focus2.mock.calls.length).toBe(1); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); it('should focus the first menu item matching the character pressed when isOpen is true', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Reactstrap 4 Lyfe , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: 52 }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(0); expect(focus2.mock.calls.length).toBe(1); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); 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', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#second').hostNodes().simulate('keydown', { which: keyCodes.down }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(0); expect(focus2.mock.calls.length).toBe(0); expect(focus3.mock.calls.length).toBe(1); wrapper.detach(); }); it('should focus the previous menu item on up arrow keydown when isOpen is true and another item is focused', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#second').hostNodes().simulate('keydown', { which: keyCodes.up }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(1); expect(focus2.mock.calls.length).toBe(0); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); it('should focus the previous menuitem on ctrl + p keydown when isOpen is true and another item is focused', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#second').hostNodes().simulate('keydown', { which: keyCodes.p, ctrlKey: true }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(1); expect(focus2.mock.calls.length).toBe(0); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); it('should wrap focus with down arrow keydown', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#third').hostNodes().simulate('keydown', { which: keyCodes.down }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(1); expect(focus2.mock.calls.length).toBe(0); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); it('should wrap focus with up arrow keydown', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.up }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(0); expect(focus2.mock.calls.length).toBe(0); expect(focus3.mock.calls.length).toBe(1); wrapper.detach(); }); it('should focus the 1st item on home key keyDown', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.home }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(1); expect(focus2.mock.calls.length).toBe(0); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); it('should focus the last item on end key keyDown', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); const focus2 = jest.fn(); const focus3 = jest.fn(); const wrapper = mount( Toggle Test Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.end }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(0); expect(focus2.mock.calls.length).toBe(0); expect(focus3.mock.calls.length).toBe(1); wrapper.detach(); }); it('should trigger a click on links when an item is focused and space[bar] it pressed', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const click = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.space }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); expect(click.mock.calls.length).toBe(1); wrapper.detach(); }); it('should trigger a click on buttons when an item is focused and space[bar] it pressed (override browser defaults for focus management)', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const click = jest.fn(); const wrapper = mount( Toggle Test Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.space }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); expect(click.mock.calls.length).toBe(1); wrapper.detach(); }); it('should not trigger anything when within an input', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const click = jest.fn(); const focus = jest.fn(); const wrapper = mount( Toggle Test Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); wrapper.find('#input').hostNodes().simulate('keydown', { which: keyCodes.up }); wrapper.find('#input').hostNodes().simulate('keydown', { which: keyCodes.down }); wrapper.find('#input').hostNodes().simulate('keydown', { which: keyCodes.space }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(click.mock.calls.length).toBe(0); expect(focus.mock.calls.length).toBe(0); wrapper.detach(); }); it('should not trigger anything when within a textarea', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const click = jest.fn(); const focus = jest.fn(); const wrapper = mount( Toggle