Skip to content

Commit

Permalink
Fixed #546. Added support for boolean values.
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaronius committed Sep 16, 2016
1 parent 15d0bb8 commit e3fb016
Show file tree
Hide file tree
Showing 4 changed files with 355 additions and 4 deletions.
2 changes: 2 additions & 0 deletions examples/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CustomComponents from './components/CustomComponents';
import CustomRender from './components/CustomRender';
import Multiselect from './components/Multiselect';
import NumericSelect from './components/NumericSelect';
import BooleanSelect from './components/BooleanSelect';
import Virtualized from './components/Virtualized';
import States from './components/States';

Expand All @@ -22,6 +23,7 @@ ReactDOM.render(
<Contributors label="Contributors (Async)" />
<GithubUsers label="Github users (Async with fetch.js)" />
<NumericSelect label="Numeric Values" />
<BooleanSelect label="Boolean Values" />
<CustomRender label="Custom Render Methods"/>
<CustomComponents label="Custom Placeholder, Option, Value, and Arrow Components" />
<Creatable
Expand Down
90 changes: 90 additions & 0 deletions examples/src/components/BooleanSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';
import Select from 'react-select';

var ValuesAsBooleansField = React.createClass({
displayName: 'ValuesAsBooleansField',
propTypes: {
label: React.PropTypes.string
},
getInitialState () {
return {
options: [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' }
],
matchPos: 'any',
matchValue: true,
matchLabel: true,
value: null,
multi: false
};
},
onChangeMatchStart(event) {
this.setState({
matchPos: event.target.checked ? 'start' : 'any'
});
},
onChangeMatchValue(event) {
this.setState({
matchValue: event.target.checked
});
},
onChangeMatchLabel(event) {
this.setState({
matchLabel: event.target.checked
});
},
onChange(value) {
this.setState({ value });
console.log('Boolean Select value changed to', value);
},
onChangeMulti(event) {
this.setState({
multi: event.target.checked
});
},
render () {
var matchProp = 'any';
if (this.state.matchLabel && !this.state.matchValue) {
matchProp = 'label';
}
if (!this.state.matchLabel && this.state.matchValue) {
matchProp = 'value';
}
return (
<div className="section">
<h3 className="section-heading">{this.props.label}</h3>
<Select
matchPos={this.state.matchPos}
matchProp={matchProp}
multi={this.state.multi}
onChange={this.onChange}
options={this.state.options}
simpleValue
value={this.state.value}
/>
<div className="checkbox-list">
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.multi} onChange={this.onChangeMulti} />
<span className="checkbox-label">Multi-Select</span>
</label>
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.matchValue} onChange={this.onChangeMatchValue} />
<span className="checkbox-label">Match value</span>
</label>
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.matchLabel} onChange={this.onChangeMatchLabel} />
<span className="checkbox-label">Match label</span>
</label>
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.matchPos === 'start'} onChange={this.onChangeMatchStart} />
<span className="checkbox-label">Only include matches from the start of the string</span>
</label>
</div>
<div className="hint">This example uses simple boolean values</div>
</div>
);
}
});

module.exports = ValuesAsBooleansField;
10 changes: 6 additions & 4 deletions src/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import Option from './Option';
import Value from './Value';

function stringifyValue (value) {
if (typeof value === 'string') {
const valueType = typeof value;
if (valueType === 'string') {
return value;
} else if (typeof value === 'object') {
} else if (valueType === 'object') {
return JSON.stringify(value);
} else if (value || value === 0) {
} else if (valueType === 'number' || valueType === 'boolean') {
return String(value);
} else {
return '';
Expand Down Expand Up @@ -561,7 +562,8 @@ const Select = React.createClass({
* @param {Object} props - the Select component's props (or nextProps)
*/
expandValue (value, props) {
if (typeof value !== 'string' && typeof value !== 'number') return value;
const valueType = typeof value;
if (valueType !== 'string' && valueType !== 'number' && valueType !== 'boolean') return value;
let { options, valueKey } = props;
if (!options) return;
for (var i = 0; i < options.length; i++) {
Expand Down
257 changes: 257 additions & 0 deletions test/Select-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,263 @@ describe('Select', () => {
});
});

describe('with values as booleans', () => {
beforeEach(() => {
options = [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' },
];

wrapper = createControlWithWrapper({
value: true,
name: 'field',
options: options,
simpleValue: true,
});
});

it('selects the initial value', () => {
expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR,
'to have text', 'Yes');
});

it('set the initial value of the hidden input control', () => {
expect(ReactDOM.findDOMNode(wrapper).querySelector(FORM_VALUE_SELECTOR).value, 'to equal', 'true' );
});

it('updates the value when the value prop is set', () => {
wrapper.setPropsForChild({ value: false });
expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR,
'to have text', 'No');
});

it('updates the value of the hidden input control after new value prop', () => {
wrapper.setPropsForChild({ value: false });
expect(ReactDOM.findDOMNode(wrapper).querySelector(FORM_VALUE_SELECTOR).value, 'to equal', 'false' );
});

it('calls onChange with the new value as a boolean', () => {
clickArrowToOpen();
pressDown();
pressEnterToAccept();
expect(onChange, 'was called with', false);
});

it('supports setting the value via prop', () => {
wrapper.setPropsForChild({ value: false });
expect(ReactDOM.findDOMNode(instance), 'queried for first', DISPLAYED_SELECTION_SELECTOR,
'to have text', 'No');
});

describe('with multi=true', () => {

beforeEach(() => {

options = [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' }
];

wrapper = createControlWithWrapper({
value: [true, false],
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">Yes</span></div>
<div><span className="Select-value-label">No</span></div>
</span>);
});

it('calls onChange with the correct value when true option is deselected', () => {

var removeIcons = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-value .Select-value-icon');
TestUtils.Simulate.mouseDown(removeIcons[0]);
expect(onChange, 'was called with', [{ value: false, label: 'No' }]);
});

it('supports updating the values via props', () => {

wrapper.setPropsForChild({
value: [false]
});

expect(instance, 'to contain',
<span className="Select-multi-value-wrapper">
<div><span className="Select-value-label">No</span></div>
</span>);
});

it('supports updating the value to a single value', () => {

wrapper.setPropsForChild({
value: true
});

expect(instance, 'to contain',
<span className="Select-multi-value-wrapper">
<div><span className="Select-value-label">Yes</span></div>
</span>);
});

it('supports updating the value to single value of false', () => {

// This test is specifically in case there's a "if (value) {... " somewhere
wrapper.setPropsForChild({
value: false
});

expect(instance, 'to contain',
<span className="Select-multi-value-wrapper">
<div><span className="Select-value-label">No</span></div>
</span>);
});

it('calls onChange with the correct values when multiple options are selected', () => {
wrapper.setPropsForChild({
value: [true]
});

typeSearchText('No');
pressEnterToAccept(); // Select 'No'

expect(onChange, 'was called with', [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' }
]);
});
});

describe('searching', () => {

let searchOptions = [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' }
];

describe('with matchPos=any and matchProp=any', () => {
beforeEach(() => {
instance = createControl({
matchPos: 'any',
matchProp: 'any',
options: searchOptions
});
});

it('finds text anywhere in value', () => {

typeSearchText('fal');
expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option',
'to satisfy', [
expect.it('to have text', 'No'),
]);
});

it('finds text at end', () => {

typeSearchText('se');
expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option',
'to satisfy', [
expect.it('to have text', 'No'),
]);
});
});

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('fa');
expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option',
'to satisfy', [
expect.it('to have text', 'No')
]);
});

it('does not match text at end', () => {

typeSearchText('se');
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('al');
expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option',
'to satisfy', [
expect.it('to have text', 'No'),
]);
});

it('finds text at end', () => {

typeSearchText('e');
expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option',
'to satisfy', [
expect.it('to have text', 'Yes'),
expect.it('to have text', 'No')
]);
});
});

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('tr');
expect(ReactDOM.findDOMNode(instance), 'queried for', '.Select-option',
'to satisfy', [
expect.it('to have text', 'Yes')
]);
});

it('does not match text at end', () => {

typeSearchText('e');
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(() => {

Expand Down

0 comments on commit e3fb016

Please sign in to comment.