Skip to content

Commit

Permalink
Add onSuggestionHighlighted prop (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
hibiken authored and moroshko committed Jun 22, 2017
1 parent 858ce1a commit 3727af1
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 11 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class Example extends React.Component {
| [`renderSuggestion`](#renderSuggestionProp) | Function || Use your imagination to define how suggestions are rendered. |
| [`inputProps`](#inputPropsProp) | Object || Pass through arbitrary props to the input. It must contain at least `value` and `onChange`. |
| [`onSuggestionSelected`](#onSuggestionSelectedProp) | Function | | Will be called every time suggestion is selected via mouse or keyboard. |
| [`onSuggestionHighlighted`](#onSuggestionHighlightedProp) | Function | | Will be called every time the highlighted suggestion changes. |
| [`shouldRenderSuggestions`](#shouldRenderSuggestionsProp) | Function | | When the input is focused, Autosuggest will consult this function when to render suggestions. Use it, for example, if you want to display suggestions when input value is at least 2 characters long. |
| [`alwaysRenderSuggestions`](#alwaysRenderSuggestionsProp) | Boolean | | Set it to `true` if you'd like to render suggestions even when the input is not focused. |
| [`highlightFirstSuggestion`](#highlightFirstSuggestionProp) | Boolean | | Set it to `true` if you'd like Autosuggest to automatically highlight the first suggestion. |
Expand Down Expand Up @@ -372,6 +373,18 @@ where:
* `'click'` - user clicked (or tapped) on the suggestion
* `'enter'` - user selected the suggestion using <kbd>Enter</kbd>

<a name="onSuggestionHighlightedProp"></a>
#### onSuggestionHighlighted (optional)

This function is called when the highlighted suggestion changes. It has the following signature:

```js
function onSuggestionHighlighted({ suggestion })
```

where:
* `suggestion` - the highlighted suggestion, or `null` if there is no highlighted suggestion.

<a name="shouldRenderSuggestionsProp"></a>
#### shouldRenderSuggestions (optional)

Expand Down
20 changes: 20 additions & 0 deletions src/Autosuggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default class Autosuggest extends Component {
}
},
onSuggestionSelected: PropTypes.func,
onSuggestionHighlighted: PropTypes.func,
renderInputComponent: PropTypes.func,
renderSuggestionsContainer: PropTypes.func,
getSuggestionValue: PropTypes.func.isRequired,
Expand Down Expand Up @@ -138,6 +139,25 @@ export default class Autosuggest extends Component {
}
}

componentDidUpdate(prevProps, prevState) {
const { onSuggestionHighlighted } = this.props;

if (!onSuggestionHighlighted) {
return;
}

const { highlightedSectionIndex, highlightedSuggestionIndex } = this.state;

if (
highlightedSectionIndex !== prevState.highlightedSectionIndex ||
highlightedSuggestionIndex !== prevState.highlightedSuggestionIndex
) {
const suggestion = this.getHighlightedSuggestion();

onSuggestionHighlighted({ suggestion });
}
}

componentWillUnmount() {
document.removeEventListener('mousedown', this.onDocumentMouseDown);
}
Expand Down
3 changes: 3 additions & 0 deletions test/focus-first-suggestion/AutosuggestApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export const onSuggestionsClearRequested = sinon.spy(() => {

export const onSuggestionSelected = sinon.spy();

export const onSuggestionHighlighted = sinon.spy();

export default class AutosuggestApp extends Component {
constructor() {
super();
Expand All @@ -66,6 +68,7 @@ export default class AutosuggestApp extends Component {
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
onSuggestionSelected={onSuggestionSelected}
onSuggestionHighlighted={onSuggestionHighlighted}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
Expand Down
24 changes: 15 additions & 9 deletions test/focus-first-suggestion/AutosuggestApp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
} from '../helpers';
import AutosuggestApp, {
onChange,
onSuggestionSelected
onSuggestionSelected,
onSuggestionHighlighted
} from './AutosuggestApp';

describe('Autosuggest with highlightFirstSuggestion={true}', () => {
Expand Down Expand Up @@ -109,12 +110,9 @@ describe('Autosuggest with highlightFirstSuggestion={true}', () => {
});

describe('inputProps.onChange', () => {
beforeEach(() => {
it('should be called once with the right parameters when Enter is pressed after autohighlight', () => {
focusAndSetInputValue('p');
onChange.reset();
});

it('should be called once with the right parameters when Enter is pressed after autohighlight', () => {
clickEnter();
expect(onChange).to.have.been.calledOnce;
expect(onChange).to.be.calledWith(syntheticEventMatcher, {
Expand All @@ -125,12 +123,9 @@ describe('Autosuggest with highlightFirstSuggestion={true}', () => {
});

describe('onSuggestionSelected', () => {
beforeEach(() => {
it('should be called once with the right parameters when Enter is pressed after autohighlight', () => {
focusAndSetInputValue('p');
onSuggestionSelected.reset();
});

it('should be called once with the right parameters when Enter is pressed after autohighlight', () => {
clickEnter();
expect(onSuggestionSelected).to.have.been.calledOnce;
expect(
Expand All @@ -144,4 +139,15 @@ describe('Autosuggest with highlightFirstSuggestion={true}', () => {
});
});
});

describe('onSuggestionHighlighted', () => {
it('should be called once with the highlighed suggestion when the first suggestion is autohighlighted', () => {
onSuggestionHighlighted.reset();
focusAndSetInputValue('p');
expect(onSuggestionHighlighted).to.have.been.calledOnce;
expect(onSuggestionHighlighted).to.have.been.calledWithExactly({
suggestion: { name: 'Perl', year: 1987 }
});
});
});
});
3 changes: 3 additions & 0 deletions test/multi-section/AutosuggestApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export const onSuggestionsClearRequested = sinon.spy(() => {

export const onSuggestionSelected = sinon.spy();

export const onSuggestionHighlighted = sinon.spy();

export const renderSectionTitle = sinon.spy(section => {
return <strong>{section.title}</strong>;
});
Expand Down Expand Up @@ -106,6 +108,7 @@ export default class AutosuggestApp extends Component {
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
onSuggestionSelected={onSuggestionSelected}
onSuggestionHighlighted={onSuggestionHighlighted}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
Expand Down
25 changes: 25 additions & 0 deletions test/multi-section/AutosuggestApp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import {
clickEscape,
clickEnter,
clickDown,
clickUp,
setInputValue,
focusAndSetInputValue,
clickClearButton
} from '../helpers';
import AutosuggestApp, {
onSuggestionsFetchRequested,
onSuggestionSelected,
onSuggestionHighlighted,
renderSectionTitle,
getSectionSuggestions,
setHighlightFirstSuggestion
Expand Down Expand Up @@ -85,6 +87,29 @@ describe('Autosuggest with multiSection={true}', () => {
});
});

describe('onSuggestionHighlighted', () => {
it('should be called once with the suggestion that becomes highlighted', () => {
focusAndSetInputValue('c');
onSuggestionHighlighted.reset();
clickDown();
expect(onSuggestionHighlighted).to.have.been.calledOnce;
expect(onSuggestionHighlighted).to.have.been.calledWithExactly({
suggestion: { name: 'C', year: 1972 }
});
});

it('should be called once with null when there is no more highlighted suggestion', () => {
focusAndSetInputValue('c');
clickDown();
onSuggestionHighlighted.reset();
clickUp();
expect(onSuggestionHighlighted).to.have.been.calledOnce;
expect(onSuggestionHighlighted).to.have.been.calledWithExactly({
suggestion: null
});
});
});

describe('onSuggestionsFetchRequested', () => {
it('should be called once with the right parameters when input gets focus and shouldRenderSuggestions returns true', () => {
onSuggestionsFetchRequested.reset();
Expand Down
5 changes: 5 additions & 0 deletions test/plain-list/AutosuggestApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export const onSuggestionSelected = sinon.spy(() => {
addEvent('onSuggestionSelected');
});

export const onSuggestionHighlighted = sinon.spy(() => {
addEvent('onSuggestionHighlighted');
});

export default class AutosuggestApp extends Component {
constructor() {
super();
Expand Down Expand Up @@ -98,6 +102,7 @@ export default class AutosuggestApp extends Component {
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
onSuggestionSelected={onSuggestionSelected}
onSuggestionHighlighted={onSuggestionHighlighted}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
Expand Down
32 changes: 30 additions & 2 deletions test/plain-list/AutosuggestApp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ import AutosuggestApp, {
shouldRenderSuggestions,
onSuggestionsFetchRequested,
onSuggestionsClearRequested,
onSuggestionSelected
onSuggestionSelected,
onSuggestionHighlighted
} from './AutosuggestApp';

describe('Default Autosuggest', () => {
Expand Down Expand Up @@ -604,7 +605,34 @@ describe('Default Autosuggest', () => {
onChange.reset();
clearEvents();
clickSuggestion(1);
expect(getEvents()).to.deep.equal(['onChange', 'onSuggestionSelected']);
expect(
getEvents().filter(event => event === 'onChange' || event === 'onSuggestionSelected')
).to.deep.equal(['onChange', 'onSuggestionSelected']);
});
});

describe('onSuggestionHighlighted', () => {
beforeEach(() => {
focusAndSetInputValue('j');
onSuggestionHighlighted.reset();
});

it('should be called once with the highlighted suggestion when mouse enters a suggestion', () => {
mouseEnterSuggestion(0);
expect(onSuggestionHighlighted).to.have.been.calledOnce;
expect(onSuggestionHighlighted).to.have.been.calledWithExactly({
suggestion: { name: 'Java', year: 1995 }
});
});

it('should be called once with null when mouse leaves a suggestion and there is no more highlighted suggestion', () => {
mouseEnterSuggestion(0);
onSuggestionHighlighted.reset();
mouseLeaveSuggestion(0);
expect(onSuggestionHighlighted).to.have.been.calledOnce;
expect(onSuggestionHighlighted).to.have.been.calledWithExactly({
suggestion: null
});
});
});

Expand Down

0 comments on commit 3727af1

Please sign in to comment.