UNPKG

react-select

Version:

A Select control built with and for ReactJS

1,603 lines (1,271 loc) 104 kB
'use strict'; /* global describe, it, beforeEach */ /* eslint react/jsx-boolean-value: 0 */ var jsdomHelper = require('../testHelpers/jsdomHelper'); var sinon = require('sinon'); var unexpected = require('unexpected'); var unexpectedDom = require('unexpected-dom'); var unexpectedSinon = require('unexpected-sinon'); var unexpectedReact = require('unexpected-react'); var expect = unexpected .clone() .installPlugin(unexpectedDom) .installPlugin(unexpectedSinon) .installPlugin(unexpectedReact) .installPlugin(require('../testHelpers/nodeListType')); jsdomHelper(); var React = require('react'); var ReactDOM = require('react-dom'); var TestUtils = require('react-addons-test-utils'); var Select = require('../src/Select'); var Value = require('../src/Value'); // The displayed text of the currently selected item, when items collapsed var DISPLAYED_SELECTION_SELECTOR = '.Select-value'; var FORM_VALUE_SELECTOR = '.Select > input'; var PLACEHOLDER_SELECTOR = '.Select-placeholder'; var ARROW_UP = { keyCode: 38, key: 'ArrowUp' }; var ARROW_DOWN = { keyCode: 40, key: 'ArrowDown' }; var KEY_ENTER = { keyCode: 13, key: 'Enter' }; class PropsWrapper extends React.Component { constructor(props) { super(props); this.state = props || {}; } setPropsForChild(props) { this.setState(props); } getChild() { return this.child; } render() { var Component = this.props.childComponent; // eslint-disable-line react/prop-types return <Component {...this.state} ref={(ref) => this.child = ref} />; } } describe('Select', () => { var options, onChange, onInputChange; var instance, wrapper; var searchInputNode; var getSelectControl = (instance) => { return ReactDOM.findDOMNode(instance).querySelector('.Select-control'); }; var enterSingleCharacter = () =>{ TestUtils.Simulate.keyDown(searchInputNode, { keyCode: 65, key: 'a' }); }; var pressEnterToAccept = () => { TestUtils.Simulate.keyDown(searchInputNode, { keyCode: 13, key: 'Enter' }); }; var pressTabToAccept = () => { TestUtils.Simulate.keyDown(searchInputNode, { keyCode: 9, key: 'Tab' }); }; var pressEscape = () => { TestUtils.Simulate.keyDown(searchInputNode, { keyCode: 27, key: 'Escape' }); }; var pressBackspace = () => { TestUtils.Simulate.keyDown(searchInputNode, { keyCode: 8, key: 'Backspace' }); }; var pressUp = () => { TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 38, key: 'ArrowUp' }); }; var pressDown = () => { TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 40, key: 'ArrowDown' }); }; var pressPageUp = () => { TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 33, key: 'PageUp' }); }; var pressPageDown = () => { TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 34, key: 'PageDown' }); }; var pressEndDown = () => { TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 35, key: 'End' }); }; var pressHomeDown = () => { TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 36, key: 'Home' }); }; var typeSearchText = (text) => { TestUtils.Simulate.change(searchInputNode, { target: { value: text } }); }; var clickArrowToOpen = () => { var selectArrow = ReactDOM.findDOMNode(instance).querySelector('.Select-arrow'); TestUtils.Simulate.mouseDown(selectArrow, { button: 0 }); }; var findAndFocusInputControl = () => { // Focus on the input, such that mouse events are accepted var searchInstance = ReactDOM.findDOMNode(instance.input); searchInputNode = null; if (searchInstance) { searchInputNode = searchInstance.querySelector('input'); if (searchInputNode) { TestUtils.Simulate.focus(searchInputNode); } } }; var createControl = (props, options) => { options = options || {}; onChange = sinon.spy(); onInputChange = sinon.spy(); // Render an instance of the component instance = TestUtils.renderIntoDocument( <Select onChange={onChange} onInputChange={onInputChange} {...props} /> ); if (options.initialFocus !== false) { findAndFocusInputControl(); } return instance; }; var setValueProp = value => wrapper.setPropsForChild({ value }); var createControlWithWrapper = (props, options) => { options = options || {}; if (options.wireUpOnChangeToValue) { onChange = sinon.spy(setValueProp); } else { onChange = sinon.spy(); } onInputChange = sinon.spy(); wrapper = TestUtils.renderIntoDocument( <PropsWrapper childComponent={Select} onChange={onChange} onInputChange={onInputChange} {...props} /> ); instance = wrapper.getChild(); if (options.initialFocus !== false) { findAndFocusInputControl(); } return wrapper; }; var defaultOptions = [ { value: 'one', label: 'One' }, { value: 'two', label: '222' }, { value: 'three', label: 'Three' }, { value: 'four', label: 'AbcDef' } ]; var longerListOptions = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three' }, { value: 'four', label: 'Four' }, { value: 'five', label: 'Five' }, { value: 'six', label: 'Six' }, { value: 'seven', label: 'Seven' }, { value: 'eight', label: 'Eight' }, { value: 'nine', label: 'Nine' }, { value: 'ten', label: 'ten' } ]; describe('has refs', () => { beforeEach(() => { options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three' } ]; instance = createControl({ name: 'form-field-name', value: 'one', options: options, simpleValue: true, joinValues: true, }); }); it('input', () => { expect(instance.input, 'not to equal', undefined); }); it('value', () => { typeSearchText('o'); expect(instance.value, 'not to equal', undefined); }); it('menuContainer', () => { clickArrowToOpen(); expect(instance.menuContainer, 'not to equal', undefined); }); it('menu', () => { clickArrowToOpen(); expect(instance.menu, 'not to equal', undefined); }); it('wrapper', () => { expect(instance.wrapper, 'not to equal', undefined); }); it('control', () => { expect(instance.control, 'not to equal', undefined); }); }); describe('with simple options', () => { beforeEach(() => { options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three' } ]; instance = createControl({ name: 'form-field-name', value: 'one', options: options, simpleValue: true, }); }); it('should assign the given name', () => { var selectInputElement = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'input')[0]; expect(ReactDOM.findDOMNode(selectInputElement).name, 'to equal', 'form-field-name'); }); it('should show the options on mouse click', function () { TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(instance).querySelector('.Select-control'), { button: 0 }); var node = ReactDOM.findDOMNode(instance); expect(node, 'queried for', '.Select-option', 'to have length', 3); }); it('should display the labels on mouse click', () => { TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(instance).querySelector('.Select-control'), { button: 0 }); var node = ReactDOM.findDOMNode(instance); expect(node, 'queried for', '.Select-option:nth-child(1)', 'to have items satisfying', 'to have text', 'One'); expect(node, 'queried for', '.Select-option:nth-child(2)', 'to have items satisfying', 'to have text', 'Two'); expect(node, 'queried for', '.Select-option:nth-child(3)', 'to have items satisfying', 'to have text', 'Three'); }); it('should filter after entering some text', () => { typeSearchText('T'); var node = ReactDOM.findDOMNode(instance); expect(node, 'queried for', '.Select-option:nth-child(1)', 'to have items satisfying', 'to have text', 'Two'); expect(node, 'queried for', '.Select-option:nth-child(2)', 'to have items satisfying', 'to have text', 'Three'); expect(node, 'queried for', '.Select-option', 'to have length', 2); }); it('should pass input value when entering text', () => { typeSearchText('a'); enterSingleCharacter('a'); expect(onInputChange, 'was called with', 'a'); }); it('should filter case insensitively', () => { typeSearchText('t'); var node = ReactDOM.findDOMNode(instance); expect(node, 'queried for', '.Select-option:nth-child(1)', 'to have items satisfying', 'to have text', 'Two'); expect(node, 'queried for', '.Select-option:nth-child(2)', 'to have items satisfying', 'to have text', 'Three'); expect(node, 'queried for', '.Select-option', 'to have length', 2); }); it('should filter using "contains"', () => { // Search 'h', should only show 'Three' typeSearchText('h'); var node = ReactDOM.findDOMNode(instance); expect(node, 'queried for', '.Select-option:nth-child(1)', 'to have items satisfying', 'to have text', 'Three'); expect(node, 'queried for', '.Select-option', 'to have length', 1); }); it('should accept when enter is pressed', () => { // Search 'h', should only show 'Three' typeSearchText('h'); pressEnterToAccept(); expect(onChange, 'was called with', 'three'); }); it('should accept when tab is pressed', () => { // Search 'h', should only show 'Three' typeSearchText('h'); pressTabToAccept(); expect(onChange, 'was called with', 'three'); }); describe('pressing escape', () => { beforeEach(() => { typeSearchText('h'); pressTabToAccept(); expect(onChange, 'was called with', 'three'); onChange.reset(); pressEscape(); }); it('should call onChange with a empty value', () => { expect(onChange, 'was called with', null); }); }); it('should display the options menu when tapped', function() { TestUtils.Simulate.touchStart(getSelectControl(instance)); TestUtils.Simulate.touchEnd(getSelectControl(instance)); var node = ReactDOM.findDOMNode(instance); expect(node, 'queried for', '.Select-option', 'to have length', 3); }); it('should not display the options menu when touched and dragged', function() { TestUtils.Simulate.touchStart(getSelectControl(instance)); TestUtils.Simulate.touchMove(getSelectControl(instance)); TestUtils.Simulate.touchEnd(getSelectControl(instance)); var node = ReactDOM.findDOMNode(instance); expect(node, 'to contain no elements matching', '.Select-option'); }); it('should focus the first value on mouse click', () => { TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(instance).querySelector('.Select-control'), { button: 0 }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'One'); }); it('should move the focused value to the second value when down pressed', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 40, key: 'ArrowDown' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Two'); }); it('should move the focused value to the second value when down pressed', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 40, key: 'ArrowDown' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 40, key: 'ArrowDown' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Three'); }); it('should loop round to top item when down is pressed on the last item', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 40, key: 'ArrowDown' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 40, key: 'ArrowDown' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 40, key: 'ArrowDown' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'One'); }); it('should loop round to bottom item when up is pressed on the first item', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 38, key: 'ArrowUp' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Three'); }); it('should move the focused value to the second item when up pressed twice', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 38, key: 'ArrowUp' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 38, key: 'ArrowUp' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Two'); }); it('should move the focused value to the end when pressing end', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 35, key: 'End' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Three'); }); it('should move the focused value to the beginning when pressing home', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl); TestUtils.Simulate.keyDown(selectControl, { keyCode: 35, key: 'End' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 36, key: 'Home' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'One'); }); it('should move the focused value to the end if page down is pressed and number of items is less than page size', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 34, key: 'PageDown' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Three'); }); it('should move the focused value down by page size on page down using default page size', () => { var longerListInstance = createControl({ name: 'form-field-name', value: 'one', options: longerListOptions, simpleValue: true, }); var selectControl = getSelectControl(longerListInstance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 34, key: 'PageDown' }); expect(ReactDOM.findDOMNode(longerListInstance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Six'); }); it('should move the focused value down by page size on page down using custom page size', () => { var longerListInstance = createControl({ name: 'form-field-name', value: 'one', options: longerListOptions, simpleValue: true, pageSize: 7 }); var selectControl = getSelectControl(longerListInstance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 34, key: 'PageDown' }); expect(ReactDOM.findDOMNode(longerListInstance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Eight'); }); it('should move the focused value to the start if page up is pressed and number of items is less than page size', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 34, key: 'PageDown' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 33, key: 'PageUp' }); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'One'); }); it('should move the focused value up by page size on page up using default page size', () => { var longerListInstance = createControl({ name: 'form-field-name', value: 'one', options: longerListOptions, simpleValue: true, }); var selectControl = getSelectControl(longerListInstance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 35, key: 'End' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 33, key: 'PageUp' }); expect(ReactDOM.findDOMNode(longerListInstance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Five'); }); it('should move the focused value up by page size on page up using custom page size', () => { var longerListInstance = createControl({ name: 'form-field-name', value: 'one', options: longerListOptions, simpleValue: true, pageSize: 7 }); var selectControl = getSelectControl(longerListInstance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 35, key: 'End' }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 33, key: 'PageUp' }); expect(ReactDOM.findDOMNode(longerListInstance), 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'Three'); }); it('should clear the selection on escape', () => { var selectControl = getSelectControl(instance); TestUtils.Simulate.mouseDown(selectControl, { button: 0 }); TestUtils.Simulate.keyDown(selectControl, { keyCode: 27, key: 'Escape' }); expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', '.Select-option'); }); it('should open the options on arrow down with the top option focused, when the options are closed', () => { var selectControl = getSelectControl(instance); var domNode = ReactDOM.findDOMNode(instance); TestUtils.Simulate.keyDown(selectControl, { keyCode: 38, key: 'ArrowDown' }); expect(domNode, 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'One'); }); it('should open the options on arrow up with the top option focused, when the options are closed', () => { var selectControl = getSelectControl(instance); var domNode = ReactDOM.findDOMNode(instance); TestUtils.Simulate.keyDown(selectControl, { keyCode: 40, key: 'ArrowDown' }); expect(domNode, 'queried for', '.Select-option.is-focused', 'to have items satisfying', 'to have text', 'One'); }); it('should close the options one the second click on the arrow', () => { var selectArrow = ReactDOM.findDOMNode(instance).querySelector('.Select-arrow'); TestUtils.Simulate.mouseDown(selectArrow, { button: 0 }); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option'), 'to have length', 3); TestUtils.Simulate.mouseDown(selectArrow, { button: 0 }); expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', '.Select-option'); }); it('should ignore a right mouse click on the arrow', () => { var selectArrow = ReactDOM.findDOMNode(instance).querySelector('.Select-arrow'); TestUtils.Simulate.mouseDown(selectArrow, { button: 1 }); expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', '.Select-option'); }); it('the input should not have a required attribute', () => { var inputNode = ReactDOM.findDOMNode(instance).querySelector('input'); expect(inputNode, 'to have attributes', { required: undefined }); }); }); describe('with a return from onInputChange', () => { let onInputChangeOverride; beforeEach(() => { options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three' } ]; onInputChangeOverride = sinon.stub(); wrapper = createControlWithWrapper({ name: 'field-onChange', value: 'one', options: options, simpleValue: true, onInputChange: onInputChangeOverride }); }); it('should change the value when onInputChange returns a value', () => { onInputChangeOverride.returns('2'); typeSearchText('1'); expect(instance.state.inputValue, 'to equal', '2'); }); it('should return the input when onInputChange returns undefined', () => { onInputChangeOverride.returns(undefined); // Not actually necessary as undefined is the default, but makes this clear typeSearchText('Test'); expect(instance.state.inputValue, 'to equal', 'Test'); }); it('should return the input when onInputChange returns null', () => { onInputChangeOverride.returns(null); typeSearchText('Test'); expect(instance.state.inputValue, 'to equal', 'Test'); }); it('should update the input when onInputChange returns a number', () => { onInputChangeOverride.returns(5); typeSearchText('Test'); expect(instance.state.inputValue, 'to equal', '5'); }); it('displays the new value in the input box', () => { onInputChangeOverride.returns('foo'); typeSearchText('Test'); const displayedValue = ReactDOM.findDOMNode(instance).querySelector('.Select-input input').value; expect(displayedValue, 'to equal', 'foo'); }); }); describe('with values as numbers', () => { beforeEach(() => { options = [ { value: 0, label: 'Zero' }, { value: 1, label: 'One' }, { value: 2, label: 'Two' }, { value: 3, label: 'Three' } ]; wrapper = createControlWithWrapper({ value: 2, name: 'field', options: options, simpleValue: true, }); }); it('selects the initial value', () => { expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR, 'to have text', 'Two'); }); it('set the initial value of the hidden input control', () => { expect(ReactDOM.findDOMNode(wrapper).querySelector(FORM_VALUE_SELECTOR).value, 'to equal', '2' ); }); it('updates the value when the value prop is set', () => { wrapper.setPropsForChild({ value: 3 }); expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR, 'to have text', 'Three'); }); it('updates the value of the hidden input control after new value prop', () => { wrapper.setPropsForChild({ value: 3 }); expect(ReactDOM.findDOMNode(wrapper).querySelector(FORM_VALUE_SELECTOR).value, 'to equal', '3' ); }); it('calls onChange with the new value as a number', () => { clickArrowToOpen(); pressDown(); pressEnterToAccept(); expect(onChange, 'was called with', 3); }); it('supports setting the value to 0 via prop', () => { wrapper.setPropsForChild({ value: 0 }); expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR, 'to have text', 'Zero'); }); it('supports selecting the zero value', () => { clickArrowToOpen(); pressUp(); pressUp(); pressEnterToAccept(); expect(onChange, 'was called with', 0); }); describe('with multi=true', () => { beforeEach(() => { options = [ { value: 0, label: 'Zero' }, { value: 1, label: 'One' }, { value: 2, label: 'Two' }, { value: 3, label: 'Three' }, { value: 4, label: 'Four' } ]; wrapper = createControlWithWrapper({ value: [2, 1], options: options, multi: true, searchable: true }); }); it('selects the initial value', () => { expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">Two</span></div> <div><span className="Select-value-label">One</span></div> </span>); }); it('calls onChange with the correct value when 1 option is selected', () => { var removeIcons = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-value .Select-value-icon'); TestUtils.Simulate.mouseDown(removeIcons[0]); expect(onChange, 'was called with', [{ value: 1, label: 'One' }]); }); it('supports updating the values via props', () => { wrapper.setPropsForChild({ value: [3, 4] }); expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">Three</span></div> <div><span className="Select-value-label">Four</span></div> </span>); }); it('supports updating the value to a single value', () => { wrapper.setPropsForChild({ value: 1 }); expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">One</span></div> </span>); }); it('supports updating the value to single value of 0', () => { // This test is specifically in case there's a "if (value) {... " somewhere wrapper.setPropsForChild({ value: 0 }); expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">Zero</span></div> </span>); }); it('calls onChange with the correct values when multiple options are selected', () => { typeSearchText('fo'); pressEnterToAccept(); // Select 'Four' expect(onChange, 'was called with', [ { value: 2, label: 'Two' }, { value: 1, label: 'One' }, { value: 4, label: 'Four' } ]); }); }); describe('searching', () => { let searchOptions = [ { value: 1, label: 'One' }, { value: 2, label: 'Two' }, { value: 10, label: 'Ten' }, { value: 20, label: 'Twenty' }, { value: 21, label: 'Twenty-one' }, { value: 34, label: 'Thirty-four' }, { value: 54, label: 'Fifty-four' } ]; describe('with matchPos=any and matchProp=any', () => { beforeEach(() => { instance = createControl({ matchPos: 'any', matchProp: 'any', options: searchOptions }); }); it('finds text anywhere in value', () => { typeSearchText('1'); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option', 'to satisfy', [ expect.it('to have text', 'One'), expect.it('to have text', 'Ten'), expect.it('to have text', 'Twenty-one') ]); }); it('finds text at end', () => { typeSearchText('4'); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option', 'to satisfy', [ expect.it('to have text', 'Thirty-four'), expect.it('to have text', 'Fifty-four') ]); }); }); describe('with matchPos=start and matchProp=any', () => { beforeEach(() => { instance = createControl({ matchPos: 'start', matchProp: 'any', options: searchOptions }); }); it('finds text at the start of the value', () => { typeSearchText('1'); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option', 'to satisfy', [ expect.it('to have text', 'One'), expect.it('to have text', 'Ten') ]); }); it('does not match text at end', () => { typeSearchText('4'); expect(ReactDOM.findDOMNode(instance), 'to contain elements matching', '.Select-noresults'); expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', '.Select-option'); }); }); describe('with matchPos=any and matchProp=value', () => { beforeEach(() => { instance = createControl({ matchPos: 'any', matchProp: 'value', options: searchOptions }); }); it('finds text anywhere in value', () => { typeSearchText('1'); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option', 'to satisfy', [ expect.it('to have text', 'One'), expect.it('to have text', 'Ten'), expect.it('to have text', 'Twenty-one') ]); }); it('finds text at end', () => { typeSearchText('4'); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option', 'to satisfy', [ expect.it('to have text', 'Thirty-four'), expect.it('to have text', 'Fifty-four') ]); }); }); describe('with matchPos=start and matchProp=value', () => { beforeEach(() => { instance = createControl({ matchPos: 'start', matchProp: 'value', options: searchOptions }); }); it('finds text at the start of the value', () => { typeSearchText('1'); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option', 'to satisfy', [ expect.it('to have text', 'One'), expect.it('to have text', 'Ten') ]); }); it('does not match text at end', () => { typeSearchText('4'); expect(ReactDOM.findDOMNode(instance), 'to contain elements matching', '.Select-noresults'); expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', '.Select-option'); }); }); }); }); describe('with options and value', () => { beforeEach(() => { options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three' } ]; // Render an instance of the component wrapper = createControlWithWrapper({ name: 'out-select-control', value: 'one', options: options, searchable: true }); }); it('starts with the given value', () => { var node = ReactDOM.findDOMNode(instance); expect(node, 'queried for', DISPLAYED_SELECTION_SELECTOR, 'to have items satisfying', 'to have text', 'One'); }); it('supports setting the value via prop', () => { wrapper.setPropsForChild({ value: 'three' }); expect(ReactDOM.findDOMNode(instance), 'queried for', DISPLAYED_SELECTION_SELECTOR, 'to have items satisfying', 'to have text', 'Three'); }); it('sets the value of the hidden form node', () => { wrapper.setPropsForChild({ value: 'three' }); expect(ReactDOM.findDOMNode(wrapper).querySelector(FORM_VALUE_SELECTOR).value, 'to equal', 'three' ); }); it('display the raw value if the option is not available', () => { wrapper.setPropsForChild({ value: { value: 'new', label: 'something new' } }); expect(ReactDOM.findDOMNode(instance), 'queried for', DISPLAYED_SELECTION_SELECTOR, 'to have items satisfying', 'to have text', 'something new'); }); it('updates the display text if the option appears later', () => { wrapper.setPropsForChild({ value: 'new' }); wrapper.setPropsForChild({ options: [ { value: 'one', label: 'One' }, { value: 'two', labal: 'Two' }, { value: 'new', label: 'New item in the options' }, { value: 'three', label: 'Three' } ] }); expect(ReactDOM.findDOMNode(instance), 'queried for', DISPLAYED_SELECTION_SELECTOR, 'to have items satisfying', 'to have text', 'New item in the options'); }); it('the input should not have a required attribute', () => { var inputNode = ReactDOM.findDOMNode(instance).querySelector('input'); expect(inputNode, 'to have attributes', { required: undefined }); }); }); describe('with a disabled option', () => { beforeEach(() => { options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two', disabled: true }, { value: 'three', label: 'Three' } ]; wrapper = createControlWithWrapper({ options: options, searchable: true }); }); it('adds the is-disabled class to the disabled option', () => { clickArrowToOpen(); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option')[1], 'to have attributes', { class: 'is-disabled' }); }); it('is not selectable by clicking', () => { clickArrowToOpen(); TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option')[1]); expect(onChange, 'was not called'); expect(ReactDOM.findDOMNode(instance), 'queried for first', PLACEHOLDER_SELECTOR, 'to have text', 'Select...'); }); it('is not selectable by keyboard', () => { clickArrowToOpen(); // Press down to get to the second option TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 40, key: 'ArrowDown' }); // Check the disable option is not focused expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', '.Select-option.is-disabled.is-focused'); }); it('jumps over the disabled option', () => { clickArrowToOpen(); // Press down to get to the second option TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 40, key: 'ArrowDown' }); // Check the focused option is the one after the disabled option expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-option.is-focused', 'to have text', 'Three'); }); it('jumps back to beginning when disabled option is last option', () => { wrapper = createControlWithWrapper({ options: [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three', disabled: true } ] }); clickArrowToOpen(); // Down twice TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 40, key: 'ArrowDown' }); TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 40, key: 'ArrowDown' }); // Selected option should be back to 'One' expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-option.is-focused', 'to have text', 'One'); }); it('skips over last option when looping round when last option is disabled', () => { wrapper = createControlWithWrapper({ options: [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three', disabled: true } ] }); clickArrowToOpen(); // Press up, should skip the bottom entry 'Three'... TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 38, key: 'ArrowUp' }); // ... and land on 'Two' expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-option.is-focused', 'to have text', 'Two'); }); it('focuses initially on the second option when the first is disabled', () => { wrapper = createControlWithWrapper({ options: [ { value: 'one', label: 'One', disabled: true }, { value: 'two', label: 'Two' }, { value: 'three', label: 'Three' } ] }); clickArrowToOpen(); expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-option.is-focused', 'to have text', 'Two'); }); it('doesn\'t focus anything when all options are disabled', () => { wrapper = createControlWithWrapper({ options: [ { value: 'one', label: 'One', disabled: true }, { value: 'two', label: 'Two', disabled: true }, { value: 'three', label: 'Three', disabled: true } ] }); clickArrowToOpen(); TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 40, key: 'ArrowDown' }); expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', '.Select-option.is-focused'); }); it('doesn\'t select anything when all options are disabled and enter is pressed', () => { wrapper = createControlWithWrapper({ options: [ { value: 'one', label: 'One', disabled: true }, { value: 'two', label: 'Two', disabled: true }, { value: 'three', label: 'Three', disabled: true } ] }); clickArrowToOpen(); TestUtils.Simulate.keyDown(getSelectControl(instance), { keyCode: 13, key: 'Enter' }); expect(onChange, 'was not called'); expect(ReactDOM.findDOMNode(instance), 'queried for first', PLACEHOLDER_SELECTOR, 'to have text', 'Select...'); }); it("doesn't select anything when a disabled option is the only item in the list after a search", () => { typeSearchText('tw'); // Only 'two' in the list pressEnterToAccept(); expect(onChange, 'was not called'); // And the menu is still open expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', DISPLAYED_SELECTION_SELECTOR); expect(ReactDOM.findDOMNode(instance), 'queried for' , '.Select-option', 'to satisfy', [ expect.it('to have text', 'Two') ]); }); it("doesn't select anything when a disabled option value matches the entered text", () => { typeSearchText('two'); // Matches value pressEnterToAccept(); expect(onChange, 'was not called'); // And the menu is still open expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', DISPLAYED_SELECTION_SELECTOR); expect(ReactDOM.findDOMNode(instance), 'queried for' , '.Select-option', 'to satisfy', [ expect.it('to have text', 'Two') ]); }); it("doesn't select anything when a disabled option label matches the entered text", () => { typeSearchText('Two'); // Matches label pressEnterToAccept(); expect(onChange, 'was not called'); // And the menu is still open expect(ReactDOM.findDOMNode(instance), 'to contain no elements matching', DISPLAYED_SELECTION_SELECTOR); expect(ReactDOM.findDOMNode(instance), 'queried for' , '.Select-option', 'to satisfy', [ expect.it('to have text', 'Two') ]); }); it('shows disabled results in a search', () => { typeSearchText('t'); var options = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option'); expect(options[0], 'to have text', 'Two'); expect(options[0], 'to have attributes', { class: 'is-disabled' }); expect(options[1], 'to have text', 'Three'); }); it('is does not close menu when disabled option is clicked', () => { clickArrowToOpen(); TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option')[1]); var options = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option'); expect(options.length, 'to equal', 3); }); }); describe('with styled options', () => { beforeEach(() => { options = [ { value: 'one', label: 'One', className: 'extra-one', title: 'Eins' }, { value: 'two', label: 'Two', className: 'extra-two', title: 'Zwei' }, { value: 'three', label: 'Three', style: { fontSize: 25 } } ]; wrapper = createControlWithWrapper({ options: options }, { wireUpOnChangeToValue: true }); }); it('uses the given className for an option', () => { clickArrowToOpen(); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option')[0], 'to have attributes', { class: 'extra-one' }); }); it('uses the given style for an option', () => { clickArrowToOpen(); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option')[2], 'to have attributes', { style: { 'font-size': '25px' } }); }); it('uses the given title for an option', () => { clickArrowToOpen(); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option')[1], 'to have attributes', { title: 'Zwei' }); }); it('uses the given className for a single selection', () => { typeSearchText('tw'); pressEnterToAccept(); expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR, 'to have attributes', { class: 'extra-two' }); }); it('uses the given style for a single selection', () => { typeSearchText('th'); pressEnterToAccept(); expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR, 'to have attributes', { style: { 'font-size': '25px' } }); }); it('uses the given title for a single selection', () => { typeSearchText('tw'); pressEnterToAccept(); expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR, 'to have attributes', { title: 'Zwei' }); }); describe('with multi', () => { beforeEach(() => { wrapper.setPropsForChild({ multi: true }); }); it('uses the given className for a selected value', () => { typeSearchText('tw'); pressEnterToAccept(); expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-value', 'to have attributes', { class: 'extra-two' }); }); it('uses the given style for a selected value', () => { typeSearchText('th'); pressEnterToAccept(); expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-value', 'to have attributes', { style: { 'font-size': '25px' } }); }); it('uses the given title for a selected value', () => { typeSearchText('tw'); pressEnterToAccept(); expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-value', 'to have attributes', { title: 'Zwei' }); }); }); }); describe('with allowCreate=true', () => { // TODO: allowCreate hasn't been implemented yet in 1.x return; beforeEach(() => { options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' }, { value: 'got spaces', label: 'Label for spaces' }, { value: 'gotnospaces', label: 'Label for gotnospaces' }, { value: 'abc 123', label: 'Label for abc 123' }, { value: 'three', label: 'Three' }, { value: 'zzzzz', label: 'test value' } ]; // Render an instance of the component wrapper = createControlWithWrapper({ value: 'one', options: options, allowCreate: true, searchable: true, addLabelText: 'Add {label} to values?' }); }); it('has an "Add xyz" option when entering xyz', () => { typeSearchText('xyz'); expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-menu .Select-option', 'to have items satisfying', 'to have text', 'Add xyz to values?'); }); it('fires an onChange with the new value when selecting the Add option', () => { typeSearchText('xyz'); TestUtils.Simulate.click(ReactDOM.findDOMNode(instance).querySelector('.Select-menu .Select-option')); expect(onChange, 'was called with', 'xyz'); }); it('allows updating the options with a new label, following the onChange', () => { typeSearchText('xyz'); TestUtils.Simulate.click(ReactDOM.findDOMNode(instance).querySelector('.Select-menu .Select-option')); expect(onChange, 'was called with', 'xyz'); // Now the client adds the option, with a new label wrapper.setPropsForChild({ options: [ { value: 'one', label: 'One' }, { value: 'xyz', label: 'XYZ Label' } ], value: 'xyz' }); expect(ReactDOM.findDOMNode(instance).querySelector(DISPLAYED_SELECTION_SELECTOR), 'to have text', 'XYZ Label'); }); it('displays an add option when a value with spaces is entered', () => { typeSearchText('got'); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option')[0], 'to have text', 'Add got to values?'); }); it('displays an add option when a value with spaces is entered', () => { typeSearchText('got'); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option')[0], 'to have text', 'Add got to values?'); }); it('displays an add option when a label with spaces is entered', () => { typeSearchText('test'); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option')[0], 'to have text', 'Add test to values?'); }); it('does not display the option label when an existing value is entered', () => { typeSearchText('zzzzz'); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option'), 'to have length', 1); expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-menu .Select-option', 'to have text', 'Add zzzzz to values?'); }); it('renders the existing option and an add option when an existing display label is entered', () => { typeSearchText('test value'); // First item should be the add option (as the "value" is not in the collection) expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option')[0], 'to have text', 'Add test value to values?'); // Second item should be the existing option with the matching label expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option')[1], 'to have text', 'test value'); expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option'), 'to have length', 2); }); }); describe('with multi-select', () => { beforeEach(() => { options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two', clearableValue: false }, { value: 'three', label: 'Three' }, { value: 'four', label: 'Four' } ]; // Render an instance of the component wrapper = createControlWithWrapper({ value: '', options: options, searchable: true, multi: true }); }); it('selects a single option on enter', () => { typeSearchText('fo'); pressEnterToAccept(); expect(onChange, 'was called with', [{ label: 'Four', value: 'four' }]); }); describe('when using the option value object', () => { it('selects an additional option', () => { setValueProp(options[3]); typeSearchText('th'); onChange.reset(); // Ignore previous onChange calls pressEnterToAccept(); expect(onChange, 'was called with', [{ label: 'Four', value: 'four' }, { label: 'Three', value: 'three' }]); }); it('displays both selected options', () => { setValueProp([options[3], options[2]]); expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">Four</span></div> <div><span className="Select-value-label">Three</span></div> </span>); }); }); describe('when using the option value', () => { it('selects an additional option', () => { setValueProp('four'); typeSearchText('th'); onChange.reset(); // Ignore previous onChange calls pressEnterToAccept(); expect(onChange, 'was called with', [{ label: 'Four', value: 'four' }, { label: 'Three', value: 'three' }]); }); it('displays both selected options', () => { setValueProp(['four', 'three']); expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">Four</span></div> <div><span className="Select-value-label">Three</span></div> </span>); }); }); it('filters the existing selections from the options', () => { setValueProp(['four','three']); typeSearchText('o'); var options = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option'); expect(options[0], 'to have text', 'One'); expect(options[1], 'to have text', 'Two'); expect(options, 'to have length', 2); // No "Four", as already selected }); it('removes the last selected option with backspace', () => { setValueProp(['four','three']); onChange.reset(); // Ignore previous onChange calls pressBackspace(); expect(onChange, 'was called with', [{ label: 'Four', value: 'four' }]); }); it('does not remove the last selected option with backspace when backspaceRemoves=false', () => { // Disable backspace wrapper.setPropsForChild({ backspaceRemoves: false, value: ['four', 'three'] }); onChange.reset(); // Ignore previous onChange calls pressBackspace(); expect(onChange, 'was not called'); expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">Four</span></div> <div><span className="Select-value-label">Three</span></div> </span>); }); it('removes an item when clicking on the X', () => { setValueProp(['four', 'three', 'two']); onChange.reset(); // Ignore previous onChange calls var threeDeleteButton = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-value-icon')[1]; TestUtils.Simulate.mouseDown(threeDeleteButton); expect(onChange, 'was called with', [ { label: 'Four', value: 'four' }, { label: 'Two', value: 'two' } ]); }); it('doesn\'t show the X if clearableValue=false', () => { setValueProp(['two']); onChange.reset(); // Ignore previous onChange calls var twoDeleteButton = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-value-icon')[0]; expect(twoDeleteButton, 'to be', undefined); }); it('doesn\'t allow clearing with backspace if clearableValue=false on the latest element', () => { setValueProp(['four', 'two']); onChange.reset(); // Ignore previous onChange calls pressBackspace(); expect(onChange, 'was not called'); expect(instance, 'to contain', <span className="Select-multi-value-wrapper"> <div><span className="Select-value-label">Four</span></div> <div><span className="Select-value-label">Two</span></div> </span>); }); describe('with late options', () => { beforeEach(() => { wrapper = createControlWithWrapper({ multi: true, options: options, value: 'one,two' }); }); it('updates the label when the options are updated', () => { wrapper.setPropsForChild({ options: [ { value: 'one', label: 'new label for One' }, { value: 'two', label: 'new label for Two' }, { value: 'three', label: 'new label for Three' } ] }); expect(instance, 'to contain',