Skip to content

Commit

Permalink
Merge 6006c10 into b9645f9
Browse files Browse the repository at this point in the history
  • Loading branch information
judeinno committed Apr 12, 2019
2 parents b9645f9 + 6006c10 commit 2a26b54
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 46 deletions.
1 change: 1 addition & 0 deletions src/components/dictionaryConcepts/components/AnswerRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class AnswerRow extends React.Component {
handleAsyncSelectChange={handleAsyncSelectChange}
source={source}
frontEndUniqueKey={frontEndUniqueKey}
isShown={false}
/>
)
}
Expand Down
5 changes: 3 additions & 2 deletions src/components/dictionaryConcepts/components/AnswersTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ const AnswersTable = (props) => {
</tr>
</thead>
<tbody>
{selectedAnswers.map(ans => (
{selectedAnswers.map((ans, index) => (
<AnswerRow
frontEndUniqueKey={ans.frontEndUniqueKey}
key={ans.frontEndUniqueKey}
// eslint-disable-next-line react/no-array-index-key
key={index}
toConceptName={ans.to_concept_name}
answerUrl={ans.url}
prePopulated={ans.prePopulated}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ export const INTERNET_ERROR = 'An error occurred with your internet connection,
export const CUSTOM_SOURCE = 'Custom';
export const ATTRIBUTE_NAME_SOURCE = 'source';
export const KEY_CODE_FOR_ENTER = 13;
export const KEY_CODE_FOR_ESCAPE = 27;
export const KEY_CODE_FOR_SPACE = 32;
76 changes: 58 additions & 18 deletions src/components/dictionaryConcepts/containers/SelectAnswers.jsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,73 @@
import React, { Component } from 'react';
import AsyncSelect from 'react-select/lib/Async';
import PropTypes from 'prop-types';
import { notify } from 'react-notify-toast';
import { queryAnswers } from '../../../redux/actions/concepts/dictionaryConcepts';
import {
KEY_CODE_FOR_ENTER,
KEY_CODE_FOR_ESCAPE,
} from '../components/helperFunction';
import { MIN_CHARACTERS_WARNING, MILLISECONDS_TO_SHOW_WARNING } from '../../../redux/reducers/generalSearchReducer';

class SelectAnswers extends Component {
state = {
inputValue: '',

options: [],
isVisible: false,
}

handleChange = (arr) => {
const { handleAsyncSelectChange, frontEndUniqueKey } = this.props;
if (arr !== null) {
handleAsyncSelectChange(arr, frontEndUniqueKey);
}
};

handleInputChange = (value) => {
this.setState({ inputValue: value });
}

handleKeyDown = async (event, inputValue) => {
if ((event.keyCode === KEY_CODE_FOR_ENTER) && inputValue.length >= 3) {
const { source } = this.props;
const options = await queryAnswers(source, inputValue);
this.setState({ options, isVisible: true });
} else if ((event.keyCode === KEY_CODE_FOR_ENTER) && (inputValue.length < 3)) {
notify.show(MIN_CHARACTERS_WARNING, 'error', MILLISECONDS_TO_SHOW_WARNING);
this.setState({ isVisible: false });
} else if (event.keyCode === KEY_CODE_FOR_ESCAPE) {
this.setState({ isVisible: false });
}
}

handleSelect = (res) => {
const { handleAsyncSelectChange, frontEndUniqueKey } = this.props;
this.setState({ isVisible: false, inputValue: res.label });
handleAsyncSelectChange(res, frontEndUniqueKey);
};

render() {
const { inputValue } = this.state;
const { source } = this.props;
const { index, isShown } = this.props;
return (
<AsyncSelect
isClearable
cacheOptions
loadOptions={async () => queryAnswers(source, inputValue)}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
placeholder="Search"
/>
<div className="conceptDetails">
<input
tabIndex={index}
className="form-control"
placeholder="Search"
type="text"
id="searchInputCiel"
name="to_concept_name"
value={inputValue}
onChange={e => this.handleInputChange(e.target.value)
}
onKeyDown={e => this.handleKeyDown(e, inputValue)}
/>
{(this.state.isVisible || isShown) && <ul className="cielConceptsList">
{this.state.options.map(result => <li key={result.display_name}>
<button
type="button"
id="selectConcept"
name="selectButton"
onClick={() => this.handleSelect(result)}
>
{`ID(${result.id}) - ${result.display_name}`}
</button>
</li>)}
</ul>}
</div>
);
}
}
Expand All @@ -40,10 +76,14 @@ SelectAnswers.propTypes = {
handleAsyncSelectChange: PropTypes.func.isRequired,
source: PropTypes.string,
frontEndUniqueKey: PropTypes.string.isRequired,
index: PropTypes.number,
isShown: PropTypes.bool,
};

SelectAnswers.defaultProps = {
source: '',
index: 0,
isShown: false,
};

export default SelectAnswers;
32 changes: 18 additions & 14 deletions src/redux/actions/concepts/dictionaryConcepts.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,22 +186,26 @@ export const filterByClass = (
};

export const queryAnswers = async (source, query) => {
const CONCEPT_TYPE = localStorage.getItem('type');
const USER_TYPE_NAME = localStorage.getItem('typeName');
try {
const CONCEPT_TYPE = localStorage.getItem('type');
const USER_TYPE_NAME = localStorage.getItem('typeName');

let url = `${CONCEPT_TYPE}/${USER_TYPE_NAME}/collections/${source}/concepts/?${query}*&verbose=true`;
if (source === INTERNAL_MAPPING_DEFAULT_SOURCE) {
url = `/orgs/${source}/sources/${source}/concepts/?q=${query}*&limit=0&verbose=true`;
let url = `${CONCEPT_TYPE}/${USER_TYPE_NAME}/collections/${source}/concepts/?${query}*&verbose=true`;
if (source === INTERNAL_MAPPING_DEFAULT_SOURCE) {
url = `/orgs/${source}/sources/${source}/concepts/?q=${query}*&limit=0&verbose=true`;
}
const response = await instance.get(url);
const defaults = { map_type: MAP_TYPE.questionAndAnswer };
const options = response.data.map(concept => ({
...concept,
...defaults,
value: `${concept.url}`,
label: `${concept.source}: ${concept.display_name}`,
}));
return options;
} catch (error) {
return [];
}
const response = await instance.get(url);
const defaults = { map_type: MAP_TYPE.questionAndAnswer };
const options = response.data.map(concept => ({
...concept,
...defaults,
value: `${concept.url}`,
label: `${concept.source}: ${concept.display_name}`,
}));
return options;
};

export const addNewAnswerRow = answer => (dispatch) => {
Expand Down
1 change: 1 addition & 0 deletions src/styles/dictionary_concepts.scss
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ input[type=text] {
padding: 5px;
background: #F4f4f4;
z-index: 10;
position: relative;

li {
display: block;
Expand Down
65 changes: 53 additions & 12 deletions src/tests/dictionaryConcepts/container/SelectAnswers.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
import { mount } from 'enzyme';
import SelectAnswers from '../../../components/dictionaryConcepts/containers/SelectAnswers';
import { KEY_CODE_FOR_ESCAPE, KEY_CODE_FOR_ENTER, KEY_CODE_FOR_SPACE } from '../../../components/dictionaryConcepts/components/helperFunction';


jest.mock('react-notify-toast');

describe('<SelectAnswers />', () => {
let wrapper;
Expand All @@ -13,22 +17,59 @@ describe('<SelectAnswers />', () => {
source: 'test source',
frontEndUniqueKey: 'unique',
};
wrapper = shallow(<SelectAnswers {...props} />);
wrapper = mount(<SelectAnswers {...props} />);
});

it('should handle change in AsncSelect', () => {
const instance = wrapper.instance();
const spy = jest.spyOn(instance, 'handleChange');
instance.handleChange(['value']);
it('should handle key down event when a user presses enter button to search for concept', async () => {
const spy = jest.spyOn(wrapper.instance(), 'handleKeyDown');
const event = { keyCode: KEY_CODE_FOR_ENTER };
await wrapper.instance().handleKeyDown(event, 'malaria');
expect(spy).toHaveBeenCalled();
instance.handleChange(null);
expect(wrapper.state().isVisible).toBeTruthy();
});

it('should handle key down event when a user presses enter button to search for concept with input less that 3', async () => {
const event = { keyCode: KEY_CODE_FOR_ENTER };
await wrapper.instance().handleKeyDown(event, 'ma');
expect(wrapper.state().isVisible).toBeFalsy();
});

it('should handle key down event when a user presses button thats not enter to search for concept', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleKeyDown');
const event = { keyCode: KEY_CODE_FOR_SPACE };
wrapper.find('#searchInputCiel').simulate('keyDown', event);
expect(spy).toHaveBeenCalled();
});

it('should update the state with current input value', () => {
wrapper = mount(<SelectAnswers {...props} />);
const instance = wrapper.instance();
instance.handleInputChange('searchTerm');
expect(wrapper.state().inputValue).toEqual('searchTerm');
it('should handle key down event when a user presses escape button to remove the dropdown', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleKeyDown');
const event = { keyCode: KEY_CODE_FOR_ESCAPE };
wrapper.find('#searchInputCiel').simulate('keyDown', event);
expect(spy).toHaveBeenCalled();
});

it('should handle select when a user clicks to select a prefered concept', () => {
const newProps = {
...props,
isShown: true,
};
wrapper = mount(<SelectAnswers {...newProps} />);
wrapper.setState({
options: [{
display_name: 'MALARIAL SMEAR',
label: 'CIEL: MALARIAL SMEAR',
value: '/orgs/CIEL/sources/CIEL/concepts/32/',
}],
}, () => {
const spy = jest.spyOn(wrapper.instance(), 'handleSelect');
wrapper.find('#selectConcept').simulate('click');
expect(spy).toHaveBeenCalled();
});
});

it('should handle change when a user inputs concept name data', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleInputChange');
wrapper.find('#searchInputCiel').simulate('change');
expect(spy).toHaveBeenCalled();
});
});

0 comments on commit 2a26b54

Please sign in to comment.