Skip to content

Commit

Permalink
Fix issue with suggestions not being shown in certain circumstances (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
moroshko authored Aug 24, 2016
1 parent eb8758e commit 281c772
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 62 deletions.
2 changes: 1 addition & 1 deletion demo/src/components/App/components/Header/redux.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fetch from 'isomorphic-fetch';
const UPDATE_STARGAZERS = 'UPDATE_STARGAZERS';

const initialState = {
stargazers: '1079'
stargazers: '1090'
};

export function loadStargazers() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"babel-register": "^6.7.2",
"bithound": "^1.7.0",
"chai": "^3.5.0",
"css-loader": "^0.23.1",
"css-loader": "^0.24.0",
"es6-promise": "^3.1.2",
"eslint": "^3.3.1",
"eslint-plugin-mocha": "^4.3.0",
Expand Down
37 changes: 23 additions & 14 deletions src/Autosuggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ function mapStateToProps(state) {
isCollapsed: state.isCollapsed,
focusedSectionIndex: state.focusedSectionIndex,
focusedSuggestionIndex: state.focusedSuggestionIndex,
valueBeforeUpDown: state.valueBeforeUpDown,
lastAction: state.lastAction
valueBeforeUpDown: state.valueBeforeUpDown
};
}

Expand Down Expand Up @@ -40,7 +39,6 @@ class Autosuggest extends Component {
focusedSectionIndex: PropTypes.number,
focusedSuggestionIndex: PropTypes.number,
valueBeforeUpDown: PropTypes.string,
lastAction: PropTypes.string,

inputFocused: PropTypes.func.isRequired,
inputBlurred: PropTypes.func.isRequired,
Expand All @@ -66,9 +64,9 @@ class Autosuggest extends Component {
if (this.willRenderSuggestions(nextProps)) {
this.maybeFocusFirstSuggestion();

const { isCollapsed, revealSuggestions, lastAction } = nextProps;
const { isCollapsed, revealSuggestions } = nextProps;

if (isCollapsed && lastAction !== 'click' && lastAction !== 'enter') {
if (isCollapsed && !this.justSelectedSuggestion) {
revealSuggestions();
}
}
Expand Down Expand Up @@ -204,11 +202,14 @@ class Autosuggest extends Component {
};

onSuggestionMouseDown = () => {
this.justClickedOnSuggestion = true;
this.justSelectedSuggestion = true;
};

onSuggestionClick = event => {
const { onSuggestionSelected, focusInputOnSuggestionClick, closeSuggestions } = this.props;
const {
alwaysRenderSuggestions, onSuggestionSelected,
focusInputOnSuggestionClick, closeSuggestions
} = this.props;
const { sectionIndex, suggestionIndex } =
this.getSuggestionIndices(this.findSuggestionElement(event.target));
const clickedSuggestion = this.getSuggestion(sectionIndex, suggestionIndex);
Expand All @@ -221,7 +222,10 @@ class Autosuggest extends Component {
sectionIndex,
method: 'click'
});
closeSuggestions('click');

if (!alwaysRenderSuggestions) {
closeSuggestions();
}

if (focusInputOnSuggestionClick === true) {
this.input.focus();
Expand All @@ -232,7 +236,7 @@ class Autosuggest extends Component {
this.maybeCallOnSuggestionsUpdateRequested({ value: clickedSuggestionValue, reason: 'click' });

setTimeout(() => {
this.justClickedOnSuggestion = false;
this.justSelectedSuggestion = false;
});
};

Expand Down Expand Up @@ -273,7 +277,7 @@ class Autosuggest extends Component {
const autowhateverInputProps = {
...inputProps,
onFocus: event => {
if (!this.justClickedOnSuggestion && !this.justClickedOnSuggestionsContainer) {
if (!this.justSelectedSuggestion && !this.justClickedOnSuggestionsContainer) {
inputFocused(shouldRenderSuggestions(value));
onFocus && onFocus(event);

Expand All @@ -290,7 +294,7 @@ class Autosuggest extends Component {

this.blurEvent = event;

if (!this.justClickedOnSuggestion) {
if (!this.justSelectedSuggestion) {
this.onBlur();

if (valueBeforeUpDown !== null && value !== valueBeforeUpDown) {
Expand All @@ -303,7 +307,7 @@ class Autosuggest extends Component {
const { shouldRenderSuggestions } = this.props;

this.maybeCallOnChange(event, value, 'type');
inputChanged(shouldRenderSuggestions(value), 'type');
inputChanged(shouldRenderSuggestions(value));
this.maybeCallOnSuggestionsUpdateRequested({ value, reason: 'type' });
},
onKeyDown: (event, data) => {
Expand Down Expand Up @@ -338,7 +342,7 @@ class Autosuggest extends Component {
const focusedSuggestion = this.getFocusedSuggestion();

if (isOpen && !alwaysRenderSuggestions) {
closeSuggestions('enter');
closeSuggestions();
}

if (focusedSuggestion !== null) {
Expand All @@ -353,6 +357,11 @@ class Autosuggest extends Component {

this.maybeCallOnChange(event, newValue, 'enter');
this.maybeCallOnSuggestionsUpdateRequested({ value: newValue, reason: 'enter' });
this.justSelectedSuggestion = true;

setTimeout(() => {
this.justSelectedSuggestion = false;
});
}
break;
}
Expand All @@ -376,7 +385,7 @@ class Autosuggest extends Component {
}

if (isOpen && !alwaysRenderSuggestions) {
closeSuggestions('escape');
closeSuggestions();
} else {
updateFocusedSuggestion(null, null);
}
Expand Down
3 changes: 1 addition & 2 deletions src/AutosuggestContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ export default class AutosuggestContainer extends Component {
isCollapsed: !alwaysRenderSuggestions,
focusedSectionIndex: null,
focusedSuggestionIndex: null,
valueBeforeUpDown: null,
lastAction: null
valueBeforeUpDown: null
};

this.store = createStore(reducer, initialState);
Expand Down
16 changes: 6 additions & 10 deletions src/redux.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ function inputBlurred(shouldRenderSuggestions) {
};
}

function inputChanged(shouldRenderSuggestions, lastAction) {
function inputChanged(shouldRenderSuggestions) {
return {
type: INPUT_CHANGED,
shouldRenderSuggestions,
lastAction
shouldRenderSuggestions
};
}

Expand All @@ -42,10 +41,9 @@ function revealSuggestions() {
};
}

function closeSuggestions(lastAction) {
function closeSuggestions() {
return {
type: CLOSE_SUGGESTIONS,
lastAction
type: CLOSE_SUGGESTIONS
};
}

Expand Down Expand Up @@ -83,8 +81,7 @@ export default function reducer(state, action) {
focusedSectionIndex: null,
focusedSuggestionIndex: null,
valueBeforeUpDown: null,
isCollapsed: !action.shouldRenderSuggestions,
lastAction: action.lastAction
isCollapsed: !action.shouldRenderSuggestions
};

case UPDATE_FOCUSED_SUGGESTION: {
Expand Down Expand Up @@ -117,8 +114,7 @@ export default function reducer(state, action) {
focusedSectionIndex: null,
focusedSuggestionIndex: null,
valueBeforeUpDown: null,
isCollapsed: true,
lastAction: action.lastAction
isCollapsed: true
};

default:
Expand Down
73 changes: 73 additions & 0 deletions test/do-not-focus-input-on-suggestion-click/AutosuggestApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { Component } from 'react';
import sinon from 'sinon';
import Autosuggest from '../../src/AutosuggestContainer';
import languages from '../plain-list/languages';
import { escapeRegexCharacters } from '../../demo/src/components/utils/utils.js';

function getMatchingLanguages(value) {
const escapedValue = escapeRegexCharacters(value.trim());
const regex = new RegExp('^' + escapedValue, 'i');

return languages.filter(language => regex.test(language.name));
}

let app = null;

export const getSuggestionValue = sinon.spy(suggestion => {
return suggestion.name;
});

export const renderSuggestion = sinon.spy(suggestion => {
return (
<span>{suggestion.name}</span>
);
});

export const onChange = sinon.spy((event, { newValue }) => {
app.setState({
value: newValue
});
});

export const onBlur = sinon.spy();

export const onSuggestionsUpdateRequested = sinon.spy(({ value }) => {
app.setState({
suggestions: getMatchingLanguages(value)
});
});

export const onSuggestionSelected = sinon.spy();

export default class AutosuggestApp extends Component {
constructor() {
super();

app = this;

this.state = {
value: '',
suggestions: getMatchingLanguages('')
};
}

render() {
const { value, suggestions } = this.state;
const inputProps = {
value,
onChange,
onBlur
};

return (
<Autosuggest
suggestions={suggestions}
onSuggestionsUpdateRequested={onSuggestionsUpdateRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
onSuggestionSelected={onSuggestionSelected}
focusInputOnSuggestionClick={false} />
);
}
}
45 changes: 45 additions & 0 deletions test/do-not-focus-input-on-suggestion-click/AutosuggestApp.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import { expect } from 'chai';
import {
init,
syntheticEventMatcher,
clickSuggestion,
focusAndSetInputValue,
isInputFocused
} from '../helpers';
import AutosuggestApp, {
onBlur,
onSuggestionsUpdateRequested
} from './AutosuggestApp';

describe('Autosuggest with focusInputOnSuggestionClick={false}', () => {
beforeEach(() => {
init(TestUtils.renderIntoDocument(<AutosuggestApp />));
});

describe('when suggestion is clicked', () => {
beforeEach(() => {
onBlur.reset();
focusAndSetInputValue('p');
onSuggestionsUpdateRequested.reset();
clickSuggestion(1);
});

it('should not focus on input', () => {
expect(isInputFocused()).to.equal(false);
});

it('should call onBlur once with the right parameters', () => {
expect(onBlur).to.have.been.calledOnce;
expect(onBlur).to.have.been.calledWithExactly(syntheticEventMatcher, {
focusedSuggestion: { name: 'PHP', year: 1995 }
});
});

it('should call onSuggestionsUpdateRequested once with the right parameters', () => {
expect(onSuggestionsUpdateRequested).to.have.been.calledOnce;
expect(onSuggestionsUpdateRequested).to.have.been.calledWithExactly({ value: 'PHP', reason: 'click' });
});
});
});
15 changes: 14 additions & 1 deletion test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import TestUtils, { Simulate } from 'react-addons-test-utils';

chai.use(sinonChai);

let app, container, input, suggestionsContainer;
const clock = sinon.useFakeTimers();

let app, container, input, suggestionsContainer, clearButton;
let eventsArray = [];

export const clearEvents = () => {
Expand All @@ -26,6 +28,7 @@ export const init = application => {
container = TestUtils.findRenderedDOMComponentWithClass(app, 'react-autosuggest__container');
input = TestUtils.findRenderedDOMComponentWithTag(app, 'input');
suggestionsContainer = TestUtils.findRenderedDOMComponentWithClass(app, 'react-autosuggest__suggestions-container');
clearButton = TestUtils.scryRenderedDOMComponentsWithTag(app, 'button')[0];
};

export const syntheticEventMatcher = sinon.match.instanceOf(SyntheticEvent);
Expand Down Expand Up @@ -142,6 +145,7 @@ export function clickSuggestion(suggestionIndex) {
blurInput();
focusInput();
Simulate.click(suggestion);
clock.tick(1);
}

export function clickSuggestionsContainer() {
Expand All @@ -164,6 +168,7 @@ export function clickEscape() {

export function clickEnter() {
Simulate.keyDown(input, { key: 'Enter' });
clock.tick(1);
}

export function clickDown(count = 1) {
Expand Down Expand Up @@ -191,3 +196,11 @@ export function focusAndSetInputValue(value) {
export function isInputFocused() {
return document.activeElement === input;
}

export function clickClearButton() {
if (clearButton) {
Simulate.mouseDown(clearButton);
} else {
throw new Error('Clear button doesn\'t exist');
}
}
Loading

0 comments on commit 281c772

Please sign in to comment.