Unverified Commit 2b67c4c4 authored by Tamika Tannis's avatar Tamika Tannis Committed by GitHub

Prevent entering of ':' into search term (#414)

* Do not allow input of ':'

* Hook up potential error message

* Update error text and test
parent ed4e16ee
...@@ -4,3 +4,5 @@ export const PLACEHOLDER_DEFAULT = 'search for data resources...'; ...@@ -4,3 +4,5 @@ export const PLACEHOLDER_DEFAULT = 'search for data resources...';
export const BUTTON_CLOSE_TEXT = 'Close'; export const BUTTON_CLOSE_TEXT = 'Close';
export const SIZE_SMALL = 'small'; export const SIZE_SMALL = 'small';
export const INVALID_SYNTAX_MESSAGE = "Your search term contains invalid syntax ':'.";
...@@ -16,6 +16,7 @@ import './styles.scss'; ...@@ -16,6 +16,7 @@ import './styles.scss';
import { import {
BUTTON_CLOSE_TEXT, BUTTON_CLOSE_TEXT,
INVALID_SYNTAX_MESSAGE,
PLACEHOLDER_DEFAULT, PLACEHOLDER_DEFAULT,
SIZE_SMALL SIZE_SMALL
} from './constants'; } from './constants';
...@@ -92,19 +93,23 @@ export class SearchBar extends React.Component<SearchBarProps, SearchBarState> { ...@@ -92,19 +93,23 @@ export class SearchBar extends React.Component<SearchBarProps, SearchBarState> {
handleValueChange = (event: React.SyntheticEvent<HTMLInputElement>) : void => { handleValueChange = (event: React.SyntheticEvent<HTMLInputElement>) : void => {
const searchTerm = (event.target as HTMLInputElement).value.toLowerCase(); const searchTerm = (event.target as HTMLInputElement).value.toLowerCase();
if (searchTerm.length > 0) { if (this.isFormValid(searchTerm)) {
this.props.onInputChange(searchTerm); if (searchTerm.length > 0) {
this.setState({ searchTerm, showTypeAhead: true }); this.props.onInputChange(searchTerm);
} this.setState({ searchTerm, showTypeAhead: true });
else { }
this.clearSearchTerm(); else {
this.clearSearchTerm();
}
} else {
this.setState({ searchTerm, showTypeAhead: false });
} }
}; };
handleValueSubmit = (event: React.FormEvent<HTMLFormElement>) : void => { handleValueSubmit = (event: React.FormEvent<HTMLFormElement>) : void => {
const searchTerm = this.state.searchTerm.trim(); const searchTerm = this.state.searchTerm.trim();
event.preventDefault(); event.preventDefault();
if (this.isFormValid()) { if (this.isFormValid(searchTerm)) {
this.props.submitSearch(searchTerm); this.props.submitSearch(searchTerm);
this.hideTypeAhead(); this.hideTypeAhead();
} }
...@@ -114,9 +119,20 @@ export class SearchBar extends React.Component<SearchBarProps, SearchBarState> { ...@@ -114,9 +119,20 @@ export class SearchBar extends React.Component<SearchBarProps, SearchBarState> {
this.setState({ showTypeAhead: false }); this.setState({ showTypeAhead: false });
}; };
isFormValid = () : boolean => { isFormValid = (searchTerm: string) : boolean => {
const form = document.getElementById("search-bar-form") as HTMLFormElement; const form = document.getElementById("search-bar-form") as HTMLFormElement;
return form.checkValidity(); const input = document.getElementById("search-input") as HTMLInputElement;
const isValid = searchTerm.indexOf(':') < 0;
/* This will set the error message, it must be explicitly set or cleared each time */
input.setCustomValidity(isValid ? "": INVALID_SYNTAX_MESSAGE)
if (searchTerm.length > 0) {
/* This will show the error message */
form.reportValidity();
}
return isValid;
}; };
onSelectInlineResult = (resourceType: ResourceType, updateUrl: boolean = false) : void => { onSelectInlineResult = (resourceType: ResourceType, updateUrl: boolean = false) : void => {
......
...@@ -90,39 +90,61 @@ describe('SearchBar', () => { ...@@ -90,39 +90,61 @@ describe('SearchBar', () => {
describe('handleValueChange', () => { describe('handleValueChange', () => {
let props; let props;
let wrapper; let wrapper;
let isFormValidSpy;
beforeAll(() => { beforeAll(() => {
const setupResult = setup(); const setupResult = setup();
props = setupResult.props; props = setupResult.props;
wrapper = setupResult.wrapper; wrapper = setupResult.wrapper;
isFormValidSpy = jest.spyOn(wrapper.instance(), 'isFormValid');
}); });
describe('if searchTerm has length', () => { describe('when form is not valid', () => {
const mockSearchTerm = 'I have Length'; beforeAll(() => {
const expectedSearchTerm = 'i have length'; isFormValidSpy.mockImplementation(() => false)
it('calls setState with searchTerm as lowercase target value & showTypeAhead as true ', () => { })
setStateSpy.mockClear();
// @ts-ignore: mocked events throw type errors
wrapper.instance().handleValueChange({ target: { value: mockSearchTerm } });
expect(setStateSpy).toHaveBeenCalledWith({ searchTerm: expectedSearchTerm, showTypeAhead: true });
});
it('calls onInputChange with searchTerm as lowercase target value', () => { it('updates the searchTerm (for visual feedback) and hides type ahead', () => {
setStateSpy.mockClear();
const testTerm = 'hello';
// @ts-ignore: mocked events throw type errors // @ts-ignore: mocked events throw type errors
props.onInputChange.mockClear(); wrapper.instance().handleValueChange({ target: { value: testTerm } });
wrapper.instance().handleValueChange({ target: { value: mockSearchTerm } }); expect(setStateSpy).toHaveBeenCalledWith({ searchTerm: testTerm, showTypeAhead: false });
expect(props.onInputChange).toHaveBeenCalledWith(expectedSearchTerm); })
});
}) })
describe('if searchTerm has zero length', () => { describe('when form is valid', () => {
const mockSearchTerm = ''; beforeAll(() => {
it('calls clearSearchTerm', () => { isFormValidSpy.mockImplementation(() => true)
const clearSearchTermSpy = jest.spyOn(wrapper.instance(), 'clearSearchTerm'); })
// @ts-ignore: mocked events throw type errors
wrapper.instance().handleValueChange({ target: { value: mockSearchTerm } }); describe('if searchTerm has length', () => {
expect(clearSearchTermSpy).toHaveBeenCalled(); const mockSearchTerm = 'I have Length';
const expectedSearchTerm = 'i have length';
it('calls setState with searchTerm as lowercase target value & showTypeAhead as true ', () => {
setStateSpy.mockClear();
// @ts-ignore: mocked events throw type errors
wrapper.instance().handleValueChange({ target: { value: mockSearchTerm } });
expect(setStateSpy).toHaveBeenCalledWith({ searchTerm: expectedSearchTerm, showTypeAhead: true });
});
it('calls onInputChange with searchTerm as lowercase target value', () => {
// @ts-ignore: mocked events throw type errors
props.onInputChange.mockClear();
wrapper.instance().handleValueChange({ target: { value: mockSearchTerm } });
expect(props.onInputChange).toHaveBeenCalledWith(expectedSearchTerm);
});
})
describe('if searchTerm has zero length', () => {
const mockSearchTerm = '';
it('calls clearSearchTerm', () => {
const clearSearchTermSpy = jest.spyOn(wrapper.instance(), 'clearSearchTerm');
// @ts-ignore: mocked events throw type errors
wrapper.instance().handleValueChange({ target: { value: mockSearchTerm } });
expect(clearSearchTermSpy).toHaveBeenCalled();
});
}); });
}); })
}); });
describe('handleValueSubmit', () => { describe('handleValueSubmit', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment