metadata-based-explorer1
Version:
Box UI Elements
661 lines (564 loc) • 22.3 kB
JavaScript
import React from 'react';
import { mount, shallow } from 'enzyme';
import sinon from 'sinon';
import DropdownMenu from '../DropdownMenu';
const sandbox = sinon.sandbox.create();
describe('components/dropdown-menu/DropdownMenu', () => {
// eslint-disable-next-line react/button-has-type
const FakeButton = props => <button {...props}>Some Button</button>;
FakeButton.displayName = 'FakeButton';
/* eslint-disable */
const FakeMenu = ({
initialFocusIndex = 0,
onClose = () => {},
...rest
}) => (
<ul {...rest} role="menu">
Some Menu
</ul>
);
FakeMenu.displayName = 'FakeMenu';
/* eslint-enable */
const getWrapper = (props = {}) =>
shallow(
<DropdownMenu {...props}>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
afterEach(() => {
sandbox.verifyAndRestore();
});
describe('render()', () => {
test('should throw an error when passed less than 2 children', () => {
expect(() => {
shallow(
<DropdownMenu>
<FakeButton />
</DropdownMenu>,
);
}).toThrow();
});
test('should throw an error when passed more than 2 children', () => {
expect(() => {
shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
<div />
</DropdownMenu>,
);
}).toThrow();
});
test('should correctly render a single child button with correct props', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
const button = wrapper.find(FakeButton);
expect(button.length).toBe(1);
expect(button.prop('id')).toEqual(instance.menuButtonID);
expect(button.key()).toEqual(instance.menuButtonID);
expect(button.prop('aria-haspopup')).toEqual('true');
expect(button.prop('aria-expanded')).toEqual('false');
expect(button.prop('aria-controls')).toBeFalsy();
});
test('should set aria-expanded="true" and aria-controls=menuID when menu is open', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
wrapper.update();
const button = wrapper.find(FakeButton);
expect(button.prop('aria-expanded')).toEqual('true');
expect(button.prop('aria-controls')).toEqual(instance.menuID);
});
test('should not render child menu when menu is closed', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const menu = wrapper.find(FakeMenu);
expect(menu.length).toBe(0);
});
test('should correctly render a single child menu with correct props when menu is open', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(1);
wrapper.update();
const menu = wrapper.find(FakeMenu);
expect(menu.length).toBe(1);
expect(menu.prop('id')).toEqual(instance.menuID);
expect(menu.key()).toEqual(instance.menuID);
expect(menu.prop('initialFocusIndex')).toEqual(1);
expect(menu.prop('aria-labelledby')).toEqual(instance.menuButtonID);
});
test('should render TetherComponent with correct props with correct default values', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.is('TetherComponent')).toBe(true);
expect(wrapper.prop('attachment')).toEqual('top left');
expect(wrapper.prop('bodyElement')).toEqual(document.body);
expect(wrapper.prop('classPrefix')).toEqual('dropdown-menu');
expect(wrapper.prop('targetAttachment')).toEqual('bottom left');
expect(wrapper.prop('constraints')).toEqual([]);
expect(wrapper.prop('enabled')).toBe(false);
});
test('should render TetherComponent in the body if invalid body element is specified', () => {
const wrapper = shallow(
<DropdownMenu bodyElement="foo">
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.is('TetherComponent')).toBe(true);
expect(wrapper.prop('attachment')).toEqual('top left');
expect(wrapper.prop('bodyElement')).toEqual(document.body);
expect(wrapper.prop('classPrefix')).toEqual('dropdown-menu');
expect(wrapper.prop('targetAttachment')).toEqual('bottom left');
expect(wrapper.prop('constraints')).toEqual([]);
expect(wrapper.prop('enabled')).toBe(false);
});
test('should render className in the className is specified', () => {
const wrapper = shallow(
<DropdownMenu className="foo">
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.is('TetherComponent')).toBe(true);
expect(wrapper.prop('className')).toEqual('foo');
});
test('should render TetherComponent with a specific body element', () => {
const bodyEl = document.createElement('div');
const wrapper = shallow(
<DropdownMenu bodyElement={bodyEl}>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.is('TetherComponent')).toBe(true);
expect(wrapper.prop('attachment')).toEqual('top left');
expect(wrapper.prop('bodyElement')).toEqual(bodyEl);
expect(wrapper.prop('classPrefix')).toEqual('dropdown-menu');
expect(wrapper.prop('targetAttachment')).toEqual('bottom left');
expect(wrapper.prop('constraints')).toEqual([]);
expect(wrapper.prop('enabled')).toBe(false);
});
test('should render TetherComponent with correct props when right aligned', () => {
const wrapper = shallow(
<DropdownMenu isRightAligned>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.prop('attachment')).toEqual('top right');
expect(wrapper.prop('targetAttachment')).toEqual('bottom right');
expect(wrapper.prop('enabled')).toBe(false);
});
test('should render TetherComponent with enabled prop when menu is open', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
wrapper.update();
expect(wrapper.prop('enabled')).toBe(true);
});
test('should render TetherComponent with scrollParent constraint when constrainToScrollParent=true', () => {
const wrapper = shallow(
<DropdownMenu constrainToScrollParent>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.prop('constraints')).toEqual([
{
to: 'scrollParent',
attachment: 'together',
},
]);
});
test('should render TetherComponent with window constraint when constrainToScrollParent=true', () => {
const wrapper = shallow(
<DropdownMenu constrainToWindow>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.prop('constraints')).toEqual([
{
to: 'window',
attachment: 'together',
},
]);
});
test('should render TetherComponent with scrollParent and window constraints when constrainToScrollParent=true and constrainToWindow=true', () => {
const wrapper = shallow(
<DropdownMenu constrainToScrollParent constrainToWindow>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
expect(wrapper.prop('constraints')).toEqual([
{
to: 'scrollParent',
attachment: 'together',
},
{
to: 'window',
attachment: 'together',
},
]);
});
});
describe('openMenuAndSetFocusIndex()', () => {
test('should call setState() with correct values', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
sandbox
.mock(instance)
.expects('setState')
.withArgs({
isOpen: true,
initialFocusIndex: 1,
});
instance.openMenuAndSetFocusIndex(1);
});
});
describe('closeMenu()', () => {
test('should call setState() with correct values', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
sandbox
.mock(instance)
.expects('setState')
.withArgs({
isOpen: false,
});
instance.closeMenu();
});
});
describe('handleButtonClick()', () => {
test('should call openMenuAndSetFocusIndex(null) when menu is currently closed', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
sandbox
.mock(instance)
.expects('openMenuAndSetFocusIndex')
.withArgs(null);
wrapper.find(FakeButton).simulate('click', {
preventDefault: sandbox.mock(),
stopPropagation: sandbox.mock(),
});
});
test('should call closeMenu() when menu is currently open', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(1);
sandbox.mock(instance).expects('closeMenu');
wrapper.find(FakeButton).simulate('click', {
preventDefault: sandbox.mock(),
stopPropagation: sandbox.mock(),
});
});
});
describe('handleButtonKeyDown()', () => {
[
{
key: ' ',
},
{
key: 'Enter',
},
{
key: 'ArrowDown',
},
].forEach(({ key }) => {
test('should call openMenuAndSetFocus(0) when an open keystroke is pressed', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
sandbox
.mock(instance)
.expects('openMenuAndSetFocusIndex')
.withArgs(0);
wrapper.find(FakeButton).simulate('keydown', {
key,
preventDefault: sandbox.mock(),
stopPropagation: sandbox.mock(),
});
});
});
test('should call openMenuAndSetFocus(-1) to last item when "up" is pressed', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
sandbox
.mock(instance)
.expects('openMenuAndSetFocusIndex')
.withArgs(-1);
wrapper.find(FakeButton).simulate('keydown', {
key: 'ArrowUp',
preventDefault: sandbox.mock(),
stopPropagation: sandbox.mock(),
});
});
});
describe('handleMenuClose()', () => {
test('should call closeMenu() and focusButton() when called', () => {
const wrapper = shallow(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
sandbox.mock(instance).expects('closeMenu');
sandbox.mock(instance).expects('focusButton');
instance.handleMenuClose();
});
});
describe('componentDidUpdate()', () => {
test('should add click and contextmenu listeners when opening menu', () => {
const wrapper = mount(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
const documentMock = sandbox.mock(document);
documentMock.expects('addEventListener').withArgs('click');
documentMock.expects('addEventListener').withArgs('contextmenu');
instance.openMenuAndSetFocusIndex(0);
});
test('should call onMenuOpen() when specified and menu is opening', () => {
const onMenuOpen = jest.fn();
const wrapper = getWrapper({
onMenuOpen,
});
document.addEventListener = jest.fn();
document.removeEventListener = jest.fn();
wrapper.setState({ isOpen: true }); // called when false => true
wrapper.setState({ isOpen: true }); // not called when true => true
wrapper.setState({ isOpen: false }); // not called when true => false
expect(onMenuOpen).toHaveBeenCalledTimes(1);
});
test('should remove click and contextmenu listeners when closing menu', () => {
const wrapper = mount(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
const documentMock = sandbox.mock(document);
documentMock.expects('removeEventListener').withArgs('contextmenu');
documentMock.expects('removeEventListener').withArgs('click');
instance.closeMenu();
});
test('should not do anything opening a menu when menu is already open', () => {
const wrapper = mount(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
const documentMock = sandbox.mock(document);
documentMock
.expects('addEventListener')
.withArgs('click')
.never();
documentMock
.expects('addEventListener')
.withArgs('contextmenu')
.never();
documentMock
.expects('removeEventListener')
.withArgs('contextmenu')
.never();
documentMock
.expects('removeEventListener')
.withArgs('click')
.never();
instance.openMenuAndSetFocusIndex(1);
});
});
describe('componentWillUnmount()', () => {
test('should not do anything when menu is closed', () => {
const wrapper = mount(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const documentMock = sandbox.mock(document);
documentMock
.expects('removeEventListener')
.withArgs('contextmenu')
.never();
documentMock
.expects('removeEventListener')
.withArgs('click')
.never();
wrapper.unmount();
});
test('should remove listeners when menu is open', () => {
const wrapper = mount(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
const documentMock = sandbox.mock(document);
documentMock.expects('removeEventListener').withArgs('contextmenu');
documentMock.expects('removeEventListener').withArgs('click');
wrapper.unmount();
});
});
describe('tests requiring body mounting', () => {
let attachTo;
let wrapper = null;
/**
* Helper method to mount things to the correct DOM element
* this makes it easier to clean up after ourselves after each test.
*/
const mountToBody = component => {
wrapper = mount(component, { attachTo });
};
beforeEach(() => {
// Set up a place to mount
attachTo = document.createElement('div');
attachTo.setAttribute('data-mounting-point', '');
document.body.appendChild(attachTo);
});
afterEach(() => {
sandbox.verifyAndRestore();
// Unmount and remove the mounting point after each test
if (wrapper) {
wrapper.unmount();
wrapper = null;
}
document.body.removeChild(attachTo);
});
describe('handleDocumentClick()', () => {
test('should call closeMenu() when event target is not within the menu or button', () => {
mountToBody(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
sandbox.mock(instance).expects('closeMenu');
instance.handleDocumentClick({
target: document.createElement('div'),
});
});
test('should not call closeMenu() when event target is within the button', () => {
mountToBody(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
sandbox
.mock(instance)
.expects('closeMenu')
.never();
instance.handleDocumentClick({
target: document.getElementById(instance.menuButtonID),
});
});
test('should not call closeMenu() when event target is within the menu', () => {
mountToBody(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
instance.openMenuAndSetFocusIndex(0);
sandbox
.mock(instance)
.expects('closeMenu')
.never();
instance.handleDocumentClick({
target: document.getElementById(instance.menuID),
});
});
});
describe('focusButton()', () => {
test('should focus the menu button when called', () => {
mountToBody(
<DropdownMenu>
<FakeButton />
<FakeMenu />
</DropdownMenu>,
);
const instance = wrapper.instance();
const menuButtonEl = document.getElementById(instance.menuButtonID);
sandbox.mock(menuButtonEl).expects('focus');
instance.focusButton();
});
});
});
});