diff --git a/examples/async-data/app.js b/examples/async-data/app.js index c1ff0b17..05a149ed 100644 --- a/examples/async-data/app.js +++ b/examples/async-data/app.js @@ -6,6 +6,7 @@ let App = React.createClass({ getInitialState () { return { + value: '', unitedStates: getStates(), loading: false } @@ -27,16 +28,17 @@ let App = React.createClass({ labelText="Choose a state from the US" inputProps={{name: "US state"}} ref="autocomplete" + value={this.state.value} items={this.state.unitedStates} getItemValue={(item) => item.name} onSelect={(value, item) => { // set the menu to only the selected item - this.setState({ unitedStates: [ item ] }) + this.setState({ value, unitedStates: [ item ] }) // or you could reset it to a default list again // this.setState({ unitedStates: getStates() }) }} onChange={(event, value) => { - this.setState({loading: true}) + this.setState({ value, loading: true }) fakeRequest(value, (items) => { this.setState({ unitedStates: items, loading: false }) }) diff --git a/examples/custom-menu/app.js b/examples/custom-menu/app.js index 1ea62c24..4fe08a44 100644 --- a/examples/custom-menu/app.js +++ b/examples/custom-menu/app.js @@ -6,6 +6,7 @@ let App = React.createClass({ getInitialState () { return { + value: '', unitedStates: getStates(), loading: false } @@ -21,13 +22,14 @@ let App = React.createClass({ letter of the alphabet.

item.name} - onSelect={() => this.setState({ unitedStates: [] }) } + onSelect={value => this.setState({ value, unitedStates: [] }) } onChange={(event, value) => { - this.setState({loading: true}) + this.setState({ value, loading: true }) fakeRequest(value, (items) => { this.setState({ unitedStates: items, loading: false }) }) diff --git a/examples/static-data/app.js b/examples/static-data/app.js index 9def5b08..e3022f2e 100644 --- a/examples/static-data/app.js +++ b/examples/static-data/app.js @@ -3,6 +3,9 @@ import { getStates, matchStateToTerm, sortStates, styles } from '../../lib/utils import Autocomplete from '../../lib/index' let App = React.createClass({ + getInitialState() { + return { value: 'Ma' } + }, render () { return (
@@ -14,13 +17,15 @@ let App = React.createClass({

item.name} shouldItemRender={matchStateToTerm} sortItems={sortStates} + onChange={(event, value) => this.setState({ value })} + onSelect={value => this.setState({ value })} renderItem={(item, isHighlighted) => (
{ - this.props.onChange(event, this.state.value) - }) + this.props.onChange(event, event.target.value) }, handleKeyUp () { @@ -151,17 +147,17 @@ let Autocomplete = React.createClass({ else { // text entered + menu item has been highlighted + enter is hit -> update value to that of selected menu item, close the menu var item = this.getFilteredItems()[this.state.highlightedIndex] + var value = this.props.getItemValue(item) this.setState({ - value: this.props.getItemValue(item), isOpen: false, highlightedIndex: null }, () => { //this.refs.input.focus() // TODO: file issue this.refs.input.setSelectionRange( - this.state.value.length, - this.state.value.length + value.length, + value.length ) - this.props.onSelect(this.state.value, item) + this.props.onSelect(value, item) }) } }, @@ -179,13 +175,13 @@ let Autocomplete = React.createClass({ if (this.props.shouldItemRender) { items = items.filter((item) => ( - this.props.shouldItemRender(item, this.state.value) + this.props.shouldItemRender(item, this.props.value) )) } if (this.props.sortItems) { items.sort((a, b) => ( - this.props.sortItems(a, b, this.state.value) + this.props.sortItems(a, b, this.props.value) )) } @@ -193,7 +189,7 @@ let Autocomplete = React.createClass({ }, maybeAutoCompleteText () { - if (this.state.value === '') + if (this.props.value === '') return var { highlightedIndex } = this.state var items = this.getFilteredItems() @@ -203,13 +199,13 @@ let Autocomplete = React.createClass({ items[highlightedIndex] : items[0] var itemValue = this.props.getItemValue(matchedItem) var itemValueDoesMatch = (itemValue.toLowerCase().indexOf( - this.state.value.toLowerCase() + this.props.value.toLowerCase() ) === 0) if (itemValueDoesMatch) { var node = this.refs.input var setSelection = () => { node.value = itemValue - node.setSelectionRange(this.state.value.length, itemValue.length) + node.setSelectionRange(this.props.value.length, itemValue.length) } if (highlightedIndex === null) this.setState({ highlightedIndex: 0 }, setSelection) @@ -237,12 +233,12 @@ let Autocomplete = React.createClass({ }, selectItemFromMouse (item) { + var value = this.props.getItemValue(item); this.setState({ - value: this.props.getItemValue(item), isOpen: false, highlightedIndex: null }, () => { - this.props.onSelect(this.state.value, item) + this.props.onSelect(value, item) this.refs.input.focus() this.setIgnoreBlur(false) }) @@ -271,7 +267,7 @@ let Autocomplete = React.createClass({ top: this.state.menuTop, minWidth: this.state.menuWidth, } - var menu = this.props.renderMenu(items, this.state.value, style) + var menu = this.props.renderMenu(items, this.props.value, style) return React.cloneElement(menu, { ref: 'menu' }) }, @@ -318,7 +314,7 @@ let Autocomplete = React.createClass({ onKeyDown={(event) => this.handleKeyDown(event)} onKeyUp={(event) => this.handleKeyUp(event)} onClick={this.handleInputClick} - value={this.state.value} + value={this.props.value} id={this.id} /> {this.state.isOpen && this.renderMenu()} diff --git a/lib/__tests__/Autocomplete-test.js b/lib/__tests__/Autocomplete-test.js index 0ed62408..f35a04fd 100644 --- a/lib/__tests__/Autocomplete-test.js +++ b/lib/__tests__/Autocomplete-test.js @@ -15,7 +15,6 @@ chai.use(chaiEnzyme()); function AutocompleteComponentJSX (extraProps) { return ( item.name} @@ -49,11 +48,11 @@ describe('Autocomplete acceptance tests', () => { expect(autocompleteWrapper.instance().refs.menu).to.exist; }); - it('should show results when partial match is typed in', () => { + it('should show results when value is a partial match', () => { // Render autocomplete results upon partial input match expect(autocompleteWrapper.ref('menu').children()).to.have.length(50); - autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } }); + autocompleteWrapper.setProps({ value: 'Ar' }); expect(autocompleteWrapper.ref('menu').children()).to.have.length(6); }); @@ -71,6 +70,27 @@ describe('Autocomplete acceptance tests', () => { // Event handler unit tests +describe('Autocomplete keyPress-> event handlers', () => { + + var autocompleteWrapper = mount(AutocompleteComponentJSX({})); + var autocompleteInputWrapper = autocompleteWrapper.find('input'); + + it('should pass updated `input.value` to `onChange` and replace with `props.value`', done => { + + let value = ''; + autocompleteWrapper.setProps({ value, onChange(_, v) { value = v; } }); + + autocompleteInputWrapper.get(0).value = 'a'; + autocompleteInputWrapper.simulate('keyPress', { key : 'a', keyCode: 97, which: 97 }); + autocompleteInputWrapper.simulate('change'); + + expect(autocompleteInputWrapper.get(0).value).to.equal(''); + expect(value).to.equal('a'); + done(); + }); + +}); + describe('Autocomplete kewDown->ArrowDown event handlers', () => { var autocompleteWrapper = mount(AutocompleteComponentJSX({})); @@ -173,9 +193,10 @@ describe('Autocomplete kewDown->Enter event handlers', () => { }); it('should close menu if input has focus but no item has been selected and then the Enter key is hit', () => { + let value = ''; autocompleteWrapper.setState({'isOpen': true}); autocompleteInputWrapper.simulate('focus'); - autocompleteInputWrapper.simulate('change', { target: { value: '' } }); + autocompleteWrapper.setProps({ value, onSelect(v) { value = v; } }); // simulate keyUp of backspace, triggering autocomplete suggestion on an empty string, which should result in nothing highlighted autocompleteInputWrapper.simulate('keyUp', { key : 'Backspace', keyCode: 8, which: 8 }); @@ -183,22 +204,23 @@ describe('Autocomplete kewDown->Enter event handlers', () => { autocompleteInputWrapper.simulate('keyDown', { key : 'Enter', keyCode: 13, which: 13 }); - expect(autocompleteWrapper.state('value')).to.equal(''); + expect(value).to.equal(''); expect(autocompleteWrapper.state('isOpen')).to.be.false; }); - it('should update input value from selected menu item and close the menu', () => { + it('should invoke `onSelect` with the selected menu item and close the menu', () => { + let value = 'Ar'; autocompleteWrapper.setState({'isOpen': true}); autocompleteInputWrapper.simulate('focus'); - autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } }); + autocompleteWrapper.setProps({ value, onSelect(v) { value = v; } }); // simulate keyUp of last key, triggering autocomplete suggestion + selection of the suggestion in the menu autocompleteInputWrapper.simulate('keyUp', { key : 'r', keyCode: 82, which: 82 }); // Hit enter, updating state.value with the selected Autocomplete suggestion autocompleteInputWrapper.simulate('keyDown', { key : 'Enter', keyCode: 13, which: 13 }); - expect(autocompleteWrapper.state('value')).to.equal('Arizona'); + expect(value).to.equal('Arizona'); expect(autocompleteWrapper.state('isOpen')).to.be.false; }); @@ -238,7 +260,7 @@ describe('Autocomplete#renderMenu', () => { it('should return a menu ReactComponent with a subset of children when partial match text has been entered', () => { // Input 'Ar' should result in 6 items in the menu, populated from autocomplete. - autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } }); + autocompleteWrapper.setProps({ value: 'Ar' }); var autocompleteMenu = autocompleteWrapper.instance().renderMenu(); expect(autocompleteMenu.props.children.length).to.be.equal(6);