diff --git a/app/javascript/shared/components/__tests__/tags.test.jsx b/app/javascript/shared/components/__tests__/tags.test.jsx
new file mode 100644
index 000000000..ed5cc530e
--- /dev/null
+++ b/app/javascript/shared/components/__tests__/tags.test.jsx
@@ -0,0 +1,52 @@
+import { h } from 'preact';
+import { deep } from 'preact-render-spy';
+import Tags from '../tags';
+
+describe('', () => {
+ beforeAll(() => {
+ const publicId = document.createElement('meta');
+ publicId.setAttribute('name', 'algolia-public-id');
+ const publicKey = document.createElement('meta');
+ publicKey.setAttribute('name', 'algolia-public-key');
+ const environment = document.createElement('meta');
+ environment.setAttribute('name', 'environment');
+ document.body.appendChild(publicId);
+ document.body.appendChild(publicKey);
+ document.body.appendChild(environment);
+ global.algoliasearch = () => ({
+ initIndex: () => 'initIndex',
+ });
+ });
+
+ let tags;
+
+ beforeEach(() => {
+ tags = deep();
+ });
+
+ describe('handleKeyDown', () => {
+ const preventDefaultMock = jest.fn();
+ const createKeyDown = key => ({
+ key,
+ preventDefault: preventDefaultMock,
+ });
+
+ beforeEach(() => {
+ preventDefaultMock.mockClear();
+ });
+
+ test('calls preventDefault on unused keyCode', () => {
+ tags.find('#tag-input').simulate('keydown', createKeyDown('ยง'));
+ tags.find('#tag-input').simulate('keydown', createKeyDown('1'));
+ tags.find('#tag-input').simulate('keydown', createKeyDown('\\'));
+ expect(preventDefaultMock).toHaveBeenCalledTimes(3);
+ });
+
+ test('does not call preventDefault on used keyCode', () => {
+ tags.find('#tag-input').simulate('keypress', createKeyDown('a'));
+ tags.find('#tag-input').simulate('keypress', createKeyDown(','));
+ tags.find('#tag-input').simulate('keypress', createKeyDown('Enter'));
+ expect(preventDefaultMock).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/app/javascript/shared/components/tags.jsx b/app/javascript/shared/components/tags.jsx
index 3f7e98c2a..42a0fa8e8 100644
--- a/app/javascript/shared/components/tags.jsx
+++ b/app/javascript/shared/components/tags.jsx
@@ -2,16 +2,26 @@ import { h, Component } from 'preact';
import PropTypes from 'prop-types';
const KEYS = {
- UP: 38,
- DOWN: 40,
- LEFT: 37,
- RIGHT: 39,
- TAB: 9,
- RETURN: 13,
- COMMA: 188,
- DELETE: 8,
+ UP: 'ArrowUp',
+ DOWN: 'ArrowDown',
+ LEFT: 'ArrowLeft',
+ RIGHT: 'ArrowRight',
+ TAB: 'Tab',
+ RETURN: 'Enter',
+ COMMA: ',',
+ DELETE: 'Backspace',
};
+const NAVIGATION_KEYS = [
+ KEYS.COMMA,
+ KEYS.DELETE,
+ KEYS.LEFT,
+ KEYS.RIGHT,
+ KEYS.TAB,
+];
+
+const LETTERS = /[a-z]/i;
+
/* TODO: Remove all instances of this.props.listing
and refactor this component to be more generic */
@@ -218,23 +228,23 @@ class Tags extends Component {
if (
component.selected.length === this.props.maxTags &&
- e.keyCode === KEYS.COMMA
+ e.key === KEYS.COMMA
) {
e.preventDefault();
return;
}
if (
- (e.keyCode === KEYS.DOWN || e.keyCode === KEYS.TAB) &&
+ (e.key === KEYS.DOWN || e.key === KEYS.TAB) &&
!this.isBottomOfSearchResults &&
component.props.defaultValue !== ''
) {
e.preventDefault();
this.moveDownInSearchResults();
- } else if (e.keyCode === KEYS.UP && !this.isTopOfSearchResults) {
+ } else if (e.key === KEYS.UP && !this.isTopOfSearchResults) {
e.preventDefault();
this.moveUpInSearchResults();
- } else if (e.keyCode === KEYS.RETURN && this.isSearchResultSelected) {
+ } else if (e.key === KEYS.RETURN && this.isSearchResultSelected) {
e.preventDefault();
this.insertTag(
component.state.searchResults[component.state.selectedIndex].name,
@@ -243,10 +253,10 @@ class Tags extends Component {
setTimeout(() => {
document.getElementById('tag-input').focus();
}, 10);
- } else if (e.keyCode === KEYS.COMMA && !this.isSearchResultSelected) {
+ } else if (e.key === KEYS.COMMA && !this.isSearchResultSelected) {
this.resetSearchResults();
this.clearSelectedSearchResult();
- } else if (e.keyCode === KEYS.DELETE) {
+ } else if (e.key === KEYS.DELETE) {
if (
component.props.defaultValue[
component.props.defaultValue.length - 1
@@ -255,13 +265,8 @@ class Tags extends Component {
this.clearSelectedSearchResult();
}
} else if (
- (e.keyCode < 65 || e.keyCode > 90) &&
- e.keyCode != KEYS.COMMA &&
- e.keyCode != KEYS.DELETE &&
- e.keyCode != KEYS.LEFT &&
- e.keyCode != KEYS.RIGHT &&
- e.keyCode != KEYS.TAB
- ) {
+ !LETTERS.test(e.key) &&
+ !NAVIGATION_KEYS.includes(e.key)) {
e.preventDefault();
}
};