Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #546. Added support for boolean values. #1224

Merged
merged 1 commit into from
Sep 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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