react-select
Version:
A Select control built with and for ReactJS
1,603 lines (1,271 loc) • 104 kB
JavaScript
'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',