From 1f9a95a9dd5a0cd89d6747a6b0130820d9cb0f69 Mon Sep 17 00:00:00 2001 From: spittank Date: Mon, 10 Sep 2018 14:41:23 +0200 Subject: [PATCH 1/2] Fixed search in option list with groups --- .flowconfig | 1 + src/Typeahead.jsx | 24 +++++++--- src/Typeahead.spec.jsx | 101 +++++++++++++++++++++++++---------------- 3 files changed, 80 insertions(+), 46 deletions(-) diff --git a/.flowconfig b/.flowconfig index 363b337..b68c78d 100644 --- a/.flowconfig +++ b/.flowconfig @@ -7,3 +7,4 @@ [lints] [options] +suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe \ No newline at end of file diff --git a/src/Typeahead.jsx b/src/Typeahead.jsx index 81ec75b..d124ba9 100644 --- a/src/Typeahead.jsx +++ b/src/Typeahead.jsx @@ -186,8 +186,9 @@ export default class Typeahead extends PureComponent { this.setState({ typedLabel: label }, () => { + const highlightedRowIndex = this._gethighlightedRowIndexByTypedLabel(); this.setState({ - highlightedRowIndex: this._gethighlightedRowIndexByTypedLabel() + highlightedRowIndex: highlightedRowIndex }); if (label === '' && this.props.isClearable) { @@ -256,9 +257,15 @@ export default class Typeahead extends PureComponent { }; _gethighlightedRowIndexByTypedLabel = (): number | typeof undefined => { - const optionIndex = this._getFilteredOptions().findIndex(this._byTypedLabel); - const typedLabelFoundInOptions = optionIndex !== -1; - return typedLabelFoundInOptions ? optionIndex : NOTHING_HIGHLIGHTED; + const rows = this._generateRows( + this._getFilteredOptions(), + this.props.groups, + this.props, + this._isUnknownValue() + ); + // $FlowFixMe Flow does not recognize that filter narrowed down the list to only OptionRows + const foundRow = rows.filter(_isOptionRow).find(this._rowByTypedLabel); + return foundRow ? foundRow.rowIndex : NOTHING_HIGHLIGHTED; }; _updateValue = (afterValueUpdated: Function): void => { @@ -378,6 +385,9 @@ export default class Typeahead extends PureComponent { _byTypedLabel = (option: Option | Group) => this._typedLabelHasText() && option.label.toLowerCase().includes(this.state.typedLabel.toLowerCase()); + _rowByTypedLabel = (row: OptionRow) => this._typedLabelHasText() && + row.option.label.toLowerCase().includes(this.state.typedLabel.toLowerCase()); + _byGroupAndTypedLabel = (option: Option) => { if (this.props.groups !== undefined) { const matchingGroupValues = this.props.groups.filter(this._byTypedLabel).map(group => group.value); @@ -636,8 +646,8 @@ export default class Typeahead extends PureComponent { return this.props.menuWidth ? this.props.menuWidth : this.props.estimateMenuWidth - ? this._estimateMenuWidth(this.props.estimateMenuWidth, rows) - : undefined; + ? this._estimateMenuWidth(this.props.estimateMenuWidth, rows) + : undefined; }; renderMenu(): Node { @@ -784,7 +794,7 @@ function _estimateMenuWidth(rows: Row[]): Optional { : undefined; } -function _isOptionRow(row) { +function _isOptionRow(row: Row): boolean { return row.hasOwnProperty('option'); } diff --git a/src/Typeahead.spec.jsx b/src/Typeahead.spec.jsx index ca72f35..f24894e 100644 --- a/src/Typeahead.spec.jsx +++ b/src/Typeahead.spec.jsx @@ -1,9 +1,6 @@ import 'raf/polyfill'; import React from 'react'; -import Enzyme, { - mount, - shallow -} from 'enzyme'; +import Enzyme, {mount, shallow} from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import Typeahead from './Typeahead'; @@ -201,21 +198,21 @@ describe('Typeahead should', () => { expect(value2Option.exists()).toBe(true); }); - it('highlight option with value from props', function () { + it('highlight option with value from props', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); const value1Option = wrapper.find('.typeahead__option[data-value="value1"]'); expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight no option when no value is set', function () { + it('highlight no option when no value is set', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); const highlightedOption = wrapper.find('.typeahead__option[data-highlighted=true]'); expect(highlightedOption.exists()).toEqual(false); }); - it('highlight first option when no value is set and arrow down key is pressed', function () { + it('highlight first option when no value is set and arrow down key is pressed', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -223,7 +220,7 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight first option when no value is set and arrow down key is pressed with groups', function () { + it('highlight first option when no value is set and arrow down key is pressed with groups', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -231,7 +228,7 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('not highlight any option when no options exist and arrow down key is pressed with groups', function () { + it('not highlight any option when no options exist and arrow down key is pressed with groups', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -239,7 +236,7 @@ describe('Typeahead should', () => { expect(value1Option.exists()).toEqual(false); }); - it('highlight special option when unknown value is set and arrow up key is pressed', function () { + it('highlight special option when unknown value is set and arrow up key is pressed', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); @@ -249,7 +246,7 @@ describe('Typeahead should', () => { expect(Boolean(unknownValueOption.prop('data-highlighted'))).toEqual(true); }); - it('highlight second option when no value is set and arrow down key is pressed twice', function () { + it('highlight second option when no value is set and arrow down key is pressed twice', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -258,7 +255,7 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight last option when arrow down key is pressed more often than options exist', function () { + it('highlight last option when arrow down key is pressed more often than options exist', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -275,34 +272,36 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight second option in a filtered list when no value is set and arrow down key is pressed twice', function () { - const wrapper = mount(); - wrapper.find('input').simulate('focus'); - simulateKeys(wrapper.find('input'), 'spe'); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - const value1Option = wrapper.find('.typeahead__option[data-value="value4"]'); - expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); - }); + it('highlight second option in a filtered list when no value is set and arrow down key is pressed twice', + function() { + const wrapper = mount(); + wrapper.find('input').simulate('focus'); + simulateKeys(wrapper.find('input'), 'spe'); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + const value1Option = wrapper.find('.typeahead__option[data-value="value4"]'); + expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); + }); - it('highlight the right option in a filtered list when no value is set and arrow down key is pressed twice', function () { - const optionsForFilter = [ - {label: 'nomatch', value: 'value1'}, - {label: 'nomatch', value: 'value2'}, - {label: 'match haus', value: 'value3'}, - {label: 'nomatch', value: 'value4'}, - {label: 'match haus 2', value: 'value5'} - ]; - const wrapper = mount(); - wrapper.find('input').simulate('focus'); - simulateKeys(wrapper.find('input'), 'haus'); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - const value1Option = wrapper.find('.typeahead__option[data-value="value5"]'); - expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); - }); + it('highlight the right option in a filtered list when no value is set and arrow down key is pressed twice', + function() { + const optionsForFilter = [ + {label: 'nomatch', value: 'value1'}, + {label: 'nomatch', value: 'value2'}, + {label: 'match haus', value: 'value3'}, + {label: 'nomatch', value: 'value4'}, + {label: 'match haus 2', value: 'value5'} + ]; + const wrapper = mount(); + wrapper.find('input').simulate('focus'); + simulateKeys(wrapper.find('input'), 'haus'); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + const value1Option = wrapper.find('.typeahead__option[data-value="value5"]'); + expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); + }); - it('highlight first option when no value is set and arrow up key is pressed', function () { + it('highlight first option when no value is set and arrow up key is pressed', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_UP}); @@ -444,7 +443,7 @@ describe('Typeahead should', () => { expect(wrapper.state('isOpen')).toBe(false); }); - it('close menu when option is clicked', function () { + it('close menu when option is clicked', function() { const wrapper = mount(); wrapper.find('input').simulate('focus'); const value2Option = wrapper.find('.typeahead__option[data-value="value1"]'); @@ -874,6 +873,30 @@ describe('Typeahead should', () => { expect(group2.exists()).toBe(false); }); + it('select single option when exactly searching for an option in a grouped list', () => { + const singleGroup = [ + {label: 'Group 1', value: 'group1'} + ]; + + const optionsWithGroups = [ + {label: 'label1', value: 'value1', group: 'group1'}, + {label: 'label2', value: 'value2', group: 'group1'} + ]; + + const wrapper = mount(); + + wrapper.find('input').simulate('focus'); + simulateKeys(wrapper.find('input'), 'label2'); + + const value2Option = wrapper.find('.typeahead__option[data-value="value2"]'); + expect(value2Option.exists()).toBe(true); + expect(value2Option.prop('data-group')).toEqual('group1'); + expect(Boolean(value2Option.prop('data-highlighted'))).toEqual(true); + + wrapper.find('input').simulate('blur'); + expect(wrapper.state('value')).toEqual('value2'); + }); + it('render empty groups when renderEmptyGroups prop is true', () => { const wrapper = mount(); From 1e6eefa0e41d9c74faa06797aa9a709c4c74d793 Mon Sep 17 00:00:00 2001 From: spittank Date: Mon, 10 Sep 2018 14:57:44 +0200 Subject: [PATCH 2/2] Undo auto format --- src/Typeahead.spec.jsx | 122 ++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 63 deletions(-) diff --git a/src/Typeahead.spec.jsx b/src/Typeahead.spec.jsx index f24894e..c999aa9 100644 --- a/src/Typeahead.spec.jsx +++ b/src/Typeahead.spec.jsx @@ -1,6 +1,9 @@ import 'raf/polyfill'; import React from 'react'; -import Enzyme, {mount, shallow} from 'enzyme'; +import Enzyme, { + mount, + shallow +} from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import Typeahead from './Typeahead'; @@ -198,21 +201,21 @@ describe('Typeahead should', () => { expect(value2Option.exists()).toBe(true); }); - it('highlight option with value from props', function() { + it('highlight option with value from props', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); const value1Option = wrapper.find('.typeahead__option[data-value="value1"]'); expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight no option when no value is set', function() { + it('highlight no option when no value is set', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); const highlightedOption = wrapper.find('.typeahead__option[data-highlighted=true]'); expect(highlightedOption.exists()).toEqual(false); }); - it('highlight first option when no value is set and arrow down key is pressed', function() { + it('highlight first option when no value is set and arrow down key is pressed', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -220,7 +223,7 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight first option when no value is set and arrow down key is pressed with groups', function() { + it('highlight first option when no value is set and arrow down key is pressed with groups', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -228,7 +231,7 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('not highlight any option when no options exist and arrow down key is pressed with groups', function() { + it('not highlight any option when no options exist and arrow down key is pressed with groups', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -236,7 +239,7 @@ describe('Typeahead should', () => { expect(value1Option.exists()).toEqual(false); }); - it('highlight special option when unknown value is set and arrow up key is pressed', function() { + it('highlight special option when unknown value is set and arrow up key is pressed', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); @@ -246,7 +249,7 @@ describe('Typeahead should', () => { expect(Boolean(unknownValueOption.prop('data-highlighted'))).toEqual(true); }); - it('highlight second option when no value is set and arrow down key is pressed twice', function() { + it('highlight second option when no value is set and arrow down key is pressed twice', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -255,7 +258,7 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight last option when arrow down key is pressed more often than options exist', function() { + it('highlight last option when arrow down key is pressed more often than options exist', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); @@ -272,36 +275,34 @@ describe('Typeahead should', () => { expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); }); - it('highlight second option in a filtered list when no value is set and arrow down key is pressed twice', - function() { - const wrapper = mount(); - wrapper.find('input').simulate('focus'); - simulateKeys(wrapper.find('input'), 'spe'); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - const value1Option = wrapper.find('.typeahead__option[data-value="value4"]'); - expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); - }); + it('highlight second option in a filtered list when no value is set and arrow down key is pressed twice', function () { + const wrapper = mount(); + wrapper.find('input').simulate('focus'); + simulateKeys(wrapper.find('input'), 'spe'); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + const value1Option = wrapper.find('.typeahead__option[data-value="value4"]'); + expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); + }); - it('highlight the right option in a filtered list when no value is set and arrow down key is pressed twice', - function() { - const optionsForFilter = [ - {label: 'nomatch', value: 'value1'}, - {label: 'nomatch', value: 'value2'}, - {label: 'match haus', value: 'value3'}, - {label: 'nomatch', value: 'value4'}, - {label: 'match haus 2', value: 'value5'} - ]; - const wrapper = mount(); - wrapper.find('input').simulate('focus'); - simulateKeys(wrapper.find('input'), 'haus'); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); - const value1Option = wrapper.find('.typeahead__option[data-value="value5"]'); - expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); - }); + it('highlight the right option in a filtered list when no value is set and arrow down key is pressed twice', function () { + const optionsForFilter = [ + {label: 'nomatch', value: 'value1'}, + {label: 'nomatch', value: 'value2'}, + {label: 'match haus', value: 'value3'}, + {label: 'nomatch', value: 'value4'}, + {label: 'match haus 2', value: 'value5'} + ]; + const wrapper = mount(); + wrapper.find('input').simulate('focus'); + simulateKeys(wrapper.find('input'), 'haus'); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + wrapper.find('input').simulate('keyDown', {keyCode: KEY_DOWN}); + const value1Option = wrapper.find('.typeahead__option[data-value="value5"]'); + expect(Boolean(value1Option.prop('data-highlighted'))).toEqual(true); + }); - it('highlight first option when no value is set and arrow up key is pressed', function() { + it('highlight first option when no value is set and arrow up key is pressed', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); wrapper.find('input').simulate('keyDown', {keyCode: KEY_UP}); @@ -443,7 +444,7 @@ describe('Typeahead should', () => { expect(wrapper.state('isOpen')).toBe(false); }); - it('close menu when option is clicked', function() { + it('close menu when option is clicked', function () { const wrapper = mount(); wrapper.find('input').simulate('focus'); const value2Option = wrapper.find('.typeahead__option[data-value="value1"]'); @@ -848,6 +849,25 @@ describe('Typeahead should', () => { expect(group2.exists()).toBe(true); }); + it('select single option when exactly searching for an option in a grouped list', () => { + const singleGroup = [ + {label: 'Group 1', value: 'group1'} + ]; + const optionsWithGroups = [ + {label: 'label1', value: 'value1', group: 'group1'}, + {label: 'label2', value: 'value2', group: 'group1'} + ]; + const wrapper = mount(); + wrapper.find('input').simulate('focus'); + simulateKeys(wrapper.find('input'), 'label2'); + const value2Option = wrapper.find('.typeahead__option[data-value="value2"]'); + expect(value2Option.exists()).toBe(true); + expect(value2Option.prop('data-group')).toEqual('group1'); + expect(Boolean(value2Option.prop('data-highlighted'))).toEqual(true); + wrapper.find('input').simulate('blur'); + expect(wrapper.state('value')).toEqual('value2'); + }); + it('render options of a group when searching for a group label', () => { const wrapper = mount(); wrapper.find('input').simulate('focus'); @@ -873,30 +893,6 @@ describe('Typeahead should', () => { expect(group2.exists()).toBe(false); }); - it('select single option when exactly searching for an option in a grouped list', () => { - const singleGroup = [ - {label: 'Group 1', value: 'group1'} - ]; - - const optionsWithGroups = [ - {label: 'label1', value: 'value1', group: 'group1'}, - {label: 'label2', value: 'value2', group: 'group1'} - ]; - - const wrapper = mount(); - - wrapper.find('input').simulate('focus'); - simulateKeys(wrapper.find('input'), 'label2'); - - const value2Option = wrapper.find('.typeahead__option[data-value="value2"]'); - expect(value2Option.exists()).toBe(true); - expect(value2Option.prop('data-group')).toEqual('group1'); - expect(Boolean(value2Option.prop('data-highlighted'))).toEqual(true); - - wrapper.find('input').simulate('blur'); - expect(wrapper.state('value')).toEqual('value2'); - }); - it('render empty groups when renderEmptyGroups prop is true', () => { const wrapper = mount(); @@ -1131,4 +1127,4 @@ describe('Typeahead should', () => { window.top = top; window.dispatchEvent(resizeEvent); } -}); +}); \ No newline at end of file