box-ui-elements
Version:
Box UI Elements
1,211 lines (997 loc) • 59.3 kB
JavaScript
import * as React from 'react';
import { Set } from 'immutable';
import sinon from 'sinon';
import isEqual from 'lodash/isEqual';
import makeSelectable from '../makeSelectable';
import shiftSelect from '../shiftSelect';
const sandbox = sinon.sandbox.create();
jest.mock('../shiftSelect');
jest.useFakeTimers();
describe('components/table/makeSelectable', () => {
const Table = (props = {}) => <table {...props} />;
const SelectableTable = makeSelectable(Table);
const data = ['a', 'b', 'c', 'd', 'e'];
const getWrapper = (props = {}) =>
shallow(<SelectableTable onSelect={sandbox.stub()} data={data} selectedItems={[]} enableHotkeys {...props} />);
const testClassNamePreventsArrowNavigation = (className, hotKey, isGridView = false) => {
const wrapper = getWrapper({
gridColumnCount: 3,
isGridView,
selectedItems: ['a'],
});
jest.spyOn(document, 'querySelector').mockImplementation(selector => className === selector);
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(undefined);
};
afterEach(() => {
jest.clearAllTimers();
jest.clearAllMocks();
sandbox.verifyAndRestore();
});
describe('componentDidMount()', () => {
test('should add keypress listener', () => {
document.addEventListener = jest.fn();
const instance = getWrapper().instance();
expect(document.addEventListener).toBeCalledWith('keypress', instance.handleKeyboardSearch);
});
});
describe('componentDidUpdate()', () => {
test('should call onFocus handler when focused index changes', () => {
const onFocus = jest.fn();
const wrapper = getWrapper({
onFocus,
});
wrapper.setState({ focusedIndex: 3 });
expect(onFocus).toBeCalledWith(3);
});
});
describe('componentWillUnmount()', () => {
test('should remove keypress listener', () => {
document.removeEventListener = jest.fn();
const wrapper = getWrapper();
const instance = wrapper.instance();
wrapper.unmount();
expect(document.removeEventListener).toBeCalledWith('keypress', instance.handleKeyboardSearch);
});
});
describe('onSelect()', () => {
test('should set previousIndex to state.focusedIndex, set state.focusedIndex, and call onSelect', () => {
const wrapper = getWrapper({
onSelect: sandbox.mock().withArgs(['c']),
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
instance.onSelect(new Set(['c']), 2);
expect(instance.previousIndex).toEqual(1);
expect(wrapper.state('focusedIndex')).toEqual(2);
});
test('should call onSelect with Immutable Set when selectedItems is given as a set', () => {
const wrapper = getWrapper({
selectedItems: new Set(['a']),
onSelect: items => {
expect(items.equals(new Set(['c']))).toBe(true);
},
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
instance.onSelect(new Set(['c']), 2);
});
});
describe('getProcessedProps()', () => {
test('should return selectedItems as Immutable object when given as plain JS', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
const instance = wrapper.instance();
const processedProps = instance.getProcessedProps();
expect(processedProps.selectedItems.equals(new Set(['a']))).toBe(true);
});
});
describe('selectToggle()', () => {
test('should remove item from selection when it is selected', () => {
const wrapper = getWrapper({
selectedItems: ['a', 'b'],
});
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['a']))).toBe(true);
expect(focusedIndex).toEqual(1);
};
instance.selectToggle(1);
});
test('should add item to selection when it is not selected', () => {
const wrapper = getWrapper({
selectedItems: [],
});
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['b']))).toBe(true);
expect(focusedIndex).toEqual(1);
};
instance.selectToggle(1);
});
});
describe('selectRange()', () => {
afterEach(() => {
shiftSelect.mockReset();
});
test('should call shiftSelect with correct args', () => {
const selectedItems = ['a', 'b', 'c'];
const focusedIndex = 1;
const rowIndex = 2;
const anchorIndex = 3;
// expected computed value
const selectedRows = new Set([0, 1, 2]);
shiftSelect.mockImplementation(() => new Set([1, 2, 3]));
const wrapper = getWrapper({
selectedItems,
});
const instance = wrapper.instance();
instance.previousIndex = focusedIndex;
instance.anchorIndex = anchorIndex;
instance.onSelect = newSelectedItems => {
// newSelectedItems should be the item-mapped equivalent of
// [1, 2, 3] returned from the call to shiftSelect
expect(newSelectedItems.equals(new Set(['b', 'c', 'd']))).toBe(true);
};
instance.selectRange(rowIndex);
expect(shiftSelect).toHaveBeenCalledWith(selectedRows, focusedIndex, rowIndex, anchorIndex);
});
test('should not change selection if clicking on same row', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
instance.previousIndex = 1;
instance.selectRange(1);
expect(shiftSelect).not.toHaveBeenCalled();
});
});
describe('selectOne()', () => {
test('should not change selection when clicking on the only already-selected row', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
const instance = wrapper.instance();
instance.onSelect = sandbox.mock().never();
instance.selectOne(0);
});
test('should set selection to contain only the target item', () => {
const wrapper = getWrapper({
selectedItems: ['a', 'b'],
});
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['c']))).toBe(true);
expect(focusedIndex).toEqual(2);
};
instance.selectOne(2);
});
});
describe('handleRowClick()', () => {
test('should call selectToggle() when meta key pressed', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const index = 1;
instance.selectToggle = sandbox.mock().withArgs(index);
instance.handleRowClick({ metaKey: true }, index);
});
test('should call selectToggle() when ctrl key pressed', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const index = 1;
instance.selectToggle = sandbox.mock().withArgs(index);
instance.handleRowClick({ ctrlKey: true }, index);
});
test('should call selectRange() when shift key pressed', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const index = 1;
instance.selectRange = sandbox.mock().withArgs(index);
instance.handleRowClick({ shiftKey: true }, index);
});
test('should call selectOne() when no modifier key pressed', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const index = 1;
instance.selectOne = sandbox.mock().withArgs(index);
instance.handleRowClick({}, index);
});
});
describe('handleCheckboxClick()', () => {
test('should call selectRange() when shift key pressed', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const index = 1;
instance.selectRange = sandbox.mock().withArgs(index);
instance.handleCheckboxClick({ nativeEvent: { shiftKey: true } }, index);
});
test('should call selectToggle() when no modifier key pressed', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const index = 1;
instance.selectToggle = sandbox.mock().withArgs(index);
instance.handleCheckboxClick({ nativeEvent: {} }, index);
});
});
describe('handleRowFocus()', () => {
test('should call onSelect() with correct args', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['a']))).toBe(true);
expect(focusedIndex).toEqual(2);
};
instance.handleRowFocus({}, 2);
});
});
describe('handleShiftKeyDown()', () => {
test('should be no-op when target is the boundary and already selected', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
onSelect: sandbox.mock().never(),
});
wrapper.setState({ focusedIndex: 0 });
wrapper.instance().handleShiftKeyDown(0, 0);
});
test('should select target when it is not already selected and source is selected', () => {
const wrapper = getWrapper({
selectedItems: ['b'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['a', 'b']))).toBe(true);
expect(focusedIndex).toEqual(0);
};
instance.handleShiftKeyDown(0, 0);
});
test('should deselect source when both source and target are selected', () => {
const wrapper = getWrapper({
selectedItems: ['a', 'b'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['b']))).toBe(true);
expect(focusedIndex).toEqual(1);
};
instance.handleShiftKeyDown(1, 4);
});
test('should select source when target is selected but not source', () => {
const wrapper = getWrapper({
selectedItems: ['b'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['a', 'b']))).toBe(true);
expect(focusedIndex).toEqual(1);
};
instance.handleShiftKeyDown(1, 0);
});
test('should select both source and target when neither are selected', () => {
const wrapper = getWrapper({
selectedItems: [],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(['a', 'b']))).toBe(true);
expect(focusedIndex).toEqual(1);
};
instance.handleShiftKeyDown(1, 4);
});
});
describe('isContiguousSelection()', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const { isContiguousSelection } = instance;
test('returns true if source is less than target and source - 1 is selected', () => {
expect(isContiguousSelection(Set([0]), 1, 2)).toEqual(true);
});
test('returns false if source is less than target and source - 1 is not selected', () => {
expect(isContiguousSelection(Set(), 1, 2)).toEqual(false);
});
test('returns true if source is greater than target and source + 1 is selected', () => {
expect(isContiguousSelection(Set([2]), 1, 0)).toEqual(true);
});
test('returns false if source is greater than target and source + 1 is not selected', () => {
expect(isContiguousSelection(Set(), 1, 2)).toEqual(false);
});
test('returns false if source and target are equal', () => {
expect(isContiguousSelection(Set([0, 2]), 1, 1)).toEqual(false);
});
});
describe('handleShiftKeyDownForGrid()', () => {
afterEach(() => {
shiftSelect.mockReset();
});
test('should select target when it is not already selected', () => {
const selectedItems = [];
const focusedIndex = 1;
const targetIndex = 0;
const anchorIndex = 1;
const wrapper = getWrapper({
selectedItems,
});
wrapper.setState({ focusedIndex });
const instance = wrapper.instance();
shiftSelect.mockImplementation(() => new Set([0]));
instance.onSelect = (funcSelectedItems, funcFocusedIndex) => {
expect(funcSelectedItems.equals(new Set(['a']))).toBe(true);
expect(funcFocusedIndex).toEqual(0);
};
instance.handleShiftKeyDownForGrid(targetIndex);
expect(shiftSelect).toHaveBeenCalledWith(Set(selectedItems), focusedIndex, targetIndex, anchorIndex);
});
test('should deselect source when both source and target are selected', () => {
const selectedItems = ['a', 'b'];
const focusedIndex = 0;
const targetIndex = 1;
const anchorIndex = 0;
const wrapper = getWrapper({
selectedItems,
});
wrapper.setState({ focusedIndex });
const instance = wrapper.instance();
shiftSelect.mockImplementation(() => new Set([1]));
instance.onSelect = (funcSelectedItems, funcFocusedIndex) => {
expect(funcSelectedItems.equals(new Set(['b']))).toBe(true);
expect(funcFocusedIndex).toEqual(1);
};
instance.handleShiftKeyDownForGrid(targetIndex);
expect(shiftSelect).toHaveBeenCalledWith(Set([0, 1]), focusedIndex, targetIndex, anchorIndex);
});
test('should select source when target is selected but not source', () => {
const selectedItems = ['b'];
const focusedIndex = 0;
const targetIndex = 1;
const anchorIndex = 0;
const wrapper = getWrapper({
selectedItems,
});
wrapper.setState({ focusedIndex });
const instance = wrapper.instance();
shiftSelect.mockImplementation(() => new Set([0, 1]));
instance.onSelect = (funcSelectedItems, funcFocusedIndex) => {
expect(funcSelectedItems.equals(new Set(['a', 'b']))).toBe(true);
expect(funcFocusedIndex).toEqual(1);
};
instance.handleShiftKeyDownForGrid(targetIndex);
expect(shiftSelect).toHaveBeenCalledWith(Set([1]), focusedIndex, targetIndex, anchorIndex);
});
test('should select source and target and set anchor when both are unselected', () => {
const selectedItems = [];
const focusedIndex = 2;
const targetIndex = 3;
const anchorIndex = 2;
const wrapper = getWrapper({
selectedItems,
});
wrapper.setState({ focusedIndex });
const instance = wrapper.instance();
shiftSelect.mockImplementation(() => new Set([2, 3]));
instance.onSelect = (funcSelectedItems, funcFocusedIndex) => {
expect(funcSelectedItems.equals(new Set(['c', 'd']))).toBe(true);
expect(funcFocusedIndex).toEqual(targetIndex);
};
instance.handleShiftKeyDownForGrid(targetIndex);
expect(shiftSelect).toHaveBeenCalledWith(Set([]), focusedIndex, targetIndex, anchorIndex);
});
test('should select source and target and not set anchor when both are unselected and it is a continuation', () => {
const selectedItems = ['a', 'b'];
const focusedIndex = 2;
const targetIndex = 3;
const anchorIndex = 0;
const wrapper = getWrapper({
selectedItems,
});
wrapper.setState({ focusedIndex });
const instance = wrapper.instance();
shiftSelect.mockImplementation(() => new Set([0, 1, 2, 3]));
instance.onSelect = (funcSelectedItems, funcFocusedIndex) => {
expect(funcSelectedItems.equals(new Set(['a', 'b', 'c', 'd']))).toBe(true);
expect(funcFocusedIndex).toEqual(targetIndex);
};
instance.handleShiftKeyDownForGrid(targetIndex);
expect(shiftSelect).toHaveBeenCalledWith(Set([0, 1]), focusedIndex, targetIndex, anchorIndex);
});
test('should set targetIndex to 0 when it is below 0', () => {
const selectedItems = ['b'];
const focusedIndex = 1;
const targetIndex = -1;
const anchorIndex = 0;
const wrapper = getWrapper({
selectedItems,
});
wrapper.setState({ focusedIndex });
const instance = wrapper.instance();
shiftSelect.mockImplementation(() => new Set([0, 1]));
instance.handleShiftKeyDownForGrid(targetIndex);
expect(shiftSelect).toHaveBeenCalledWith(Set([1]), focusedIndex, 0, anchorIndex);
});
test('should set targetIndex to max index when it is greater than the max index', () => {
const selectedItems = ['b'];
const focusedIndex = 1;
const targetIndex = data.length;
const anchorIndex = 0;
const wrapper = getWrapper({
selectedItems,
});
wrapper.setState({ focusedIndex });
const instance = wrapper.instance();
shiftSelect.mockImplementation(() => new Set([0, 1]));
instance.handleShiftKeyDownForGrid(targetIndex);
expect(shiftSelect).toHaveBeenCalledWith(Set([1]), focusedIndex, data.length - 1, anchorIndex);
});
});
describe('clearFocus()', () => {
test('should clear focus', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
wrapper.setState({ focusedIndex: 1 });
instance.clearFocus();
expect(wrapper.state('focusedIndex')).toBeUndefined();
});
});
describe('blur detection', () => {
test('should not set timer when table does not have focus', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
instance.clearFocus = jest.fn();
wrapper.setState({ focusedIndex: undefined });
instance.handleTableBlur();
jest.runAllTimers();
expect(instance.blurTimerID).toBeNull();
expect(instance.clearFocus).toBeCalledTimes(0);
});
test('should clear focus after timeout expires', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
instance.clearFocus = jest.fn();
wrapper.setState({ focusedIndex: 1 });
instance.handleTableBlur();
jest.runAllTimers();
expect(instance.clearFocus).toBeCalledTimes(1);
});
test('should not clear focus if focus is regained before timeout expires', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
instance.clearFocus = jest.fn();
wrapper.setState({ focusedIndex: 1 });
instance.handleTableBlur();
instance.handleTableFocus(); // regain focus
jest.runAllTimers();
expect(instance.clearFocus).toBeCalledTimes(0);
});
});
describe('keyboard shortcuts', () => {
test('should set and return this.hotkeys when this.hotkeys is null', () => {
const instance = getWrapper({}).instance();
// should be initially null
instance.hotkeys = null;
const shortcuts = instance.getHotkeyConfigs();
// should not be null anymore
expect(instance.hotkeys).not.toBeNull();
expect(shortcuts).toEqual(instance.hotkeys);
});
test('should use correct description and hotkey type for all shortcuts', () => {
const hotkeyType = 'item selection';
const instance = getWrapper({
hotkeyType,
}).instance();
const shortcuts = instance.getHotkeyConfigs();
shortcuts.forEach(shortcut => {
expect(shortcut.description).toBeTruthy();
expect(shortcut.type).toEqual(hotkeyType);
});
});
describe('meta+a / ctrl+a', () => {
const hotKey = ['meta+a', 'ctrl+a'];
test('should call event.preventDefault() and select all items', () => {
const wrapper = getWrapper({
selectedItems: [],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set(data))).toBe(true);
expect(focusedIndex).toEqual(1);
};
const shortcut = instance.getHotkeyConfigs().find(h => isEqual(h.get('key'), hotKey));
shortcut.handler({ preventDefault: sandbox.mock() });
});
});
describe('esc', () => {
const hotKey = 'esc';
test('should set selection to empty', () => {
const wrapper = getWrapper({
selectedItems: ['a', 'b', 'c'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
instance.onSelect = (selectedItems, focusedIndex) => {
expect(selectedItems.equals(new Set([]))).toBe(true);
expect(focusedIndex).toEqual(1);
};
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
});
describe('shift+x', () => {
const hotKey = 'shift+x';
test('should be no-op when focusedIndex is undefined', () => {
const wrapper = getWrapper({
onSelect: sandbox.mock().never(),
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should call selectToggle on focused item', () => {
const wrapper = getWrapper({
focusedItem: 'b',
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
instance.selectToggle = sandbox.mock().withArgs(1);
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
});
describe('ListView specific', () => {
describe('down', () => {
const hotKey = 'down';
test('should set focus to first row when no currently focused item', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should call event.preventDefault() and set focus to next item', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(1);
});
test('should not focus on an index higher than the highest index in the table', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 4 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(4);
});
test.each([['flyout-overlay'], ['dropdown-menu-element']])(
'should not set focus if element with class %s is rendered',
className => {
testClassNamePreventsArrowNavigation(className, hotKey);
},
);
});
describe('up', () => {
const hotKey = 'up';
test('should call event.preventDefault() and call onSelect with new focused item', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should not focus on an index lower than 0', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test.each([['flyout-overlay'], ['dropdown-menu-element']])(
'should not set focus if element with class %s is rendered',
className => {
testClassNamePreventsArrowNavigation(className, hotKey);
},
);
});
describe('shift+down', () => {
const hotKey = 'shift+down';
test('should be no-op when focusedIndex is undefined', () => {
const wrapper = getWrapper({
onSelect: sandbox.mock().never(),
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should call handleShiftKeyDown() with the index of the next item in the table', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
instance.handleShiftKeyDown = sandbox.mock().withArgs(1, data.length - 1);
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should not call handleShiftKeyDown() with an index greater than the highest index', () => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 4 });
const instance = wrapper.instance();
instance.handleShiftKeyDown = sandbox.mock().withArgs(data.length - 1, data.length - 1);
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
});
describe('shift+up', () => {
const hotKey = 'shift+up';
test('should be no-op when focusedIndex is undefined', () => {
const wrapper = getWrapper({
onSelect: sandbox.mock().never(),
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should call handleShiftKeyDown() with index of the next item in the table', () => {
const wrapper = getWrapper({
focusedItem: 'b',
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
instance.handleShiftKeyDown = sandbox.mock().withArgs(0, 0);
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should not call handleShiftKeyDown() with an index lower than 0', () => {
const wrapper = getWrapper({
focusedItem: 'a',
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
instance.handleShiftKeyDown = sandbox.mock().withArgs(0, 0);
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
});
describe('QuickSearch specific', () => {
const hotKey = 'down';
test.each([
['quickSearchResultItemFooBar'],
['quickSearchRecentItemFooBar'],
['quickSearchQueryFooter'],
['bp_text_button_module'],
])('should not set focus if target is %s', targetClass => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ target: { className: targetClass } });
expect(wrapper.state('focusedIndex')).toEqual(undefined);
});
test.each([['bpSmallListItem'], ['radixCollectionItem']])(
'should not set focus if dataset contains %s',
datasetKey => {
const wrapper = getWrapper({
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
const event = { target: { dataset: { [datasetKey]: true } } };
shortcut.handler(event);
expect(wrapper.state('focusedIndex')).toEqual(undefined);
},
);
});
});
describe('GridView specific', () => {
const gridColumnCount = 3;
describe('right', () => {
const hotKey = 'right';
test('should set focus to first row when no currently focused item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should not set focus to first row if target has role of slider', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ target: { role: 'slider' } });
expect(wrapper.state('focusedIndex')).toEqual(undefined);
});
test('should call event.preventDefault() and set focus to next item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(1);
});
test('should not focus on an index higher than the highest index in the table', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 4 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(4);
});
test.each([['flyout-overlay'], ['dropdown-menu-element']])(
'should not set focus if element with class %s is rendered',
className => {
testClassNamePreventsArrowNavigation(className, hotKey, true);
},
);
});
describe('left', () => {
const hotKey = 'left';
test('should not set focus to first row if target has role of slider', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ target: { role: 'slider' } });
expect(wrapper.state('focusedIndex')).toEqual(undefined);
});
test('should call event.preventDefault() and call onSelect with new focused item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should call event.preventDefault() and set focus to previous item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should not focus on an index lower than 0', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test.each([['flyout-overlay'], ['dropdown-menu-element']])(
'should not set focus if element with class %s is rendered',
className => {
testClassNamePreventsArrowNavigation(className, hotKey, true);
},
);
});
describe('down', () => {
const hotKey = 'down';
test('should set focus to first row when no currently focused item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should not set focus to first row if target has role of slider', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ target: { role: 'slider' } });
expect(wrapper.state('focusedIndex')).toEqual(undefined);
});
test('should call event.preventDefault() and set focus to next row item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(gridColumnCount);
});
test('should not focus on an index higher than the highest index in the table', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 4 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(4);
});
test.each([['flyout-overlay'], ['dropdown-menu-element']])(
'should not set focus if element with class %s is rendered',
className => {
testClassNamePreventsArrowNavigation(className, hotKey, true);
},
);
});
describe('up', () => {
const hotKey = 'up';
test('should not set focus to first row if target has role of slider', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ target: { role: 'slider' } });
expect(wrapper.state('focusedIndex')).toEqual(undefined);
});
test('should call event.preventDefault() and call onSelect with new focused item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 1 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should call event.preventDefault() and set focus to previous row item', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: gridColumnCount });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.mock() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test('should not focus on an index lower than 0', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler({ preventDefault: sandbox.stub() });
expect(wrapper.state('focusedIndex')).toEqual(0);
});
test.each([['flyout-overlay'], ['dropdown-menu-element']])(
'should not set focus if element with class %s is rendered',
className => {
testClassNamePreventsArrowNavigation(className, hotKey, true);
},
);
});
describe('shift+right', () => {
const hotKey = 'shift+right';
test('should be no-op when focusedIndex is undefined', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
onSelect: sandbox.mock().never(),
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should call handleShiftKeyDownForGrid() with the index of the next item in the table', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 0 });
const instance = wrapper.instance();
instance.handleShiftKeyDownForGrid = sandbox.mock().withArgs(1);
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should not call handleShiftKeyDownForGrid() with an index greater than the highest index', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
selectedItems: ['a'],
});
wrapper.setState({ focusedIndex: 4 });
const instance = wrapper.instance();
instance.handleShiftKeyDownForGrid = sandbox.mock().withArgs(data.length - 1);
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
});
describe('shift+left', () => {
const hotKey = 'shift+left';
test('should be no-op when focusedIndex is undefined', () => {
const wrapper = getWrapper({
gridColumnCount,
isGridView: true,
onSelect: sandbox.mock().never(),
});
wrapper.setState({ focusedIndex: undefined });
const instance = wrapper.instance();
const shortcut = instance.getHotkeyConfigs().find(h => h.get('key') === hotKey);
shortcut.handler();
});
test('should call handleShiftKeyDownF