123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- import {mountWithTheme} from 'sentry-test/enzyme';
- import AutoComplete from 'sentry/components/autoComplete';
- const items = [
- {
- name: 'Apple',
- },
- {
- name: 'Pineapple',
- },
- {
- name: 'Orange',
- },
- ];
- /**
- * For every render, we push all injected params into `autoCompleteState`, we probably want to
- * assert against those instead of the wrapper's state since component state will be different if we have
- * "controlled" props where <AutoComplete> does not handle state
- */
- describe('AutoComplete', function () {
- let wrapper;
- let input;
- let autoCompleteState = [];
- const mocks = {
- onSelect: jest.fn(),
- onClose: jest.fn(),
- onOpen: jest.fn(),
- };
- const createWrapper = props => {
- autoCompleteState = [];
- Object.keys(mocks).forEach(key => mocks[key].mockReset());
- wrapper = mountWithTheme(
- <AutoComplete {...mocks} itemToString={item => item.name} {...props}>
- {injectedProps => {
- const {
- getRootProps,
- getInputProps,
- getMenuProps,
- getItemProps,
- inputValue,
- highlightedIndex,
- isOpen,
- } = injectedProps;
- // This is purely for testing
- autoCompleteState.push(injectedProps);
- return (
- <div {...getRootProps({style: {position: 'relative'}})}>
- <input {...getInputProps({})} />
- {isOpen && (
- <div
- {...getMenuProps({
- style: {
- boxShadow:
- '0 1px 4px 1px rgba(47,40,55,0.08), 0 4px 16px 0 rgba(47,40,55,0.12)',
- position: 'absolute',
- backgroundColor: 'white',
- padding: '0',
- },
- })}
- >
- <ul>
- {items
- .filter(
- item =>
- item.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1
- )
- .map((item, index) => (
- <li
- key={item.name}
- {...getItemProps({
- item,
- index,
- style: {
- cursor: 'pointer',
- padding: '6px 12px',
- backgroundColor:
- index === highlightedIndex
- ? 'rgba(0, 0, 0, 0.02)'
- : undefined,
- },
- })}
- >
- {item.name}
- </li>
- ))}
- </ul>
- </div>
- )}
- </div>
- );
- }}
- </AutoComplete>
- );
- input = wrapper.find('input');
- return wrapper;
- };
- describe('Uncontrolled', function () {
- beforeEach(() => {
- wrapper = createWrapper();
- });
- it('shows dropdown menu when input has focus', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.find('li')).toHaveLength(3);
- });
- it('only tries to close once if input is blurred and click outside occurs', async function () {
- jest.useFakeTimers();
- input.simulate('focus');
- input.simulate('blur');
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.find('li')).toHaveLength(3);
- wrapper.find('DropdownMenu').prop('onClickOutside')();
- jest.runAllTimers();
- await Promise.resolve();
- wrapper.update();
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('only calls onClose dropdown menu when input is blurred', function () {
- jest.useFakeTimers();
- input.simulate('focus');
- input.simulate('blur');
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.find('li')).toHaveLength(3);
- jest.runAllTimers();
- wrapper.update();
- expect(wrapper.state('isOpen')).toBe(false);
- expect(wrapper.find('li')).toHaveLength(0);
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('can close dropdown menu when Escape is pressed', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('keyDown', {key: 'Escape'});
- expect(wrapper.state('isOpen')).toBe(false);
- });
- it('can open and close dropdown menu using injected actions', function () {
- const [injectedProps] = autoCompleteState;
- injectedProps.actions.open();
- expect(wrapper.state('isOpen')).toBe(true);
- expect(mocks.onOpen).toHaveBeenCalledTimes(1);
- injectedProps.actions.close();
- expect(wrapper.state('isOpen')).toBe(false);
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('reopens dropdown menu after Escape is pressed and input is changed', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('keyDown', {key: 'Escape'});
- expect(wrapper.state('isOpen')).toBe(false);
- input.simulate('change', {target: {value: 'a'}});
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.instance().items.size).toBe(3);
- });
- it('reopens dropdown menu after item is selected and then input is changed', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('change', {target: {value: 'eapp'}});
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.instance().items.size).toBe(1);
- input.simulate('keyDown', {key: 'Enter'});
- expect(wrapper.state('isOpen')).toBe(false);
- input.simulate('change', {target: {value: 'app'}});
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.instance().items.size).toBe(2);
- });
- it('selects dropdown item by clicking and sets input to selected value', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.instance().items.size).toBe(3);
- wrapper.find('li').at(1).simulate('click');
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[1],
- expect.objectContaining({inputValue: '', highlightedIndex: 0}),
- expect.anything()
- );
- expect(wrapper.state('inputValue')).toBe('Pineapple');
- expect(wrapper.instance().items.size).toBe(0);
- });
- it('can navigate dropdown items with keyboard and select with "Enter" keypress', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(2);
- expect(wrapper.instance().items.size).toBe(3);
- input.simulate('keyDown', {key: 'Enter'});
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[2],
- expect.objectContaining({inputValue: '', highlightedIndex: 2}),
- expect.anything()
- );
- expect(wrapper.instance().items.size).toBe(0);
- expect(wrapper.state('inputValue')).toBe('Orange');
- });
- it('respects list bounds when navigating filtered items with arrow keys', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(2);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(2);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.instance().items.size).toBe(3);
- });
- it('can filter items and then navigate with keyboard', function () {
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.instance().items.size).toBe(3);
- input.simulate('change', {target: {value: 'a'}});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.state('inputValue')).toBe('a');
- // Apple, pineapple, orange
- expect(wrapper.instance().items.size).toBe(3);
- input.simulate('change', {target: {value: 'ap'}});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.state('inputValue')).toBe('ap');
- expect(autoCompleteState[autoCompleteState.length - 1].inputValue).toBe('ap');
- // Apple, pineapple
- expect(wrapper.instance().items.size).toBe(2);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- expect(wrapper.instance().items.size).toBe(2);
- input.simulate('keyDown', {key: 'Enter'});
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[1],
- expect.objectContaining({inputValue: 'ap', highlightedIndex: 1}),
- expect.anything()
- );
- expect(wrapper.instance().items.size).toBe(0);
- expect(wrapper.state('inputValue')).toBe('Pineapple');
- });
- it('can reset input when menu closes', function () {
- jest.useFakeTimers();
- wrapper.setProps({resetInputOnClose: true});
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('change', {target: {value: 'a'}});
- expect(wrapper.state('inputValue')).toBe('a');
- input.simulate('blur');
- jest.runAllTimers();
- expect(wrapper.state('isOpen')).toBe(false);
- expect(wrapper.state('inputValue')).toBe('');
- });
- });
- describe('Controlled', function () {
- beforeEach(function () {
- wrapper = createWrapper({isOpen: true});
- });
- it('has dropdown menu initially open', function () {
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.find('li')).toHaveLength(3);
- });
- it('closes when props change', function () {
- wrapper.setProps({isOpen: false});
- expect(wrapper.state('isOpen')).toBe(true);
- wrapper.update();
- // Menu should be closed
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.find('li')).toHaveLength(0);
- });
- it('remains closed when input is focused, but calls `onOpen`', function () {
- wrapper = createWrapper({isOpen: false});
- jest.useFakeTimers();
- expect(wrapper.state('isOpen')).toBe(false);
- input.simulate('focus');
- jest.runAllTimers();
- wrapper.update();
- expect(wrapper.state('isOpen')).toBe(false);
- expect(wrapper.find('li')).toHaveLength(0);
- expect(mocks.onOpen).toHaveBeenCalledTimes(1);
- });
- it('remains open when input focus/blur events occur, but calls `onClose`', function () {
- jest.useFakeTimers();
- input.simulate('focus');
- input.simulate('blur');
- jest.runAllTimers();
- wrapper.update();
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.find('li')).toHaveLength(3);
- // This still gets called even though menu is open
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('calls onClose when Escape is pressed', function () {
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('keyDown', {key: 'Escape'});
- expect(wrapper.state('isOpen')).toBe(true);
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('does not open and close dropdown menu using injected actions', function () {
- const [injectedProps] = autoCompleteState;
- injectedProps.actions.open();
- expect(wrapper.state('isOpen')).toBe(true);
- expect(mocks.onOpen).toHaveBeenCalledTimes(1);
- injectedProps.actions.close();
- expect(wrapper.state('isOpen')).toBe(true);
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('onClose is called after item is selected', function () {
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('change', {target: {value: 'eapp'}});
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.instance().items.size).toBe(1);
- input.simulate('keyDown', {key: 'Enter'});
- expect(wrapper.state('isOpen')).toBe(true);
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('selects dropdown item by clicking and sets input to selected value', function () {
- expect(wrapper.instance().items.size).toBe(3);
- wrapper.find('li').at(1).simulate('click');
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[1],
- expect.objectContaining({inputValue: '', highlightedIndex: 0}),
- expect.anything()
- );
- expect(wrapper.state('inputValue')).toBe('Pineapple');
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- });
- it('can navigate dropdown items with keyboard and select with "Enter" keypress', function () {
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(2);
- expect(wrapper.instance().items.size).toBe(3);
- input.simulate('keyDown', {key: 'Enter'});
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[2],
- expect.objectContaining({inputValue: '', highlightedIndex: 2}),
- expect.anything()
- );
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- expect(wrapper.state('inputValue')).toBe('Orange');
- });
- it('respects list bounds when navigating filtered items with arrow keys', function () {
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(2);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(2);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- input.simulate('keyDown', {key: 'ArrowUp'});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.instance().items.size).toBe(3);
- });
- it('can filter items and then navigate with keyboard', function () {
- expect(wrapper.state('isOpen')).toBe(true);
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.instance().items.size).toBe(3);
- input.simulate('change', {target: {value: 'a'}});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.state('inputValue')).toBe('a');
- // Apple, pineapple, orange
- expect(wrapper.instance().items.size).toBe(3);
- input.simulate('change', {target: {value: 'ap'}});
- expect(wrapper.state('highlightedIndex')).toBe(0);
- expect(wrapper.state('inputValue')).toBe('ap');
- expect(autoCompleteState[autoCompleteState.length - 1].inputValue).toBe('ap');
- // Apple, pineapple
- expect(wrapper.instance().items.size).toBe(2);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- expect(wrapper.instance().items.size).toBe(2);
- input.simulate('keyDown', {key: 'Enter'});
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[1],
- expect.objectContaining({inputValue: 'ap', highlightedIndex: 1}),
- expect.anything()
- );
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- expect(wrapper.state('inputValue')).toBe('Pineapple');
- });
- });
- it('selects using enter key', function () {
- wrapper = createWrapper({isOpen: true, shouldSelectWithEnter: false});
- input.simulate('change', {target: {value: 'pine'}});
- input.simulate('keyDown', {key: 'Enter'});
- expect(mocks.onSelect).not.toHaveBeenCalled();
- wrapper = createWrapper({isOpen: true, shouldSelectWithEnter: true});
- input.simulate('change', {target: {value: 'pine'}});
- input.simulate('keyDown', {key: 'Enter'});
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[1],
- expect.objectContaining({inputValue: 'pine', highlightedIndex: 0}),
- expect.anything()
- );
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- expect(wrapper.state('inputValue')).toBe('Pineapple');
- });
- it('selects using tab key', function () {
- wrapper = createWrapper({isOpen: true, shouldSelectWithTab: false});
- input.simulate('change', {target: {value: 'pine'}});
- input.simulate('keyDown', {key: 'Tab'});
- expect(mocks.onSelect).not.toHaveBeenCalled();
- wrapper = createWrapper({isOpen: true, shouldSelectWithTab: true});
- input.simulate('change', {target: {value: 'pine'}});
- input.simulate('keyDown', {key: 'Tab'});
- expect(mocks.onSelect).toHaveBeenCalledWith(
- items[1],
- expect.objectContaining({inputValue: 'pine', highlightedIndex: 0}),
- expect.anything()
- );
- expect(mocks.onClose).toHaveBeenCalledTimes(1);
- expect(wrapper.state('inputValue')).toBe('Pineapple');
- });
- it('does not reset highlight state if `closeOnSelect` is false and we select a new item', function () {
- wrapper = createWrapper({closeOnSelect: false});
- jest.useFakeTimers();
- input.simulate('focus');
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(1);
- // Select item
- input.simulate('keyDown', {key: 'Enter'});
- // Should still remain open with same highlightedIndex
- expect(wrapper.state('highlightedIndex')).toBe(1);
- expect(wrapper.state('isOpen')).toBe(true);
- input.simulate('keyDown', {key: 'ArrowDown'});
- expect(wrapper.state('highlightedIndex')).toBe(2);
- // Select item
- input.simulate('keyDown', {key: 'Enter'});
- // Should still remain open with same highlightedIndex
- expect(wrapper.state('highlightedIndex')).toBe(2);
- expect(wrapper.state('isOpen')).toBe(true);
- });
- });
|