UNPKG

box-ui-elements-mlh

Version:
1,345 lines (1,095 loc) 52.2 kB
import React from 'react'; import sinon from 'sinon'; import { FormattedMessage } from 'react-intl'; import { scrollIntoView } from '../../../utils/dom'; import { BaseSelectFieldBase as BaseSelectField } from '../BaseSelectField'; import { OVERLAY_SCROLLABLE_CLASS } from '../SelectFieldDropdown'; import { ARROW_DOWN, ARROW_UP, ENTER, ESCAPE, SPACE, TAB } from '../../../common/keyboard-events'; import CLEAR from '../constants'; import messages from '../messages'; const sandbox = sinon.sandbox.create(); jest.mock('../../../utils/dom', () => ({ scrollIntoView: jest.fn(), })); describe('components/select-field/BaseSelectField', () => { afterEach(() => { sandbox.verifyAndRestore(); jest.clearAllMocks(); }); const intl = { formatMessage: jest.fn(), }; const options = [ { displayText: 'Any Type', value: '' }, { displayText: 'Audio', value: 'audio' }, { displayText: 'Documents', value: 'document' }, { displayText: 'Videos', value: 'video' }, ]; const onOptionSelectSpy = sandbox.stub(); const shallowRenderSelectField = props => shallow( <BaseSelectField intl={intl} isDisabled={false} onChange={() => {}} onOptionSelect={onOptionSelectSpy} options={options} shouldShowClearOption={false} {...props} />, ); describe('renderButtonText()', () => { test('should render placeholder when there are no selected items and placeholder has been specified', () => { const wrapper = shallowRenderSelectField({ placeholder: 'place me', }); const instance = wrapper.instance(); const buttonText = instance.renderButtonText(); expect(buttonText).toEqual('place me'); }); test('should prioritize placeholder over user specified title when there are no selected items and both placeholder and title have been passed', () => { const wrapper = shallowRenderSelectField({ placeholder: 'place me', title: 'foo', }); const instance = wrapper.instance(); const buttonText = instance.renderButtonText(); expect(buttonText).toEqual('place me'); }); test('should return title prop when passed', () => { const wrapper = shallowRenderSelectField({ title: 'foo' }); const instance = wrapper.instance(); const buttonText = instance.renderButtonText(); expect(buttonText).toEqual('foo'); }); test('should return auto-generated title of concatenated selected displayTexts', () => { const wrapper = shallowRenderSelectField({ selectedValues: ['audio', 'document'], }); const instance = wrapper.instance(); const buttonText = instance.renderButtonText(); expect(buttonText).toEqual('Audio, Documents'); }); }); describe('renderSelectButton()', () => { test('should render select button should initially render with appropriate props and title text', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const buttonWrapper = wrapper.find('PopperComponent').childAt(0); expect(buttonWrapper.prop('aria-activedescendant')).toEqual(null); expect(buttonWrapper.prop('aria-autocomplete')).toEqual('list'); expect(buttonWrapper.prop('aria-expanded')).toBe(false); expect(buttonWrapper.prop('aria-owns')).toEqual(instance.selectFieldID); expect(buttonWrapper.prop('role')).toEqual('combobox'); expect(buttonWrapper.prop('isDisabled')).toEqual(false); }); test('should render select button with params according to state', () => { const wrapper = shallowRenderSelectField(); wrapper.setState({ isOpen: true, activeItemID: 'datalistitem-123', }); const buttonWrapper = wrapper.find('PopperComponent').childAt(0); expect(buttonWrapper.length).toBe(1); expect(buttonWrapper.prop('aria-activedescendant')).toEqual('datalistitem-123'); expect(buttonWrapper.prop('aria-expanded')).toBe(true); }); test('should render disabled select button if isDisabled is true', () => { const wrapper = shallowRenderSelectField({ isDisabled: true }); const buttonWrapper = wrapper.find('PopperComponent').childAt(0); expect(buttonWrapper.prop('isDisabled')).toBe(true); }); test('should render additional buttonProps on select button', () => { const wrapper = shallowRenderSelectField({ buttonProps: { 'data-resin-thing': 'hi' }, }); const buttonWrapper = wrapper.find('PopperComponent').childAt(0); expect(buttonWrapper.prop('data-resin-thing')).toBe('hi'); }); test('should send error to select button when error has some value', () => { const wrapper = shallowRenderSelectField({ error: 'error', }); const buttonWrapper = wrapper.find('PopperComponent').childAt(0); expect(buttonWrapper).toMatchSnapshot(); }); }); describe('renderSearchInput', () => { test('should render SearchForm if shouldShowSearchInput is true', () => { const wrapper = shallowRenderSelectField({ shouldShowSearchInput: true, }); const searchForm = wrapper.find('SearchForm'); expect(searchForm.length).toBe(1); }); }); describe('renderSelectOptions()', () => { test('should render FormattedMessage if searchText is not a substring of any of the options', () => { const searchText = 'abc'; const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); instance.setState({ searchText, }); const message = wrapper.find(FormattedMessage); expect(message.props().id).toBe(messages.noResults.id); }); test('should only render the option that matches the searchText substring', () => { const searchText = 'Audio'; const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); instance.setState({ searchText, }); const itemsWrapper = wrapper.find('DatalistItem'); const option = itemsWrapper.at(0); expect(itemsWrapper.length).toBe(1); expect(option.find('.bdl-SelectField-optionText').props().title).toEqual(searchText); }); test('should render DatalistItems per option', () => { const wrapper = shallowRenderSelectField(); const itemsWrapper = wrapper.find('DatalistItem'); expect(itemsWrapper.length).toBe(4); // Spot check that props are correct expect(itemsWrapper.at(0).prop('className')).toEqual('select-option'); expect(itemsWrapper.at(0).key()).toEqual('0'); expect(itemsWrapper.at(2).key()).toEqual('2'); }); test('should render selected checkboxes for selected options', () => { const wrapper = shallowRenderSelectField({ selectedValues: ['audio', 'document'], }); const itemsWrapper = wrapper.find('DatalistItem'); expect(itemsWrapper.at(0).find('IconCheck').length).toBe(0); expect(itemsWrapper.at(1).find('IconCheck').length).toBe(1); expect(itemsWrapper.at(2).find('IconCheck').length).toBe(1); expect(itemsWrapper.at(3).find('IconCheck').length).toBe(0); }); test('should set isActive prop on current active index item', () => { const wrapper = shallowRenderSelectField({ selectedValues: ['audio', 'document'], }); wrapper.setState({ activeItemIndex: 2, }); const itemsWrapper = wrapper.find('DatalistItem'); expect(itemsWrapper.at(0).prop('isActive')).toBeFalsy(); expect(itemsWrapper.at(2).prop('isActive')).toBe(true); }); test('should render separators when specified', () => { const wrapper = shallowRenderSelectField({ separatorIndices: [1, 3], }); const overlayWrapper = wrapper.find('PopperComponent').childAt(1); expect(overlayWrapper.childAt(1).prop('role')).toEqual('separator'); expect(overlayWrapper.childAt(4).prop('role')).toEqual('separator'); }); test('should render option content using optionRenderer when provided', () => { const optionRenderer = jest.fn().mockImplementation(({ displayText, value }) => ( <span> {displayText}-{value} </span> )); const wrapper = shallowRenderSelectField({ optionRenderer }); const itemsWrapper = wrapper.find('DatalistItem'); expect(optionRenderer).toHaveBeenCalledTimes(options.length); expect(itemsWrapper).toMatchSnapshot(); }); test('should render a clear option if shouldShowClearOption is true and value is CLEAR and searchText is empty string', () => { const wrapper = shallowRenderSelectField({ options: [{ displayText: 'Clear All', value: CLEAR }], shouldShowClearOption: true, }); const itemsWrapper = wrapper.find('DatalistItem'); expect(itemsWrapper.at(0).prop('className')).toEqual('select-option is-clear-option'); }); test('should not render a clear option if shouldShowClearOption is true and value is CLEAR and searchText is not empty string', () => { const wrapper = shallowRenderSelectField({ options: [{ displayText: 'Clear All', value: CLEAR }], shouldShowClearOption: true, }); wrapper.instance().setState({ searchText: 'C', }); const itemsWrapper = wrapper.find('DatalistItem'); expect(itemsWrapper.at(0).prop('className')).not.toEqual('select-option is-clear-option'); }); }); describe('render()', () => { test('should render a div wrapper with the specified class', () => { const className = 'test'; const wrapper = shallowRenderSelectField({ className, }); expect(wrapper.hasClass('select-container')).toBe(true); expect(wrapper.hasClass(className)).toBe(true); }); test('should render a listbox in overlay with options', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); // Dive past the ForwardRef and SelectFieldDropdown const overlay = wrapper .find('PopperComponent') .childAt(1) .dive() .dive(); expect(overlay.length).toBe(1); expect(overlay.is('ul')).toBe(true); expect(overlay.prop('role')).toEqual('listbox'); expect(overlay.prop('id')).toEqual(instance.selectFieldID); expect(overlay.prop('aria-multiselectable')).toBeFalsy(); }); test('should render a listbox with aria-multiselectable when multiple prop is passed', () => { const wrapper = shallowRenderSelectField({ multiple: true }); // Dive past the ForwardRef and SelectFieldDropdown const overlay = wrapper .find('PopperComponent') .childAt(1) .dive() .dive(); expect(overlay.prop('aria-multiselectable')).toBe(true); }); test.each([ [true, true], [false, false], ])( 'should apply the correct CSS classes to the overlay element when isScrollable is %s', (isScrollable, result) => { const wrapper = shallowRenderSelectField({ isScrollable }); // Dive past the ForwardRef and SelectFieldDropdown const overlay = wrapper .find('PopperComponent') .childAt(1) .dive() .dive(); expect(overlay.hasClass(OVERLAY_SCROLLABLE_CLASS)).toBe(result); }, ); test('should apply preventOverflow modifier when isEscapedWithReference is true', () => { const wrapper = shallowRenderSelectField({ isEscapedWithReference: true }); const props = wrapper.find('PopperComponent').props(); expect(props.modifiers.preventOverflow).toEqual({ escapeWithReference: true }); }); test('should not apply preventOverflow modifier when isEscapedWithReference is not set', () => { const wrapper = shallowRenderSelectField(); const props = wrapper.find('PopperComponent').props(); expect(props.modifiers.preventOverflow).toBeUndefined(); }); }); describe('onBlur', () => { test('should not call closeDropdown() when dropdown is not open', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: false }); sandbox .mock(instance) .expects('closeDropdown') .never(); wrapper.simulate('blur'); }); test('should call closeDropdown() when dropdown is open and event.relatedTarget.classList does not contain select-button and does not contain search-input', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const spy = jest.spyOn(instance, 'closeDropdown'); wrapper.setState({ isOpen: true }); const targetWithClassName = { relatedTarget: document.createElement('button'), }; targetWithClassName.relatedTarget.className = 'not-select-button'; instance.handleBlur(targetWithClassName); expect(spy).toHaveBeenCalled(); }); test.each` className ${'search-input'} ${'select-button'} `( 'should not call closeDropdown when dropdown is open and event.relatedTarget.classList contains $className', ({ className }) => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const spy = jest.spyOn(instance, 'closeDropdown'); wrapper.setState({ isOpen: true }); const targetWithClassName = { relatedTarget: document.createElement('button'), }; targetWithClassName.relatedTarget.className = className; instance.handleBlur(targetWithClassName); expect(spy).not.toHaveBeenCalled(); }, ); test('should not call closeDropdown when dropdown is open and event.relatedTarget.classList contains blurExceptionClassNames', () => { const exception = 'foobar'; const wrapper = shallowRenderSelectField({ blurExceptionClassNames: [exception] }); const instance = wrapper.instance(); const spy = jest.spyOn(instance, 'closeDropdown'); wrapper.setState({ isOpen: true }); const targetWithClassName = { relatedTarget: document.createElement('button'), }; targetWithClassName.relatedTarget.className = exception; instance.handleBlur(targetWithClassName); expect(spy).not.toHaveBeenCalled(); }); }); describe('onArrowDown', () => { let event; beforeEach(() => { event = { key: ARROW_DOWN, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }; }); test('should set next active item when key is arrow down and dropdown is open', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: 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 = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex: 3, isOpen: 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 = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: false }); sandbox.mock(instance).expects('openDropdown'); wrapper.simulate('keyDown', event); }); }); describe('onArrowUp', () => { let event; beforeEach(() => { event = { key: ARROW_UP, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }; }); test('should set previous active item when key is arrow up and dropdown is open', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex: 0, isOpen: 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 = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: true }); sandbox .mock(instance) .expects('setActiveItem') .withArgs(3); wrapper.simulate('keyDown', event); }); test('should open dropdown when key is arrow up and dropdown is closed', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: false }); sandbox.mock(instance).expects('openDropdown'); wrapper.simulate('keyDown', event); }); }); describe('onEnter', () => { test('should not stop default event or select item when no item is active', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: true }); sandbox .mock(instance) .expects('selectOption') .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 = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex: 0, isOpen: false }); sandbox .mock(instance) .expects('selectOption') .never(); wrapper.simulate('keyDown', { key: ENTER, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); test('should stop default event and select item and close dropdown when an item is active and dropdown is open', () => { const activeItemIndex = 0; const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex, isOpen: true }); sandbox .mock(instance) .expects('selectOption') .withArgs(activeItemIndex); sandbox.mock(instance).expects('closeDropdown'); wrapper.simulate('keyDown', { key: ENTER, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }); }); test('should call handleClearClick if shouldShowClearOption is true and activeItemIndex === 0', () => { const activeItemIndex = 0; const wrapper = shallowRenderSelectField({ shouldShowClearOption: true, }); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex, isOpen: true }); sandbox.mock(instance).expects('handleClearClick'); wrapper.simulate('keyDown', { key: ENTER, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }); }); const mockedSpaceEvent = { key: SPACE, target: {}, preventDefault: jest.fn(), stopPropagation: jest.fn() }; const mockedEnterEvent = { key: ENTER, target: {}, preventDefault: jest.fn(), stopPropagation: jest.fn() }; test.each` event | condition ${mockedSpaceEvent} | ${'the key is SPACE'} ${mockedEnterEvent} | ${'key is ENTER and no item is active'} `( 'should not call handleClearClick / selectOption / closeDropdown if shouldShowSearchInput is true and $condition', ({ event }) => { const wrapper = shallowRenderSelectField({ shouldShowSearchInput: true, }); const instance = wrapper.instance(); const handleClearClickSpy = jest.spyOn(instance, 'handleClearClick'); const selectOptionSpy = jest.spyOn(instance, 'selectOption'); const closeDropdownSpy = jest.spyOn(instance, 'closeDropdown'); instance.handleKeyDown(event); expect(handleClearClickSpy).not.toHaveBeenCalled(); expect(selectOptionSpy).not.toHaveBeenCalled(); expect(closeDropdownSpy).not.toHaveBeenCalled(); }, ); }); describe('onSpacebar', () => { test('should not stop default event or select item when shouldShowSearchInput is true', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setProps({ shouldShowSearchInput: true, }); wrapper.setState({ isOpen: true }); sandbox .mock(instance) .expects('selectOption') .never(); wrapper.simulate('keyDown', { key: SPACE, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); test('should not stop default event or select item when no item is active', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: true }); sandbox .mock(instance) .expects('selectOption') .never(); wrapper.simulate('keyDown', { key: SPACE, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); test('should not stop default event or select item when dropdown is closed', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex: 0, isOpen: false }); sandbox .mock(instance) .expects('selectOption') .never(); wrapper.simulate('keyDown', { key: SPACE, 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 = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex, isOpen: true }); sandbox .mock(instance) .expects('selectOption') .withArgs(activeItemIndex); wrapper.simulate('keyDown', { key: SPACE, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }); }); test('should call handleClearClick if shouldShowClearOption is true and activeItemIndex === 0', () => { const activeItemIndex = 0; const wrapper = shallowRenderSelectField({ shouldShowClearOption: true, }); const instance = wrapper.instance(); wrapper.setState({ activeItemIndex, isOpen: true }); sandbox.mock(instance).expects('handleClearClick'); wrapper.simulate('keyDown', { key: SPACE, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }); }); }); describe('onEscape', () => { test('should not stop default event nor close dropdown when dropdown is closed', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const instanceMock = sandbox.mock(instance); wrapper.setState({ isOpen: false }); instanceMock.expects('closeDropdown').never(); wrapper.simulate('keyDown', { key: ESCAPE, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); test('should stop default event and close dropdown when dropdown is open', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const instanceMock = sandbox.mock(instance); wrapper.setState({ isOpen: true }); instanceMock.expects('closeDropdown'); wrapper.simulate('keyDown', { key: ESCAPE, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }); }); }); describe('onTab', () => { test('should not close dropdown when dropdown is closed nor stop default event ', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const instanceMock = sandbox.mock(instance); wrapper.setState({ isOpen: false }); instanceMock.expects('closeDropdown').never(); wrapper.simulate('keyDown', { key: TAB, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); test('should close dropdown when dropdown is open and should not stop default event', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const instanceMock = sandbox.mock(instance); wrapper.setState({ isOpen: true }); instanceMock.expects('closeDropdown'); wrapper.simulate('keyDown', { key: TAB, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); }); describe('onAnyKey', () => { test('should set the active item based on letter', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const instanceMock = sandbox.mock(instance); wrapper.setState({ isOpen: true }); instanceMock.expects('setActiveItem').withArgs(2); wrapper.simulate('keyDown', { key: 'd', preventDefault: sandbox.mock(), stopPropagation: sandbox.mock(), }); }); }); describe('handleChange', () => { test('should call props.onChange with list of selected items', () => { const wrapper = shallowRenderSelectField(); wrapper.setProps({ onChange: sandbox.mock().withArgs(['what', 'is', 'up']), }); wrapper.instance().handleChange(['what', 'is', 'up']); }); }); describe('handleClearClick', () => { test('should call handleChange with empty array', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); sandbox .mock(instance) .expects('handleChange') .withArgs([]); instance.handleClearClick(); }); }); describe('handleOptionSelect', () => { test('should call props.onOptionSelect with newly selected (or deselected) item', () => { const wrapper = shallowRenderSelectField(); wrapper.setProps({ onOptionSelect: sandbox.mock().withArgs('up'), }); wrapper.instance().handleOptionSelect('up'); }); }); describe('handleButtonClick', () => { test('should open dropdown when it is closed', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: false }); sandbox.mock(instance).expects('openDropdown'); wrapper .find('PopperComponent') .childAt(0) .simulate('click'); }); test('should close dropdown when it is open', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: true }); sandbox.mock(instance).expects('closeDropdown'); wrapper .find('PopperComponent') .childAt(0) .simulate('click'); }); }); describe('handleButtonKeyDown', () => { [ { key: SPACE, }, { key: ENTER, }, ].forEach(({ key }) => { test('should preventDefault() when key is space or enter and activeItemIndex != -1', () => { const wrapper = shallowRenderSelectField(); wrapper.setState({ isOpen: true, activeItemIndex: 2 }); wrapper .find('PopperComponent') .childAt(0) .simulate('keyDown', { key, preventDefault: sandbox.mock(), stopPropagation: sandbox.mock().never(), }); }); test('should not preventDefault() when key is space or enter and activeItemIndex == -1', () => { const wrapper = shallowRenderSelectField(); wrapper.setState({ isOpen: true, activeItemIndex: -1 }); wrapper .find('PopperComponent') .childAt(0) .simulate('keyDown', { key, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); }); test('should not preventDefault() when key is not space or enter', () => { const wrapper = shallowRenderSelectField(); wrapper.setState({ isOpen: true, activeItemIndex: 2 }); wrapper .find('PopperComponent') .childAt(0) .simulate('keyDown', { key: ARROW_DOWN, preventDefault: sandbox.mock().never(), stopPropagation: sandbox.mock().never(), }); }); }); describe('onOptionClick', () => { test('should call handleClearClick if index is 0 and shouldShowClearOption is true', () => { const wrapper = shallowRenderSelectField({ options: [{ displayText: 'Clear All', value: CLEAR }], shouldShowClearOption: true, }); wrapper.setState({ activeItemIndex: 0, }); const instance = wrapper.instance(); sandbox.mock(instance).expects('handleClearClick'); wrapper .find('DatalistItem') .at(0) .simulate('click', { preventDefault: sandbox.mock(), }); }); test('should select item and close dropdown when item is clicked', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); sandbox .mock(instance) .expects('selectOption') .withArgs(1); wrapper .find('DatalistItem') .at(1) .simulate('click', { preventDefault: sandbox.mock(), }); }); }); describe('onItemMouseEnter', () => { test('should set correct active item index when hovering over item', () => { const wrapper = shallowRenderSelectField(); wrapper .find('DatalistItem') .at(2) .simulate('mouseEnter'); expect(wrapper.state('activeItemIndex')).toEqual(2); }); test('should set shouldScrollIntoView to false when hovering over item', () => { const wrapper = shallowRenderSelectField(); wrapper.setState({ shouldScrollIntoView: true }); wrapper .find('DatalistItem') .at(2) .simulate('mouseEnter'); expect(wrapper.state('shouldScrollIntoView')).toBe(false); }); }); describe('setActiveItem()', () => { const wrapper = shallowRenderSelectField(); 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); }); test('should set shouldScrollIntoView to true by default when called', () => { wrapper.setState({ shouldScrollIntoView: false }); instance.setActiveItem(1); expect(wrapper.state('shouldScrollIntoView')).toBe(true); }); }); describe('setActiveItemID()', () => { const wrapper = shallowRenderSelectField(); 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 scroll into view when called and previously specified in state', () => { wrapper.setState({ shouldScrollIntoView: false }); instance.setActiveItemID(id); expect(scrollIntoView).toHaveBeenCalledTimes(0); wrapper.setState({ shouldScrollIntoView: true }); instance.setActiveItemID(id); expect(scrollIntoView).toHaveBeenCalledTimes(1); }); }); describe('openDropdown()', () => { test('should set isOpen state to true when called', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); instance.openDropdown(); expect(wrapper.state('isOpen')).toBe(true); }); test('should add document click listener', () => { document.addEventListener = jest.fn(); const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); instance.openDropdown(); expect(document.addEventListener).toHaveBeenCalled(); }); test('should call this.searchInputRef.focus() if shouldShowSearchInput is true', () => { const wrapper = shallowRenderSelectField({ shouldShowSearchInput: true, }); const mockSearchInputRef = { focus: jest.fn(), }; const instance = wrapper.instance(); instance.searchInputRef = mockSearchInputRef; instance.openDropdown(); expect(mockSearchInputRef.focus).toHaveBeenCalled(); }); }); describe('closeDropdown()', () => { test('should set isOpen state to false and reset all active item data when called', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ activeItemID: 'test', activeItemIndex: 1, isOpen: true, }); instance.closeDropdown(); expect(wrapper.state('isOpen')).toBe(false); expect(wrapper.state('activeItemID')).toEqual(null); expect(wrapper.state('activeItemIndex')).toEqual(-1); expect(wrapper.state('searchText')).toBe(''); }); test('should remove document click listener', () => { document.removeEventListener = jest.fn(); const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); wrapper.setState({ isOpen: true }); instance.closeDropdown(); expect(document.removeEventListener).toHaveBeenCalled(); }); }); describe('selectOption()', () => { test('should call selectMultiOption() when multiple prop is passed', () => { const wrapper = shallowRenderSelectField({ multiple: true }); const instance = wrapper.instance(); const index = 1; sandbox .mock(instance) .expects('selectMultiOption') .withArgs(index); instance.selectOption(index); }); test('should call selectSingleOption() when single selecting', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const index = 1; sandbox .mock(instance) .expects('selectSingleOption') .withArgs(index); sandbox.mock(instance).expects('closeDropdown'); instance.selectOption(index); }); }); describe('getFilteredOptions', () => { test('should return the correct item when searchText is empty string', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const index = 0; const item = instance.getFilteredOptions()[index]; expect(item).toEqual(options[index]); }); test('should return the correct item when searchText is not empty string', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); instance.setState({ searchText: 'Audio', }); const indexBeforeFilter = 1; // Index of audio option in default options array const indexAfterFilter = 0; // Index of audio option when user enters a search string of 'Audio' const item = instance.getFilteredOptions()[indexAfterFilter]; expect(item).toEqual(options[indexBeforeFilter]); }); test('should filter out the clear option if searchText is not empty string', () => { const wrapper = shallowRenderSelectField({ options: [{ displayText: 'Clear All', value: CLEAR }], }); const instance = wrapper.instance(); instance.setState({ searchText: 'Audio', }); const filteredOptions = instance.getFilteredOptions(); expect(filteredOptions.length).toBe(0); }); test('should not filter out the clear option if searchText is empty string', () => { const wrapper = shallowRenderSelectField({ options: [{ displayText: 'Clear All', value: CLEAR }], }); const instance = wrapper.instance(); const filteredOptions = instance.getFilteredOptions(); expect(filteredOptions.length).toBe(1); }); }); describe('selectSingleOption()', () => { test('should call handleChange() when index selected was not selected previously', () => { const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const index = 1; sandbox .mock(instance) .expects('handleChange') .withArgs([options[index]]); instance.selectSingleOption(index); }); test('should call handleChange with the correct item when searchText is not empty string', () => { const indexBeforeFilter = 1; // Index of audio option in default options array const indexAfterFilter = 0; // Index of audio option after user inputs a search string of 'Audio' const wrapper = shallowRenderSelectField(); const instance = wrapper.instance(); const spy = jest.spyOn(instance, 'handleChange'); instance.setState({ searchText: 'Audio', }); instance.selectSingleOption(indexAfterFilter); expect(spy).toHaveBeenCalledWith([options[indexBeforeFilter]]); }); test('should not call handleChange() when index selected was previously selected', () => { const index = 1; const wrapper = shallowRenderSelectField({ selectedValues: [options[index].value], }); const instance = wrapper.instance(); sandbox .mock(instance) .expects('handleChange') .never(); instance.selectSingleOption(index); }); test('should not call handleChange when index selected was previously selected and searchText is not empty string', () => { const indexBeforeFilter = 1; // Index of audio option in default options array const indexAfterFilter = 0; // Index of audio option after user inputs a search string of 'Audio' const wrapper = shallowRenderSelectField({ selectedValues: [options[indexBeforeFilter].value], }); const instance = wrapper.instance(); const spy = jest.spyOn(instance, 'handleChange'); instance.setState({ searchText: 'Audio', }); instance.selectSingleOption(indexAfterFilter); expect(spy).not.toHaveBeenCalled(); }); test('should call handleOptionSelect() even when index selected was previously selected', () => { const index = 1; const wrapper = shallowRenderSelectField({ selectedValues: [options[index].value], }); const instance = wrapper.instance(); sandbox.mock(instance).expects('handleOptionSelect'); instance.selectSingleOption(index); }); }); describe('selectMultiOption()', () => { test('should call selectSingleOption() when option selected is the default option', () => { const wrapper = shallowRenderSelectField({ defaultValue: '', selectedValues: ['audio'], }); // default value index is 0 in this case const instance = wrapper.instance(); const index = 0; sandbox .mock(instance) .expects('selectSingleOption') .withArgs(index); instance.selectMultiOption(index); }); test('should call selectSingleOption() when no options are selected but a default is specified', () => { const defaultIndex = 0; const wrapper = shallowRenderSelectField({ defaultValue: '', selectedValues: ['video'], }); // default value index is 0 in this case const instance = wrapper.instance(); const index = 3; // Matches video option sandbox .mock(instance) .expects('selectSingleOption') .withArgs(defaultIndex); instance.selectMultiOption(index); }); test('should call handleChange with selected values without default value', () => { const wrapper = shallowRenderSelectField({ defaultValue: '', selectedValues: [''], }); // default value index is 0 in this case const instance = wrapper.instance(); const index = 3; // Matches video option sandbox .mock(instance) .expects('handleChange') .withArgs([options[index]]); instance.selectMultiOption(index); }); test('should call handleChange with selected values when called', () => { const wrapper = shallowRenderSelectField({ defaultValue: '', selectedValues: ['audio'], }); // default value index is 0 in this case const instance = wrapper.instance(); const index = 3; // Matches video option sandbox .mock(instance) .expects('handleChange') .withArgs([options[1], options[index]]); // audio + video instance.selectMultiOption(index); }); test('should call handleOptionSelect with value selected when called', () => { const wrapper = shallowRenderSelectField({ defaultValue: '', selectedValues: ['audio'], }); // default value index is 0 in this case const instance = wrapper.instance(); const index = 3; // Matches video option sandbox .mock(instance) .expects('handleOptionSelect') .withArgs(options[index]); // audio + video instance.selectMultiOption(index); }); test('should call handleOptionSelect with correct value when searchText is not empty string', () => { const indexBeforeFilter = 3; // Matches video option in original options array const indexAfterFilter = 0; // Matches index of video option after search filter is applied const wrapper = shallowRenderSelectField({ defaultValue: '', selectedValues: ['audio'], }); // default value index is 0 in this case const instance = wrapper.instance(); const spy = jest.spyOn(instance, 'handleOptionSelect'); instance.setState({ searchText: 'Video', }); instance.selectMultiOption(indexAfterFilter); expect(spy).toHaveBeenCalledWith(options[indexBeforeFilter]); // audio + video }); }); describe('updateSearchText', () => { const wrapper = shallowRenderSelectField({ options: [ { displayText: 'hello', value: 0 }, { displayText: 'goodbye', value: 1 }, ], }); const instance = wrapper.instance(); test('should set text in state and call setActiveItem if the input text is a substring of an option', () => { const spy = jest.spyOn(instance, 'setActiveItem'); const text = 'he'; instance.updateSearchText(text); expect(wrapper.state('searchText')).toBe(text); expect(spy).toHaveBeenCalledWith(0); }); test('should set text in state and not call setActiveItem if inpu