box-ui-elements-mlh
Version:
742 lines (610 loc) • 26.4 kB
JavaScript
import React, { Children } from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import * as domUtils from '../../../utils/dom';
import SelectorDropdown from '..';
const sandbox = sinon.sandbox.create();
describe('components/selector-dropdown/SelectorDropdown', () => {
afterEach(() => {
sandbox.verifyAndRestore();
});
const Selector = () => <input />;
const renderEmptyDropdown = props => shallow(<SelectorDropdown selector={<Selector />} {...props} />);
const renderDropdownWithChildren = (children, props) =>
shallow(
<SelectorDropdown selector={<Selector />} {...props}>
{Children.map(children, item => (
<li key={item}>{item}</li>
))}
</SelectorDropdown>,
);
describe('componentWillUnmount()', () => {
test('should remove document click listener', () => {
document.removeEventListener = jest.fn();
const wrapper = renderEmptyDropdown();
wrapper.unmount();
expect(document.removeEventListener.mock.calls.length).toBe(1);
});
});
describe('render()', () => {
test('should render a div wrapper with the specified class', () => {
const className = 'test';
const wrapper = renderEmptyDropdown({ className });
expect(wrapper.hasClass('SelectorDropdown')).toBe(true);
expect(wrapper.hasClass(className)).toBe(true);
});
test('should render the selector with aria props', () => {
const wrapper = renderEmptyDropdown();
const selector = wrapper.find('Selector');
const inputProps = selector.prop('inputProps');
expect(selector.length).toBe(1);
expect(inputProps['aria-activedescendant']).toBeNull();
expect(inputProps['aria-expanded']).toBe(false);
});
test('should not set aria-owns and render a listbox when dropdown is closed', () => {
const wrapper = renderEmptyDropdown();
const inputProps = wrapper.find('Selector').prop('inputProps');
expect(inputProps['aria-owns']).toBeFalsy();
expect(wrapper.find('.overlay-wrapper').length).toBe(0);
});
test('should render listbox with children when dropdown is open', () => {
const wrapper = renderDropdownWithChildren(['Testing', 'Hello']);
wrapper.setState({
activeItemIndex: 0,
shouldOpen: true,
});
const inputProps = wrapper.find('Selector').prop('inputProps');
expect(inputProps['aria-owns']).toBeTruthy();
const overlay = wrapper.find('ul.overlay');
expect(overlay.length).toBe(1);
expect(overlay.prop('id')).toBeTruthy();
const items = wrapper.find('li');
expect(items.length).toBe(2);
expect(items.at(0).prop('setActiveItemID')).toBeTruthy();
expect(items.at(0).prop('isActive')).toBe(true);
});
test('should render header that is passed in when dropdown is open', () => {
const title = <div className="title" />;
const wrapper = renderDropdownWithChildren(['Testing', 'Hello'], {
title,
});
wrapper.setState({
activeItemIndex: 0,
shouldOpen: true,
});
expect(wrapper.find('.title')).toHaveLength(1);
});
test('should render title when passed overlayTitle', () => {
const wrapper = renderDropdownWithChildren(['Testing', 'Hello'], {
isAlwaysOpen: true,
});
expect(wrapper.find('.SelectorDropdown-title')).toHaveLength(0);
});
test('should not render titie when not passed overlayTitle', () => {
const wrapper = renderDropdownWithChildren(['Testing', 'Hello'], {
isAlwaysOpen: true,
overlayTitle: 'Some Title',
});
expect(wrapper.find('.SelectorDropdown-title')).toHaveLength(1);
});
test('should render divider when passed dividerIndex', () => {
const wrapper = renderDropdownWithChildren(['Testing', 'Hello'], { dividerIndex: 1, isAlwaysOpen: true });
expect(wrapper.find('.SelectorDropdown-divider')).toHaveLength(1);
});
test('should not render divider when not passed dividerIndex', () => {
const wrapper = renderDropdownWithChildren(['Testing', 'Hello'], { isAlwaysOpen: true });
expect(wrapper.find('.SelectorDropdown-divider')).toHaveLength(0);
});
});
describe('onFocus', () => {
test('should set shouldOpen state to true when called', () => {
const wrapper = renderEmptyDropdown();
wrapper.simulate('focus');
expect(wrapper.state('shouldOpen')).toBe(true);
});
});
describe('handleDocumentClick', () => {
test('should close dropdown when click occurs outside of selector dropdown', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
wrapper.simulate('focus');
expect(wrapper.state('shouldOpen')).toBe(true);
sandbox.mock(instance).expects('closeDropdown');
instance.handleDocumentClick({
target: document.createElement('div'),
});
});
test('should not close dropdown when click occurs on selector dropdown container', () => {
const wrapper = renderEmptyDropdown();
wrapper.simulate('focus');
expect(wrapper.state('shouldOpen')).toBe(true);
wrapper.simulate('click');
expect(wrapper.state('shouldOpen')).toBe(true);
});
test('should not close dropdown when click occurs on dropdown menu', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
wrapper.simulate('focus');
expect(wrapper.state('shouldOpen')).toBe(true);
sandbox
.mock(instance)
.expects('closeDropdown')
.never();
instance.handleDocumentClick({
target: document.getElementById(instance.listboxID),
});
});
});
describe('handleInput()', () => {
test('should call openDropdown() when key is pressed', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
sandbox.mock(instance).expects('openDropdown');
wrapper.simulate('keyPress');
});
test('should call openDropdown() when text is pasted', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
sandbox.mock(instance).expects('openDropdown');
wrapper.simulate('paste');
});
});
describe('onArrowDown', () => {
let event;
const preventDefault = sandbox.spy();
const stopPropagation = sandbox.spy();
beforeEach(() => {
event = {
key: 'ArrowDown',
preventDefault,
stopPropagation,
};
});
test('should set next active item when key is arrow down and dropdown is open', () => {
const wrapper = renderDropdownWithChildren(['test']);
const instance = wrapper.instance();
sandbox.stub(instance, 'isDropdownOpen').returns(true);
sandbox
.mock(instance)
.expects('setActiveItem')
.withArgs(0);
wrapper.simulate('keyDown', event);
});
test('should reset active item when key is arrow down, dropdown is open, and the last item is active', () => {
const wrapper = renderDropdownWithChildren(['test']);
const instance = wrapper.instance();
wrapper.setState({ activeItemIndex: 0 });
sandbox.stub(instance, 'isDropdownOpen').returns(true);
sandbox
.mock(instance)
.expects('setActiveItem')
.withArgs(-1);
wrapper.simulate('keyDown', event);
});
test('should open dropdown when key is arrow down and dropdown is closed', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
sandbox.stub(instance, 'isDropdownOpen').returns(false);
sandbox.stub(instance, 'openDropdown');
wrapper.simulate('keyDown', event);
expect(instance.openDropdown.calledOnce).toEqual(true);
});
});
describe('onArrowUp', () => {
let event;
const preventDefault = sandbox.spy();
const stopPropagation = sandbox.spy();
beforeEach(() => {
event = {
key: 'ArrowUp',
preventDefault,
stopPropagation,
};
});
test('should set previous active item when key is arrow up and dropdown is open', () => {
const wrapper = renderDropdownWithChildren(['test']);
const instance = wrapper.instance();
wrapper.setState({ activeItemIndex: 0 });
sandbox.stub(instance, 'isDropdownOpen').returns(true);
sandbox
.mock(instance)
.expects('setActiveItem')
.withArgs(-1);
wrapper.simulate('keyDown', event);
});
test('should correctly set active item when key is arrow up, dropdown is open, and no item is active', () => {
const wrapper = renderDropdownWithChildren(['test']);
const instance = wrapper.instance();
sandbox.stub(instance, 'isDropdownOpen').returns(true);
sandbox
.mock(instance)
.expects('setActiveItem')
.withArgs(0);
wrapper.simulate('keyDown', event);
});
test('should open dropdown when key is arrow up and dropdown is closed', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
sandbox.stub(instance, 'isDropdownOpen').returns(false);
sandbox.stub(instance, 'openDropdown');
wrapper.simulate('keyDown', event);
expect(instance.openDropdown.calledOnce).toEqual(true);
});
});
describe('onEnter', () => {
test('should not stop default event or select item when no item is active', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
sandbox
.mock(instance)
.expects('selectItem')
.never();
wrapper.simulate('keyDown', {
key: 'Enter',
preventDefault: sandbox.mock().never(),
stopPropagation: sandbox.mock().never(),
});
});
test('should not stop default event or select item when dropdown is closed', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
wrapper.setState({ activeItemIndex: 0 });
sandbox.stub(instance, 'isDropdownOpen').returns(false);
sandbox
.mock(instance)
.expects('selectItem')
.never();
wrapper.simulate('keyDown', {
key: 'Enter',
preventDefault: sandbox.mock().never(),
stopPropagation: sandbox.mock().never(),
});
});
test('should stop default event and select item when an item is active and dropdown is open', () => {
const activeItemIndex = 0;
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
const event = {
key: 'Enter',
preventDefault: sandbox.mock(),
stopPropagation: sandbox.mock(),
};
wrapper.setState({ activeItemIndex });
sandbox.stub(instance, 'isDropdownOpen').returns(true);
sandbox
.mock(instance)
.expects('selectItem')
.withExactArgs(activeItemIndex, event);
wrapper.simulate('keyDown', event);
});
test('should call onEnter() when specified and no item is active', () => {
const event = {
key: 'Enter',
};
const wrapper = renderEmptyDropdown({
onEnter: sandbox.mock().withExactArgs(event),
});
wrapper.simulate('keyDown', event);
});
test('should call onEnter() when specified and dropdown is closed', () => {
const event = {
key: 'Enter',
};
const wrapper = renderEmptyDropdown({
onEnter: sandbox.mock().withExactArgs(event),
});
const instance = wrapper.instance();
wrapper.setState({ activeItemIndex: 0 });
sandbox.stub(instance, 'isDropdownOpen').returns(false);
wrapper.simulate('keyDown', event);
});
});
describe('onTab', () => {
test('should not close dropdown or reset active item when dropdown is closed', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
const instanceMock = sandbox.mock(instance);
sandbox.stub(instance, 'isDropdownOpen').returns(false);
instanceMock.expects('closeDropdown').never();
instanceMock.expects('resetActiveItem').never();
wrapper.simulate('keyDown', {
key: 'Tab',
});
});
test('should call closeDropdown() and reset active item when dropdown is open', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
const instanceMock = sandbox.mock(instance);
sandbox.stub(instance, 'isDropdownOpen').returns(true);
instanceMock.expects('closeDropdown');
instanceMock.expects('resetActiveItem');
wrapper.simulate('keyDown', {
key: 'Tab',
});
});
});
describe('onEscape', () => {
test('should not stop default event, close dropdown, or reset active item when dropdown is closed', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
const instanceMock = sandbox.mock(instance);
sandbox.stub(instance, 'isDropdownOpen').returns(false);
instanceMock.expects('closeDropdown').never();
instanceMock.expects('resetActiveItem').never();
wrapper.simulate('keyDown', {
key: 'Escape',
preventDefault: sandbox.mock().never(),
stopPropagation: sandbox.mock().never(),
});
});
test('should stop default event, close dropdown, and reset active item when dropdown is open', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
const instanceMock = sandbox.mock(instance);
sandbox.stub(instance, 'isDropdownOpen').returns(true);
instanceMock.expects('closeDropdown');
instanceMock.expects('resetActiveItem');
wrapper.simulate('keyDown', {
key: 'Escape',
preventDefault: sandbox.mock(),
stopPropagation: sandbox.mock(),
});
});
test('should not prevent default event and should not stop propagation', () => {
const wrapper = renderEmptyDropdown({
isAlwaysOpen: true,
});
const instance = wrapper.instance();
const instanceMock = sandbox.stub(instance, 'isDropdownOpen').returns(true);
sandbox.mock(instanceMock.closeDropdown).never();
sandbox.mock(instanceMock.resetActiveItem).never();
wrapper.simulate('keyDown', {
key: 'Escape',
preventDefault: sandbox.mock().never(),
stopPropagation: sandbox.mock().never(),
});
});
});
describe('onItemMouseDown', () => {
test('should prevent default when mousedown on item occurs to prevent blur', () => {
const wrapper = renderDropdownWithChildren(['test']);
wrapper.setState({
shouldOpen: true,
});
wrapper.find('li').simulate('mouseDown', {
preventDefault: sandbox.mock(),
});
});
});
describe('onItemMouseEnter', () => {
test('should set correct active item index when hovering over item', () => {
const wrapper = renderDropdownWithChildren(['test']);
wrapper.setState({
shouldOpen: true,
});
wrapper.find('li').simulate('mouseEnter');
expect(wrapper.state('activeItemIndex')).toEqual(0);
});
});
describe('componentDidUpdate()', () => {
[
// No Children
{
children: null,
},
// same children
{
children: ['test'],
},
].forEach(({ children }) => {
test('should not call resetActiveItem() when children have not changed', () => {
const wrapper = renderDropdownWithChildren(children);
const instance = wrapper.instance();
sandbox
.mock(instance)
.expects('resetActiveItem')
.never();
wrapper.setProps({ className: 'test' });
});
});
[
// Children Different Length
{
children: ['hi', 'bye'],
nextChildren: ['hi'],
},
// Next Children
{
children: ['hi'],
nextChildren: ['bye'],
},
].forEach(({ children, nextChildren }) => {
test('should call resetActiveItem() when children have changed', () => {
const wrapper = renderDropdownWithChildren(children);
const instanceMock = sandbox.mock(wrapper.instance());
instanceMock.expects('resetActiveItem');
instanceMock.expects('setActiveItem').never();
wrapper.setProps({
children: Children.map(nextChildren, item => <li key={item}>{item}</li>),
});
});
test('should call setActiveItem() with index 0 when children have changed and shouldSetActiveItemOnOpen is set to true', () => {
const wrapper = renderDropdownWithChildren(children, { shouldSetActiveItemOnOpen: true });
const instanceMock = sandbox.mock(wrapper.instance());
instanceMock.expects('resetActiveItem').never();
instanceMock
.expects('setActiveItem')
.once()
.withArgs(0);
wrapper.setProps({
children: Children.map(nextChildren, item => <li key={item}>{item}</li>),
});
});
});
});
describe('setActiveItem()', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
test('should update activeItemIndex state when called', () => {
const index = 1;
sandbox
.mock(instance)
.expects('setActiveItemID')
.never();
instance.setActiveItem(index);
expect(wrapper.state('activeItemIndex')).toEqual(index);
});
test('should reset active item ID when index is -1', () => {
sandbox
.mock(instance)
.expects('setActiveItemID')
.withArgs(null);
instance.setActiveItem(-1);
});
});
describe('setActiveItemID()', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
const id = 'test123';
test('should update activeItemID state when called', () => {
instance.setActiveItemID(id);
expect(wrapper.state('activeItemID')).toEqual(id);
});
test('should call scrollIntoView', () => {
const scrollIntoView = jest.spyOn(domUtils, 'scrollIntoView');
instance.setActiveItemID(id);
expect(scrollIntoView).toHaveBeenCalled();
});
});
describe('resetActiveItem()', () => {
test('should update activeItemIndex state when called', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
wrapper.setState({
activeItemID: 'test',
activeItemIndex: 1,
});
instance.resetActiveItem();
expect(wrapper.state('activeItemID')).toEqual(null);
expect(wrapper.state('activeItemIndex')).toEqual(-1);
});
});
describe('isDropdownOpen()', () => {
[
// No Children and Open
{
hasChildren: false,
shouldOpen: true,
isAlwaysOpen: false,
exp: false,
},
// With Children and Closed
{
hasChildren: true,
shouldOpen: false,
isAlwaysOpen: false,
exp: false,
},
// No Children and Closed
{
hasChildren: false,
shouldOpen: false,
isAlwaysOpen: false,
exp: false,
},
// With Children and Open
{
hasChildren: true,
shouldOpen: true,
isAlwaysOpen: false,
exp: true,
},
// Forced Open with Children
{
hasChildren: true,
shouldOpen: false,
isAlwaysOpen: true,
exp: true,
},
// Forced Open Without Children
{
hasChildren: false,
shouldOpen: false,
isAlwaysOpen: true,
exp: false,
},
].forEach(({ hasChildren, shouldOpen, isAlwaysOpen, exp }) => {
test('should open dropdown when all conditions are met', () => {
const wrapper = hasChildren
? renderDropdownWithChildren(['test'], { isAlwaysOpen })
: renderEmptyDropdown({ isAlwaysOpen });
const instance = wrapper.instance();
wrapper.setState({ shouldOpen });
expect(instance.isDropdownOpen()).toEqual(exp);
});
});
});
describe('openDropdown()', () => {
test('should set shouldOpen state to true when called', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
instance.openDropdown();
expect(wrapper.state('shouldOpen')).toBe(true);
});
test('should add document click listener', () => {
document.addEventListener = jest.fn();
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
instance.openDropdown();
expect(document.addEventListener.mock.calls.length).toBe(1);
});
test('should activate first item when dropdown is opened and shouldSetActiveItemOnOpen is set to true', () => {
const setActiveItem = jest.fn();
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
instance.setActiveItem = setActiveItem;
wrapper.setProps({ shouldSetActiveItemOnOpen: false });
instance.openDropdown();
expect(setActiveItem).toHaveBeenCalledTimes(0);
instance.closeDropdown();
wrapper.setProps({ shouldSetActiveItemOnOpen: true });
instance.openDropdown();
expect(setActiveItem).toHaveBeenCalledWith(0);
expect(setActiveItem).toHaveBeenCalledTimes(1);
});
});
describe('closeDropdown()', () => {
test('should set shouldOpen state to false when called', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
wrapper.setState({ shouldOpen: true });
instance.closeDropdown();
expect(wrapper.state('shouldOpen')).toBe(false);
});
test('should remove document click listener', () => {
document.removeEventListener = jest.fn();
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
wrapper.setState({ shouldOpen: true });
instance.closeDropdown();
expect(document.removeEventListener.mock.calls.length).toBe(1);
});
});
describe('selectItem()', () => {
test('should call onSelect() with the index and event when prop is specified', () => {
const onSelectSpy = sandbox.spy();
const wrapper = renderEmptyDropdown({ onSelect: onSelectSpy });
const instance = wrapper.instance();
const index = 1;
const event = { type: 'click' };
instance.selectItem(index, event);
expect(onSelectSpy.calledWith(index, event)).toBe(true);
});
test('should call closeDropdown() when called', () => {
const wrapper = renderEmptyDropdown();
const instance = wrapper.instance();
sandbox.mock(instance).expects('closeDropdown');
instance.selectItem(0, {});
});
});
});