]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15942 Remove deprecated keycode
authorJeremy Davis <jeremy.davis@sonarsource.com>
Thu, 20 Jan 2022 17:20:23 +0000 (18:20 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 24 Jan 2022 20:02:53 +0000 (20:02 +0000)
32 files changed:
server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx
server/sonar-web/src/main/js/app/components/search/Search.tsx
server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx
server/sonar-web/src/main/js/apps/account/notifications/ProjectModal.tsx
server/sonar-web/src/main/js/apps/account/notifications/__tests__/ProjectModal-test.tsx
server/sonar-web/src/main/js/apps/code/components/Search.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/AssigneeSelection.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/AssigneeSelection-test.tsx
server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx
server/sonar-web/src/main/js/components/common/MultiSelect.tsx
server/sonar-web/src/main/js/components/common/SelectList.tsx
server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.tsx
server/sonar-web/src/main/js/components/common/__tests__/SelectList-test.tsx
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelect-test.tsx.snap
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/SelectList-test.tsx.snap
server/sonar-web/src/main/js/components/controls/EscKeydownHandler.tsx
server/sonar-web/src/main/js/components/controls/SearchBox.tsx
server/sonar-web/src/main/js/components/controls/__tests__/EscKeydownHandler-test.tsx
server/sonar-web/src/main/js/components/hoc/__tests__/withKeyboardNavigation-test.tsx
server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx
server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentPopup-test.tsx.snap
server/sonar-web/src/main/js/helpers/keycodes.ts
server/sonar-web/src/main/js/helpers/testUtils.ts

index eb2e08e8395fa45d2cd4f8df05b375da046278ac..e9d079c80c65888e1de6321175fe517d84f4f21a 100644 (file)
@@ -28,7 +28,7 @@ import {
   isPullRequest,
   isSameBranchLike
 } from '../../../../../helpers/branch-like';
-import { KeyCodes } from '../../../../../helpers/keycodes';
+import { KeyboardCodes } from '../../../../../helpers/keycodes';
 import { translate } from '../../../../../helpers/l10n';
 import { getBranchLikeUrl } from '../../../../../helpers/urls';
 import { BranchLike, BranchLikeTree } from '../../../../../types/branch-like';
@@ -109,16 +109,16 @@ export class Menu extends React.PureComponent<Props, State> {
   };
 
   handleKeyDown = (event: React.KeyboardEvent) => {
-    switch (event.keyCode) {
-      case KeyCodes.Enter:
+    switch (event.nativeEvent.code) {
+      case KeyboardCodes.Enter:
         event.preventDefault();
         this.openHighlightedBranchLike();
         break;
-      case KeyCodes.UpArrow:
+      case KeyboardCodes.UpArrow:
         event.preventDefault();
         this.highlightSiblingBranchlike(-1);
         break;
-      case KeyCodes.DownArrow:
+      case KeyboardCodes.DownArrow:
         event.preventDefault();
         this.highlightSiblingBranchlike(+1);
         break;
index f0ca9926d3cfdcfc63d4f959720d9b4ca6124a63..788febe0ecbcd32d72a161ba73e0581eb2e0a322 100644 (file)
@@ -21,7 +21,7 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { Link } from 'react-router';
 import SearchBox from '../../../../../../components/controls/SearchBox';
-import { KeyCodes } from '../../../../../../helpers/keycodes';
+import { KeyboardCodes } from '../../../../../../helpers/keycodes';
 import {
   mockPullRequest,
   mockSetOfBranchAndPullRequest
@@ -93,14 +93,14 @@ it('should handle keyboard shortcut correctly', () => {
 
   const { onKeyDown } = wrapper.find(SearchBox).props();
 
-  onKeyDown!(mockEvent({ keyCode: KeyCodes.UpArrow }));
+  onKeyDown!(mockEvent({ nativeEvent: { code: KeyboardCodes.UpArrow } }));
   expect(wrapper.state().selectedBranchLike).toBe(branchLikes[5]);
 
-  onKeyDown!(mockEvent({ keyCode: KeyCodes.DownArrow }));
-  onKeyDown!(mockEvent({ keyCode: KeyCodes.DownArrow }));
+  onKeyDown!(mockEvent({ nativeEvent: { code: KeyboardCodes.DownArrow } }));
+  onKeyDown!(mockEvent({ nativeEvent: { code: KeyboardCodes.DownArrow } }));
   expect(wrapper.state().selectedBranchLike).toBe(branchLikes[0]);
 
-  onKeyDown!(mockEvent({ keyCode: KeyCodes.Enter }));
+  onKeyDown!(mockEvent({ nativeEvent: { code: KeyboardCodes.Enter } }));
   expect(push).toHaveBeenCalled();
 });
 
index 6818bdabe43800c114e9676e266b23153f92b280..5bcfd07fec5f3a495f3c502b04475933c160a07a 100644 (file)
@@ -29,6 +29,7 @@ import SearchBox from '../../../components/controls/SearchBox';
 import ClockIcon from '../../../components/icons/ClockIcon';
 import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { scrollToElement } from '../../../helpers/scrolling';
 import { getComponentOverviewUrl } from '../../../helpers/urls';
@@ -286,16 +287,16 @@ export class Search extends React.PureComponent<Props, State> {
   };
 
   handleKeyDown = (event: React.KeyboardEvent) => {
-    switch (event.keyCode) {
-      case 13:
+    switch (event.nativeEvent.code) {
+      case KeyboardCodes.Enter:
         event.preventDefault();
         this.openSelected();
         return;
-      case 38:
+      case KeyboardCodes.UpArrow:
         event.preventDefault();
         this.selectPrevious();
         return;
-      case 40:
+      case KeyboardCodes.DownArrow:
         event.preventDefault();
         this.selectNext();
         // keep this return to prevent fall-through in case more cases will be adder later
index d7ab82e39d1954fae1903f4a6cf0a53b1113bcb0..67d98023a44c9233f160e5d31f5a97343ee37adb 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow, ShallowWrapper } from 'enzyme';
 import * as React from 'react';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
 import { mockRouter } from '../../../../helpers/testMocks';
 import { elementKeydown } from '../../../../helpers/testUtils';
 import { ComponentQualifier } from '../../../../types/component';
@@ -56,7 +57,7 @@ it('opens selected project on enter', () => {
     selected: selectedKey
   });
 
-  elementKeydown(form.find('SearchBox'), 13);
+  elementKeydown(form.find('SearchBox'), KeyboardCodes.Enter);
   expect(router.push).toBeCalledWith({ pathname: '/dashboard', query: { id: selectedKey } });
 });
 
@@ -72,7 +73,7 @@ it('opens selected portfolio on enter', () => {
     selected: selectedKey
   });
 
-  elementKeydown(form.find('SearchBox'), 13);
+  elementKeydown(form.find('SearchBox'), KeyboardCodes.Enter);
   expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
 });
 
@@ -88,7 +89,7 @@ it('opens selected subportfolio on enter', () => {
     selected: selectedKey
   });
 
-  elementKeydown(form.find('SearchBox'), 13);
+  elementKeydown(form.find('SearchBox'), KeyboardCodes.Enter);
   expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
 });
 
@@ -112,12 +113,12 @@ function component(key: string, qualifier = ComponentQualifier.Project) {
 }
 
 function next(form: ShallowWrapper<Search['props'], Search['state']>, expected: string) {
-  elementKeydown(form.find('SearchBox'), 40);
+  elementKeydown(form.find('SearchBox'), KeyboardCodes.DownArrow);
   expect(form.state().selected).toBe(expected);
 }
 
 function prev(form: ShallowWrapper<Search['props'], Search['state']>, expected: string) {
-  elementKeydown(form.find('SearchBox'), 38);
+  elementKeydown(form.find('SearchBox'), KeyboardCodes.UpArrow);
   expect(form.state().selected).toBe(expected);
 }
 
index 7a0060f4569fddc1ae46ed20ce9e042651eb8450..b83d7555ff6877c2286dd94be9bab23b40f9e5d5 100644 (file)
@@ -25,6 +25,7 @@ import { ResetButtonLink, SubmitButton } from '../../../components/controls/butt
 import { DropdownOverlay } from '../../../components/controls/Dropdown';
 import SearchBox from '../../../components/controls/SearchBox';
 import SimpleModal from '../../../components/controls/SimpleModal';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
@@ -61,16 +62,16 @@ export default class ProjectModal extends React.PureComponent<Props, State> {
   }
 
   handleKeyDown = (event: React.KeyboardEvent) => {
-    switch (event.keyCode) {
-      case 13:
+    switch (event.nativeEvent.code) {
+      case KeyboardCodes.Enter:
         event.preventDefault();
         this.handleSelectHighlighted();
         break;
-      case 38:
+      case KeyboardCodes.UpArrow:
         event.preventDefault();
         this.handleHighlightPrevious();
         break;
-      case 40:
+      case KeyboardCodes.DownArrow:
         event.preventDefault();
         this.handleHighlightNext();
         break;
index e8707186222b9748f5545f3e57222951a6cfb5f4..9d47ebc3a1dab61a28b58cbce78660056a025c0c 100644 (file)
@@ -20,6 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { getSuggestions } from '../../../../api/components';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
 import { change, elementKeydown, submit, waitAndUpdate } from '../../../../helpers/testUtils';
 import ProjectModal from '../ProjectModal';
 
@@ -102,27 +103,24 @@ it('should handle up and down keys', async () => {
   });
   await waitAndUpdate(wrapper);
 
-  // Down.
-  elementKeydown(wrapper.dive().find('SearchBox'), 40);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.DownArrow);
   expect(wrapper.state('highlighted')).toEqual(foo);
-  elementKeydown(wrapper.dive().find('SearchBox'), 40);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.DownArrow);
   expect(wrapper.state('highlighted')).toEqual(bar);
-  elementKeydown(wrapper.dive().find('SearchBox'), 40);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.DownArrow);
   expect(wrapper.state('highlighted')).toEqual(foo);
 
-  // Up.
-  elementKeydown(wrapper.dive().find('SearchBox'), 38);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.UpArrow);
   expect(wrapper.state('highlighted')).toEqual(bar);
-  elementKeydown(wrapper.dive().find('SearchBox'), 38);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.UpArrow);
   expect(wrapper.state('highlighted')).toEqual(foo);
-  elementKeydown(wrapper.dive().find('SearchBox'), 38);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.UpArrow);
   expect(wrapper.state('highlighted')).toEqual(bar);
 
-  // Enter.
-  elementKeydown(wrapper.dive().find('SearchBox'), 13);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.Enter);
   expect(wrapper.state('selectedProject')).toEqual(bar);
   expect(onSubmit).not.toHaveBeenCalled();
-  elementKeydown(wrapper.dive().find('SearchBox'), 13);
+  elementKeydown(wrapper.dive().find('SearchBox'), KeyboardCodes.Enter);
   expect(onSubmit).toHaveBeenCalledWith(bar);
 });
 
index 90d9fbb8c83781256ec3ccd2e9077116d832964b..188c4dff9c1f430403aeb427aaa6e7c0f61671f6 100644 (file)
@@ -24,6 +24,7 @@ import SearchBox from '../../../components/controls/SearchBox';
 import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
 import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { translate } from '../../../helpers/l10n';
 import { BranchLike } from '../../../types/branch-like';
 import PortfolioNewCodeToggle from './PortfolioNewCodeToggle';
@@ -74,10 +75,10 @@ export class Search extends React.PureComponent<Props, State> {
   }
 
   handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
-    switch (event.keyCode) {
-      case 13:
-      case 38:
-      case 40:
+    switch (event.nativeEvent.code) {
+      case KeyboardCodes.Enter:
+      case KeyboardCodes.UpArrow:
+      case KeyboardCodes.DownArrow:
         event.preventDefault();
         event.currentTarget.blur();
         break;
index c64244bb8f68c8bc9adde36a5c680b96c4b737e6..474aacb0a11fc16b6c1854301d99ab1b0fb9ef0e 100644 (file)
@@ -43,7 +43,7 @@ import {
   isSameBranchLike
 } from '../../../helpers/branch-like';
 import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication';
-import { KeyCodes } from '../../../helpers/keycodes';
+import { KeyboardCodes, KeyboardKeys } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import {
   addSideBarClass,
@@ -268,19 +268,19 @@ export default class App extends React.PureComponent<Props, State> {
     if (key.getScope() !== 'issues') {
       return;
     }
-    if (event.keyCode === KeyCodes.Alt) {
+    if (event.key === KeyboardKeys.Alt) {
       event.preventDefault();
       this.setState(actions.enableLocationsNavigator);
-    } else if (event.keyCode === KeyCodes.DownArrow && event.altKey) {
+    } else if (event.code === KeyboardCodes.DownArrow && event.altKey) {
       event.preventDefault();
       this.selectNextLocation();
-    } else if (event.keyCode === KeyCodes.UpArrow && event.altKey) {
+    } else if (event.code === KeyboardCodes.UpArrow && event.altKey) {
       event.preventDefault();
       this.selectPreviousLocation();
-    } else if (event.keyCode === KeyCodes.LeftArrow && event.altKey) {
+    } else if (event.code === KeyboardCodes.LeftArrow && event.altKey) {
       event.preventDefault();
       this.selectPreviousFlow();
-    } else if (event.keyCode === KeyCodes.RightArrow && event.altKey) {
+    } else if (event.code === KeyboardCodes.RightArrow && event.altKey) {
       event.preventDefault();
       this.selectNextFlow();
     }
@@ -290,8 +290,7 @@ export default class App extends React.PureComponent<Props, State> {
     if (key.getScope() !== 'issues') {
       return;
     }
-    if (event.keyCode === KeyCodes.Alt) {
-      // alt
+    if (event.key === KeyboardKeys.Alt) {
       this.setState(actions.disableLocationsNavigator);
     }
   };
index ec5f4e5e767d3ac1292b09e6e256efc476faafc4..404e5859b828451f73adb7f399721114d9f41040 100644 (file)
@@ -21,7 +21,7 @@ import { shallow } from 'enzyme';
 import key from 'keymaster';
 import * as React from 'react';
 import handleRequiredAuthentication from '../../../../helpers/handleRequiredAuthentication';
-import { KeyCodes } from '../../../../helpers/keycodes';
+import { KeyboardCodes, KeyboardKeys } from '../../../../helpers/keycodes';
 import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import {
@@ -63,7 +63,8 @@ jest.mock('../../../../helpers/handleRequiredAuthentication', () => jest.fn());
 jest.mock('keymaster', () => {
   const key: any = (bindKey: string, _: string, callback: Function) => {
     document.addEventListener('keydown', (event: KeyboardEvent) => {
-      if (bindKey.split(',').includes(KEYCODE_MAP[event.keyCode])) {
+      const keymasterCode = event.code && KEYCODE_MAP[event.code as KeyboardCodes];
+      if (keymasterCode && bindKey.split(',').includes(keymasterCode)) {
         return callback();
       }
       return true;
@@ -217,25 +218,25 @@ it('should correctly bind key events for issue navigation', async () => {
 
   expect(wrapper.state('selected')).toBe(ISSUES[0].key);
 
-  keydown(KeyCodes.DownArrow);
+  keydown({ code: KeyboardCodes.DownArrow });
   expect(wrapper.state('selected')).toBe(ISSUES[1].key);
 
-  keydown(KeyCodes.UpArrow);
-  keydown(KeyCodes.UpArrow);
+  keydown({ code: KeyboardCodes.UpArrow });
+  keydown({ code: KeyboardCodes.UpArrow });
   expect(wrapper.state('selected')).toBe(ISSUES[0].key);
 
-  keydown(KeyCodes.DownArrow);
-  keydown(KeyCodes.DownArrow);
-  keydown(KeyCodes.DownArrow);
-  keydown(KeyCodes.DownArrow);
-  keydown(KeyCodes.DownArrow);
-  keydown(KeyCodes.DownArrow);
+  keydown({ code: KeyboardCodes.DownArrow });
+  keydown({ code: KeyboardCodes.DownArrow });
+  keydown({ code: KeyboardCodes.DownArrow });
+  keydown({ code: KeyboardCodes.DownArrow });
+  keydown({ code: KeyboardCodes.DownArrow });
+  keydown({ code: KeyboardCodes.DownArrow });
   expect(wrapper.state('selected')).toBe(ISSUES[3].key);
 
-  keydown(KeyCodes.RightArrow);
+  keydown({ code: KeyboardCodes.RightArrow });
   expect(push).toBeCalledTimes(1);
 
-  keydown(KeyCodes.LeftArrow);
+  keydown({ code: KeyboardCodes.LeftArrow });
   expect(push).toBeCalledTimes(2);
   expect(window.addEventListener).toBeCalledTimes(2);
 });
@@ -432,28 +433,28 @@ describe('keydown event handler', () => {
   });
 
   it('should handle alt', () => {
-    instance.handleKeyDown(mockEvent({ keyCode: 18 }));
+    instance.handleKeyDown(mockEvent({ key: KeyboardKeys.Alt }));
     expect(instance.setState).toHaveBeenCalledWith(enableLocationsNavigator);
   });
   it('should handle alt+↓', () => {
-    instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 40 }));
+    instance.handleKeyDown(mockEvent({ altKey: true, code: KeyboardCodes.DownArrow }));
     expect(instance.setState).toHaveBeenCalledWith(selectNextLocation);
   });
   it('should handle alt+↑', () => {
-    instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 38 }));
+    instance.handleKeyDown(mockEvent({ altKey: true, code: KeyboardCodes.UpArrow }));
     expect(instance.setState).toHaveBeenCalledWith(selectPreviousLocation);
   });
   it('should handle alt+←', () => {
-    instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 37 }));
+    instance.handleKeyDown(mockEvent({ altKey: true, code: KeyboardCodes.LeftArrow }));
     expect(instance.setState).toHaveBeenCalledWith(selectPreviousFlow);
   });
   it('should handle alt+→', () => {
-    instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 39 }));
+    instance.handleKeyDown(mockEvent({ altKey: true, code: KeyboardCodes.RightArrow }));
     expect(instance.setState).toHaveBeenCalledWith(selectNextFlow);
   });
   it('should ignore different scopes', () => {
     key.setScope('notissues');
-    instance.handleKeyDown(mockEvent({ keyCode: 18 }));
+    instance.handleKeyDown(mockEvent({ key: KeyboardKeys.Alt }));
     expect(instance.setState).not.toHaveBeenCalled();
   });
 });
@@ -472,13 +473,13 @@ describe('keyup event handler', () => {
   });
 
   it('should handle alt', () => {
-    instance.handleKeyUp(mockEvent({ keyCode: 18 }));
+    instance.handleKeyUp(mockEvent({ key: KeyboardKeys.Alt }));
     expect(instance.setState).toHaveBeenCalledWith(disableLocationsNavigator);
   });
 
   it('should ignore different scopes', () => {
     key.setScope('notissues');
-    instance.handleKeyUp(mockEvent({ keyCode: 18 }));
+    instance.handleKeyUp(mockEvent({ key: KeyboardKeys.Alt }));
     expect(instance.setState).not.toHaveBeenCalled();
   });
 });
index 5a2d364fcb6207a98e4480eb8f798b5fb9146254..4469a4d3d6b865b3996dd52b6563bb1b18ca288d 100644 (file)
@@ -20,7 +20,7 @@
 import { debounce } from 'lodash';
 import * as React from 'react';
 import { searchUsers } from '../../../../api/users';
-import { KeyCodes } from '../../../../helpers/keycodes';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
 import { translate } from '../../../../helpers/l10n';
 import { isUserActive } from '../../../../helpers/users';
 import AssigneeSelectionRenderer from './AssigneeSelectionRenderer';
@@ -104,16 +104,16 @@ export default class AssigneeSelection extends React.PureComponent<Props, State>
   };
 
   handleKeyDown = (event: React.KeyboardEvent) => {
-    switch (event.keyCode) {
-      case KeyCodes.Enter:
+    switch (event.nativeEvent.code) {
+      case KeyboardCodes.Enter:
         event.preventDefault();
         this.selectHighlighted();
         break;
-      case KeyCodes.UpArrow:
+      case KeyboardCodes.UpArrow:
         event.preventDefault();
         this.highlightPrevious();
         break;
-      case KeyCodes.DownArrow:
+      case KeyboardCodes.DownArrow:
         event.preventDefault();
         this.highlightNext();
         break;
index 7820149048ac15c4d76f3fac4b75559950c7b4ce..73fa33023505608fe9545a16b88d3020eabb9fb6 100644 (file)
@@ -20,7 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { searchUsers } from '../../../../../api/users';
-import { KeyCodes } from '../../../../../helpers/keycodes';
+import { KeyboardCodes } from '../../../../../helpers/keycodes';
 import { mockLoggedInUser, mockUser } from '../../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../../helpers/testUtils';
 import AssigneeSelection from '../AssigneeSelection';
@@ -34,7 +34,7 @@ it('should render correctly', () => {
 });
 
 it('should handle keydown', () => {
-  const mockEvent = (keyCode: number) => ({ preventDefault: jest.fn(), keyCode });
+  const mockEvent = (code: KeyboardCodes) => ({ preventDefault: jest.fn(), nativeEvent: { code } });
   const suggestedUsers = [
     mockUser({ login: '1' }) as T.UserActive,
     mockUser({ login: '2' }) as T.UserActive,
@@ -44,29 +44,29 @@ it('should handle keydown', () => {
   const onSelect = jest.fn();
   const wrapper = shallowRender({ onSelect });
 
-  wrapper.instance().handleKeyDown(mockEvent(KeyCodes.UpArrow) as any);
+  wrapper.instance().handleKeyDown(mockEvent(KeyboardCodes.UpArrow) as any);
   expect(wrapper.state().highlighted).toEqual({ login: '', name: 'unassigned' });
 
   wrapper.setState({ suggestedUsers });
 
   // press down to highlight the first
-  wrapper.instance().handleKeyDown(mockEvent(KeyCodes.DownArrow) as any);
+  wrapper.instance().handleKeyDown(mockEvent(KeyboardCodes.DownArrow) as any);
   expect(wrapper.state().highlighted).toBe(suggestedUsers[0]);
 
   // press up to loop around to last
-  wrapper.instance().handleKeyDown(mockEvent(KeyCodes.UpArrow) as any);
+  wrapper.instance().handleKeyDown(mockEvent(KeyboardCodes.UpArrow) as any);
   expect(wrapper.state().highlighted).toBe(suggestedUsers[2]);
 
   // press down to loop around to first
-  wrapper.instance().handleKeyDown(mockEvent(KeyCodes.DownArrow) as any);
+  wrapper.instance().handleKeyDown(mockEvent(KeyboardCodes.DownArrow) as any);
   expect(wrapper.state().highlighted).toBe(suggestedUsers[0]);
 
   // press down highlight the next
-  wrapper.instance().handleKeyDown(mockEvent(KeyCodes.DownArrow) as any);
+  wrapper.instance().handleKeyDown(mockEvent(KeyboardCodes.DownArrow) as any);
   expect(wrapper.state().highlighted).toBe(suggestedUsers[1]);
 
   // press enter to select the highlighted user
-  wrapper.instance().handleKeyDown(mockEvent(KeyCodes.Enter) as any);
+  wrapper.instance().handleKeyDown(mockEvent(KeyboardCodes.Enter) as any);
   expect(onSelect).toBeCalledWith(suggestedUsers[1]);
 });
 
index f237d48a30436bd6b0e54e01dcc889a6ba7b1fe9..0bc426876f82d3e8f23141bcbe11b13c57523286 100644 (file)
@@ -62,6 +62,8 @@ interface State {
 
 const SAFE_SET_STATE_DELAY = 3000;
 
+const formNoop = (e: React.FormEvent<HTMLFormElement>) => e.preventDefault();
+
 export class Definition extends React.PureComponent<Props, State> {
   timeout?: number;
   mounted = false;
@@ -189,7 +191,7 @@ export class Definition extends React.PureComponent<Props, State> {
               </span>
             )}
           </div>
-          <form>
+          <form onSubmit={formNoop}>
             <Input
               hasValueChanged={hasValueChanged}
               onCancel={this.handleCancel}
index 40609c6b17a17fde8274d5b8adcd06952dd830cf..0d3bed68fd4ce7d8554306b4f31346ad8e0cbe09 100644 (file)
@@ -23,7 +23,7 @@ import * as React from 'react';
 import { connect } from 'react-redux';
 import { InjectedRouter } from 'react-router';
 import { withRouter } from '../../../components/hoc/withRouter';
-import { KeyCodes } from '../../../helpers/keycodes';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { getSettingsAppAllDefinitions, Store } from '../../../store/rootReducer';
 import { SettingCategoryDefinition } from '../../../types/settings';
 import { ADDITIONAL_SETTING_DEFINITIONS, buildSettingLink } from '../utils';
@@ -118,16 +118,16 @@ export class SettingsSearch extends React.Component<Props, State> {
   };
 
   handleKeyDown = (event: React.KeyboardEvent) => {
-    switch (event.keyCode) {
-      case KeyCodes.Enter:
+    switch (event.nativeEvent.code) {
+      case KeyboardCodes.Enter:
         event.preventDefault();
         this.openSelected();
         return;
-      case KeyCodes.UpArrow:
+      case KeyboardCodes.UpArrow:
         event.preventDefault();
         this.selectPrevious();
         return;
-      case KeyCodes.DownArrow:
+      case KeyboardCodes.DownArrow:
         event.preventDefault();
         this.selectNext();
         // keep this return to prevent fall-through in case more cases will be adder later
index a29b3db703eff647debf120a04b25de0158104d9..f6ea708a34de59030dbe5d1fd10cf9cde516fefd 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
 import { mockDefinition } from '../../../../helpers/mocks/settings';
 import { mockRouter } from '../../../../helpers/testMocks';
 import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
@@ -96,11 +97,11 @@ describe('instance', () => {
 
   it('should handle "enter" keyboard event', () => {
     wrapper.setState({ selectedResult: undefined });
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 13 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.Enter } }));
     expect(router.push).not.toBeCalled();
 
     wrapper.setState({ selectedResult: 'foo' });
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 13 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.Enter } }));
 
     expect(router.push).toBeCalledWith({
       hash: '#foo',
@@ -111,27 +112,27 @@ describe('instance', () => {
 
   it('should handle "down" keyboard event', () => {
     wrapper.setState({ selectedResult: undefined });
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 40 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.DownArrow } }));
     expect(wrapper.state().selectedResult).toBeUndefined();
 
     wrapper.setState({ selectedResult: 'foo' });
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 40 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.DownArrow } }));
     expect(wrapper.state().selectedResult).toBe('sonar.new_code_period');
 
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 40 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.DownArrow } }));
     expect(wrapper.state().selectedResult).toBe('sonar.new_code_period');
   });
 
   it('should handle "up" keyboard event', () => {
     wrapper.setState({ selectedResult: undefined });
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 38 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.UpArrow } }));
     expect(wrapper.state().selectedResult).toBeUndefined();
 
     wrapper.setState({ selectedResult: 'sonar.new_code_period' });
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 38 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.UpArrow } }));
     expect(wrapper.state().selectedResult).toBe('foo');
 
-    wrapper.instance().handleKeyDown(mockEvent({ keyCode: 38 }));
+    wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.UpArrow } }));
     expect(wrapper.state().selectedResult).toBe('foo');
   });
 });
index c7425dafd23e6d0bf10450a70f85940d028ba36b..3bf97fc13348dfe6d7542544d35e50d04e5f2047 100644 (file)
@@ -34,7 +34,9 @@ exports[`should render correctly 1`] = `
     <div
       className="settings-definition-state"
     />
-    <form>
+    <form
+      onSubmit={[Function]}
+    >
       <Input
         hasValueChanged={false}
         onCancel={[Function]}
index 31ad1d081189400f78095a8ec6e6aaff6e929597..8cb2110a064af4a2dc5f635462f95d72a8366387 100644 (file)
  */
 import classNames from 'classnames';
 import * as React from 'react';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
 import { DefaultSpecializedInputProps } from '../../utils';
 
-interface Props extends DefaultSpecializedInputProps {
+export interface SimpleInputProps extends DefaultSpecializedInputProps {
   value: string | number;
 }
 
-export default class SimpleInput extends React.PureComponent<Props> {
+export default class SimpleInput extends React.PureComponent<SimpleInputProps> {
   handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
     this.props.onChange(event.currentTarget.value);
   };
 
   handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
-    if (event.keyCode === 13 && this.props.onSave) {
+    if (event.nativeEvent.code === KeyboardCodes.Enter && this.props.onSave) {
       this.props.onSave();
-    } else if (event.keyCode === 27 && this.props.onCancel) {
+    } else if (event.nativeEvent.code === KeyboardCodes.Escape && this.props.onCancel) {
       this.props.onCancel();
     }
   };
index f61a4ee4ce59480e2dce99b37fbfde6d89a89eea..3c326c7f17caf52d5b2cd3715890f5326e0d0694 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { KeyboardCodes } from '../../../../../helpers/keycodes';
 import { mockSetting } from '../../../../../helpers/mocks/settings';
+import { mockEvent } from '../../../../../helpers/testMocks';
 import { change } from '../../../../../helpers/testUtils';
-import SimpleInput from '../SimpleInput';
+import SimpleInput, { SimpleInputProps } from '../SimpleInput';
 
 it('should render input', () => {
-  const onChange = jest.fn();
-  const input = shallow(
-    <SimpleInput
-      className="input-large"
-      isDefault={false}
-      name="foo"
-      onChange={onChange}
-      type="text"
-      setting={mockSetting()}
-      value="bar"
-    />
-  ).find('input');
+  const input = shallowRender().find('input');
   expect(input.length).toBe(1);
   expect(input.prop('type')).toBe('text');
   expect(input.prop('className')).toContain('input-large');
@@ -46,20 +37,51 @@ it('should render input', () => {
 
 it('should call onChange', () => {
   const onChange = jest.fn();
-  const input = shallow(
+  const input = shallowRender({ onChange }).find('input');
+  expect(input.length).toBe(1);
+  expect(input.prop('onChange')).toBeDefined();
+
+  change(input, 'qux');
+  expect(onChange).toBeCalledWith('qux');
+});
+
+it('should handle enter', () => {
+  const onSave = jest.fn();
+  shallowRender({ onSave })
+    .instance()
+    .handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.Enter } }));
+  expect(onSave).toBeCalled();
+});
+
+it('should handle esc', () => {
+  const onCancel = jest.fn();
+  shallowRender({ onCancel })
+    .instance()
+    .handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.Escape } }));
+  expect(onCancel).toBeCalled();
+});
+
+it('should ignore other keys', () => {
+  const onSave = jest.fn();
+  const onCancel = jest.fn();
+  shallowRender({ onCancel, onSave })
+    .instance()
+    .handleKeyDown(mockEvent({ nativeEvent: { code: KeyboardCodes.LeftArrow } }));
+  expect(onSave).not.toBeCalled();
+  expect(onCancel).not.toBeCalled();
+});
+
+function shallowRender(overrides: Partial<SimpleInputProps> = {}) {
+  return shallow<SimpleInput>(
     <SimpleInput
       className="input-large"
       isDefault={false}
       name="foo"
-      onChange={onChange}
+      onChange={jest.fn()}
       type="text"
       setting={mockSetting()}
       value="bar"
+      {...overrides}
     />
-  ).find('input');
-  expect(input.length).toBe(1);
-  expect(input.prop('onChange')).toBeDefined();
-
-  change(input, 'qux');
-  expect(onChange).toBeCalledWith('qux');
-});
+  );
+}
index 6f0aab023c85b8af983d9d5c85485e2b4aa85a70..67075ca2cc5b04788c89e775924598aadde8c033 100644 (file)
@@ -21,10 +21,11 @@ import classNames from 'classnames';
 import { difference } from 'lodash';
 import * as React from 'react';
 import SearchBox from '../../components/controls/SearchBox';
+import { KeyboardCodes } from '../../helpers/keycodes';
 import { translateWithParameters } from '../../helpers/l10n';
 import MultiSelectOption from './MultiSelectOption';
 
-interface Props {
+export interface MultiSelectProps {
   allowNewElements?: boolean;
   allowSelection?: boolean;
   elements: string[];
@@ -55,9 +56,9 @@ interface DefaultProps {
   validateSearchInput: (value: string) => string;
 }
 
-type PropsWithDefault = Props & DefaultProps;
+type PropsWithDefault = MultiSelectProps & DefaultProps;
 
-export default class MultiSelect extends React.PureComponent<Props, State> {
+export default class MultiSelect extends React.PureComponent<MultiSelectProps, State> {
   container?: HTMLDivElement | null;
   searchInput?: HTMLInputElement | null;
   mounted = false;
@@ -70,7 +71,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
     validateSearchInput: (value: string) => value
   };
 
-  constructor(props: Props) {
+  constructor(props: MultiSelectProps) {
     super(props);
     this.state = {
       activeIdx: 0,
@@ -137,23 +138,23 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
     });
   };
 
-  handleKeyboard = (evt: KeyboardEvent) => {
-    switch (evt.keyCode) {
-      case 40: // down
-        evt.stopPropagation();
-        evt.preventDefault();
+  handleKeyboard = (event: KeyboardEvent) => {
+    switch (event.code) {
+      case KeyboardCodes.DownArrow:
+        event.stopPropagation();
+        event.preventDefault();
         this.setState(this.selectNextElement);
         break;
-      case 38: // up
-        evt.stopPropagation();
-        evt.preventDefault();
+      case KeyboardCodes.UpArrow:
+        event.stopPropagation();
+        event.preventDefault();
         this.setState(this.selectPreviousElement);
         break;
-      case 37: // left
-      case 39: // right
-        evt.stopPropagation();
+      case KeyboardCodes.LeftArrow:
+      case KeyboardCodes.RightArrow:
+        event.stopPropagation();
         break;
-      case 13: // enter
+      case KeyboardCodes.Enter:
         if (this.state.activeIdx >= 0) {
           this.toggleSelect(this.getAllElements(this.props, this.state)[this.state.activeIdx]);
         }
@@ -175,7 +176,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
 
   onUnselectItem = (item: string) => this.props.onUnselect(item);
 
-  isNewElement = (elem: string, { selectedElements, elements }: Props) =>
+  isNewElement = (elem: string, { selectedElements, elements }: MultiSelectProps) =>
     elem.length > 0 && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1;
 
   updateSelectedElements = (props: PropsWithDefault) => {
@@ -207,7 +208,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
     });
   };
 
-  getAllElements = (props: Props, state: State) => {
+  getAllElements = (props: MultiSelectProps, state: State) => {
     if (this.isNewElement(state.query, props)) {
       return [...state.selectedElements, ...state.unselectedElements, state.query];
     } else {
@@ -217,7 +218,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
 
   setElementActive = (idx: number) => this.setState({ activeIdx: idx });
 
-  selectNextElement = (state: State, props: Props) => {
+  selectNextElement = (state: State, props: MultiSelectProps) => {
     const { activeIdx } = state;
     const allElements = this.getAllElements(props, state);
     if (activeIdx < 0 || activeIdx >= allElements.length - 1) {
@@ -227,7 +228,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
     }
   };
 
-  selectPreviousElement = (state: State, props: Props) => {
+  selectPreviousElement = (state: State, props: MultiSelectProps) => {
     const { activeIdx } = state;
     const allElements = this.getAllElements(props, state);
     if (activeIdx <= 0) {
index e27ce103f2c7209d0e17eb3ab31aedb4cf6e6d82..cf5cc752154074ff731cec7a60eae467bf854240 100644 (file)
@@ -21,6 +21,7 @@ import classNames from 'classnames';
 import key from 'keymaster';
 import { uniqueId } from 'lodash';
 import * as React from 'react';
+import { KeyboardCodes } from '../../helpers/keycodes';
 import SelectListItem from './SelectListItem';
 
 interface Props {
@@ -76,7 +77,11 @@ export default class SelectList extends React.PureComponent<Props, State> {
       filter: (event: KeyboardEvent & { target: HTMLElement }) => {
         const { tagName } = event.target || event.srcElement;
         const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
-        return [13, 38, 40].includes(event.keyCode) || !isInput;
+        return (
+          [KeyboardCodes.Enter, KeyboardCodes.UpArrow, KeyboardCodes.DownArrow].includes(
+            event.code as KeyboardCodes
+          ) || !isInput
+        );
       }
     });
 
index 2915951fc2d3efbdf4aae9555322622e405309d5..29cd2d8a565e7ebaa422f80ad823aa77acad999c 100644 (file)
@@ -19,7 +19,9 @@
  */
 import { mount, shallow } from 'enzyme';
 import * as React from 'react';
-import MultiSelect from '../MultiSelect';
+import { KeyboardCodes } from '../../../helpers/keycodes';
+import { mockEvent } from '../../../helpers/testUtils';
+import MultiSelect, { MultiSelectProps } from '../MultiSelect';
 
 const props = {
   selectedElements: ['bar'],
@@ -31,14 +33,10 @@ const props = {
   placeholder: ''
 };
 
-const elements = [
-  { key: 'foo', label: 'foo' },
-  { key: 'bar', label: 'bar' },
-  { key: 'baz', label: 'baz' }
-];
+const elements = ['foo', 'bar', 'baz'];
 
 it('should render multiselect with selected elements', () => {
-  const multiselect = shallow(<MultiSelect {...props} />);
+  const multiselect = shallowRender();
   // Will not only the selected element
   expect(multiselect).toMatchSnapshot();
 
@@ -55,9 +53,53 @@ it('should render with the focus inside the search input', () => {
    * Need to attach to document body to have it set to `document.activeElement`
    * See: https://github.com/jsdom/jsdom/issues/2723#issuecomment-580163361
    */
-  const multiselect = mount(<MultiSelect {...props} />, { attachTo: document.body });
+  const container = document.createElement('div');
+  document.body.appendChild(container);
+  const multiselect = mount(<MultiSelect {...props} />, { attachTo: container });
 
   expect(multiselect.find('input').getDOMNode()).toBe(document.activeElement);
 
   multiselect.unmount();
 });
+
+it.each([
+  [KeyboardCodes.DownArrow, 1, 1],
+  [KeyboardCodes.UpArrow, 1, 1],
+  [KeyboardCodes.LeftArrow, 1, 0]
+])('should handle keyboard event: %s', (code, stopPropagationCalls, preventDefaultCalls) => {
+  const wrapper = shallowRender();
+
+  const stopPropagation = jest.fn();
+  const preventDefault = jest.fn();
+  const event = mockEvent({ preventDefault, stopPropagation, code });
+
+  wrapper.instance().handleKeyboard(event);
+
+  expect(stopPropagation).toBeCalledTimes(stopPropagationCalls);
+  expect(preventDefault).toBeCalledTimes(preventDefaultCalls);
+});
+
+it('should handle keyboard event: enter', () => {
+  const wrapper = shallowRender();
+
+  wrapper.instance().toggleSelect = jest.fn();
+
+  wrapper.instance().handleKeyboard(mockEvent({ code: KeyboardCodes.Enter }));
+
+  expect(wrapper.instance().toggleSelect).toBeCalled();
+});
+
+function shallowRender(overrides: Partial<MultiSelectProps> = {}) {
+  return shallow<MultiSelect>(
+    <MultiSelect
+      selectedElements={['bar']}
+      elements={[]}
+      onSearch={() => Promise.resolve()}
+      onSelect={jest.fn()}
+      onUnselect={jest.fn()}
+      renderLabel={(element: string) => element}
+      placeholder=""
+      {...overrides}
+    />
+  );
+}
index 8c4449fcf9b8203dcac5f6d41505fe71214b93f5..1c83728fef72852fa6c09b5a28aae48f9387aea7 100644 (file)
  */
 import { mount, shallow } from 'enzyme';
 import * as React from 'react';
-import { click, keydown } from '../../../helpers/testUtils';
+import { KeyboardCodes } from '../../../helpers/keycodes';
+import { click, KEYCODE_MAP, keydown } from '../../../helpers/testUtils';
 import SelectList from '../SelectList';
 import SelectListItem from '../SelectListItem';
 
+jest.mock('keymaster', () => {
+  const key: any = (bindKey: string, _: string, callback: Function) => {
+    document.addEventListener('keydown', (event: KeyboardEvent) => {
+      const keymasterCode = event.code && KEYCODE_MAP[event.code as KeyboardCodes];
+      if (keymasterCode && bindKey.split(',').includes(keymasterCode)) {
+        return callback();
+      }
+      return true;
+    });
+  };
+  let scope = 'key-scope';
+
+  key.getScope = () => scope;
+  key.setScope = (newScope: string) => {
+    scope = newScope;
+  };
+  key.deleteScope = jest.fn();
+
+  return key;
+});
+
 it('should render correctly without children', () => {
   const onSelect = jest.fn();
   expect(
@@ -56,7 +78,7 @@ it('should render correctly with children', () => {
 it('should correclty handle user actions', () => {
   const onSelect = jest.fn();
   const items = ['item', 'seconditem', 'third'];
-  const list = mount(
+  const list = mount<SelectList>(
     <SelectList currentItem="seconditem" items={items} onSelect={onSelect}>
       {items.map(item => (
         <SelectListItem item={item} key={item}>
@@ -66,12 +88,15 @@ it('should correclty handle user actions', () => {
       ))}
     </SelectList>
   );
-  keydown(40);
-  expect(list.state()).toMatchSnapshot();
-  keydown(40);
-  expect(list.state()).toMatchSnapshot();
-  keydown(38);
-  expect(list.state()).toMatchSnapshot();
+  expect(list.state().active).toBe('seconditem');
+  keydown({ code: KeyboardCodes.DownArrow });
+  expect(list.state().active).toBe('third');
+  keydown({ code: KeyboardCodes.DownArrow });
+  expect(list.state().active).toBe('item');
+  keydown({ code: KeyboardCodes.UpArrow });
+  expect(list.state().active).toBe('third');
+  keydown({ code: KeyboardCodes.UpArrow });
+  expect(list.state().active).toBe('seconditem');
   click(list.find('a').at(2));
-  expect(onSelect.mock.calls).toMatchSnapshot(); // eslint-disable-linelist
+  expect(onSelect).toBeCalledWith('third');
 });
index 87a1bfd052c9ed239b4890c56d590d32f92074b5..bd2ca89e1dc834d8638271149998658f9dab4d9d 100644 (file)
@@ -63,13 +63,8 @@ exports[`should render multiselect with selected elements 2`] = `
     <MultiSelectOption
       active={false}
       disabled={false}
-      element={
-        Object {
-          "key": "foo",
-          "label": "foo",
-        }
-      }
-      key="[object Object]"
+      element="foo"
+      key="foo"
       onHover={[Function]}
       onSelectChange={[Function]}
       renderLabel={[Function]}
@@ -77,27 +72,8 @@ exports[`should render multiselect with selected elements 2`] = `
     <MultiSelectOption
       active={false}
       disabled={false}
-      element={
-        Object {
-          "key": "bar",
-          "label": "bar",
-        }
-      }
-      key="[object Object]"
-      onHover={[Function]}
-      onSelectChange={[Function]}
-      renderLabel={[Function]}
-    />
-    <MultiSelectOption
-      active={false}
-      disabled={false}
-      element={
-        Object {
-          "key": "baz",
-          "label": "baz",
-        }
-      }
-      key="[object Object]"
+      element="baz"
+      key="baz"
       onHover={[Function]}
       onSelectChange={[Function]}
       renderLabel={[Function]}
@@ -137,13 +113,8 @@ exports[`should render multiselect with selected elements 3`] = `
     <MultiSelectOption
       active={false}
       disabled={false}
-      element={
-        Object {
-          "key": "foo",
-          "label": "foo",
-        }
-      }
-      key="[object Object]"
+      element="foo"
+      key="foo"
       onHover={[Function]}
       onSelectChange={[Function]}
       renderLabel={[Function]}
@@ -151,27 +122,8 @@ exports[`should render multiselect with selected elements 3`] = `
     <MultiSelectOption
       active={true}
       disabled={false}
-      element={
-        Object {
-          "key": "bar",
-          "label": "bar",
-        }
-      }
-      key="[object Object]"
-      onHover={[Function]}
-      onSelectChange={[Function]}
-      renderLabel={[Function]}
-    />
-    <MultiSelectOption
-      active={false}
-      disabled={false}
-      element={
-        Object {
-          "key": "baz",
-          "label": "baz",
-        }
-      }
-      key="[object Object]"
+      element="baz"
+      key="baz"
       onHover={[Function]}
       onSelectChange={[Function]}
       renderLabel={[Function]}
@@ -211,13 +163,8 @@ exports[`should render multiselect with selected elements 4`] = `
     <MultiSelectOption
       active={false}
       disabled={false}
-      element={
-        Object {
-          "key": "foo",
-          "label": "foo",
-        }
-      }
-      key="[object Object]"
+      element="foo"
+      key="foo"
       onHover={[Function]}
       onSelectChange={[Function]}
       renderLabel={[Function]}
@@ -225,27 +172,8 @@ exports[`should render multiselect with selected elements 4`] = `
     <MultiSelectOption
       active={true}
       disabled={false}
-      element={
-        Object {
-          "key": "bar",
-          "label": "bar",
-        }
-      }
-      key="[object Object]"
-      onHover={[Function]}
-      onSelectChange={[Function]}
-      renderLabel={[Function]}
-    />
-    <MultiSelectOption
-      active={false}
-      disabled={false}
-      element={
-        Object {
-          "key": "baz",
-          "label": "baz",
-        }
-      }
-      key="[object Object]"
+      element="baz"
+      key="baz"
       onHover={[Function]}
       onSelectChange={[Function]}
       renderLabel={[Function]}
index c1c91a2ba8e2a8801d81e7e298e61d8fabada919..af2d76a8216bac11df8e60cfaff0a60586a29de0 100644 (file)
@@ -1,31 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should correclty handle user actions 1`] = `
-Object {
-  "active": "third",
-}
-`;
-
-exports[`should correclty handle user actions 2`] = `
-Object {
-  "active": "item",
-}
-`;
-
-exports[`should correclty handle user actions 3`] = `
-Object {
-  "active": "third",
-}
-`;
-
-exports[`should correclty handle user actions 4`] = `
-Array [
-  Array [
-    "third",
-  ],
-]
-`;
-
 exports[`should render correctly with children 1`] = `
 <ul
   className="menu"
index 487bf13a6af825e1d85e78c14fc78d002a05d35a..0c6a4ecfbd5a3ba5551c9e685817dd3aa5ccca9f 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { KeyCodes } from '../../helpers/keycodes';
+import { KeyboardCodes } from '../../helpers/keycodes';
 
 interface Props {
   children: React.ReactNode;
@@ -37,7 +37,7 @@ export default class EscKeydownHandler extends React.Component<Props> {
   }
 
   handleKeyDown = (event: KeyboardEvent) => {
-    if (event.keyCode === KeyCodes.Escape) {
+    if (event.code === KeyboardCodes.Escape) {
       this.props.onKeydown();
     }
   };
index 70c97cb3b1227781c79518a3d3026f9e93c82d83..d417471bae0202ab70641efae2c050f1ac1d7ace 100644 (file)
@@ -20,6 +20,7 @@
 import classNames from 'classnames';
 import { Cancelable, debounce } from 'lodash';
 import * as React from 'react';
+import { KeyboardCodes } from '../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import SearchIcon from '../icons/SearchIcon';
 import DeferredSpinner from '../ui/DeferredSpinner';
@@ -94,8 +95,7 @@ export default class SearchBox extends React.PureComponent<Props, State> {
   };
 
   handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
-    if (event.keyCode === 27) {
-      // escape
+    if (event.nativeEvent.code === KeyboardCodes.Escape) {
       event.preventDefault();
       this.handleResetClick();
     }
index 6c852477b81c840bf74d1a96666caaffaf397fc4..de7f00a27b88fd68075eeef961ba184250cbc102 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { KeyCodes } from '../../../helpers/keycodes';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { keydown } from '../../../helpers/testUtils';
 import EscKeydownHandler from '../EscKeydownHandler';
 
@@ -33,7 +33,7 @@ it('should correctly trigger the keydown handler when hitting Esc', () => {
   const onKeydown = jest.fn();
   shallowRender({ onKeydown });
   jest.runAllTimers();
-  keydown(KeyCodes.Escape);
+  keydown({ code: KeyboardCodes.Escape });
   expect(onKeydown).toBeCalled();
 });
 
index 22d907817b928b30b7ee6bf58b343d112bdf9bd8..ac05b25a60f86c0802d56837f63e9f6d72aa4e64 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { mount, shallow } from 'enzyme';
 import * as React from 'react';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { mockComponent } from '../../../helpers/mocks/component';
 import { KEYCODE_MAP, keydown } from '../../../helpers/testUtils';
 import withKeyboardNavigation, { WithKeyboardNavigationProps } from '../withKeyboardNavigation';
@@ -43,7 +44,8 @@ const COMPONENTS = [
 jest.mock('keymaster', () => {
   const key: any = (bindKey: string, _: string, callback: Function) => {
     document.addEventListener('keydown', (event: KeyboardEvent) => {
-      if (bindKey.split(',').includes(KEYCODE_MAP[event.keyCode])) {
+      const keymasterCode = event.code && KEYCODE_MAP[event.code as KeyboardCodes];
+      if (keymasterCode && bindKey.split(',').includes(keymasterCode)) {
         return callback();
       }
       return true;
@@ -78,28 +80,28 @@ it('should correctly bind key events for component navigation', () => {
     })
   );
 
-  keydown('down');
+  keydown({ code: KeyboardCodes.DownArrow });
   expect(onHighlight).toBeCalledWith(COMPONENTS[2]);
   expect(onSelect).not.toBeCalled();
 
-  keydown('up');
-  keydown('up');
+  keydown({ code: KeyboardCodes.UpArrow });
+  keydown({ code: KeyboardCodes.UpArrow });
   expect(onHighlight).toBeCalledWith(COMPONENTS[0]);
   expect(onSelect).not.toBeCalled();
 
-  keydown('up');
+  keydown({ code: KeyboardCodes.UpArrow });
   expect(onHighlight).toBeCalledWith(COMPONENTS[2]);
 
-  keydown('down');
+  keydown({ code: KeyboardCodes.DownArrow });
   expect(onHighlight).toBeCalledWith(COMPONENTS[0]);
 
-  keydown('right');
+  keydown({ code: KeyboardCodes.RightArrow });
   expect(onSelect).toBeCalledWith(COMPONENTS[0]);
 
-  keydown('enter');
+  keydown({ code: KeyboardCodes.Enter });
   expect(onSelect).toBeCalledWith(COMPONENTS[0]);
 
-  keydown('left');
+  keydown({ code: KeyboardCodes.LeftArrow });
   expect(onGoToParent).toBeCalled();
 });
 
@@ -116,18 +118,18 @@ it('should support not cycling through elements, and triggering a callback on re
     })
   );
 
-  keydown('down');
+  keydown({ code: KeyboardCodes.DownArrow });
   expect(onHighlight).toBeCalledWith(COMPONENTS[0]);
-  keydown('down');
-  keydown('down');
-  keydown('down');
+  keydown({ code: KeyboardCodes.DownArrow });
+  keydown({ code: KeyboardCodes.DownArrow });
+  keydown({ code: KeyboardCodes.DownArrow });
   expect(onHighlight).toBeCalledWith(COMPONENTS[2]);
   expect(onEndOfList).toBeCalled();
 
-  keydown('up');
-  keydown('up');
-  keydown('up');
-  keydown('up');
+  keydown({ code: KeyboardCodes.UpArrow });
+  keydown({ code: KeyboardCodes.UpArrow });
+  keydown({ code: KeyboardCodes.UpArrow });
+  keydown({ code: KeyboardCodes.UpArrow });
   expect(onHighlight).toBeCalledWith(COMPONENTS[0]);
 });
 
@@ -148,19 +150,19 @@ it('should correctly bind key events for codeview navigation', () => {
 
   expect(onHighlight).not.toBeCalled();
 
-  keydown('down');
+  keydown({ code: KeyboardCodes.DownArrow });
   expect(onHighlight).not.toBeCalled();
 
-  keydown('up');
+  keydown({ code: KeyboardCodes.UpArrow });
   expect(onHighlight).not.toBeCalled();
 
-  keydown('right');
+  keydown({ code: KeyboardCodes.RightArrow });
   expect(onSelect).not.toBeCalled();
 
-  keydown('enter');
+  keydown({ code: KeyboardCodes.Enter });
   expect(onSelect).not.toBeCalled();
 
-  keydown('left');
+  keydown({ code: KeyboardCodes.LeftArrow });
   expect(onGoToParent).toBeCalled();
 });
 
index e03c28cb1750e0149b31c2b4ebbb745027b4c039..4481dacee72b4277767d1bfb51dae5bda7d78991 100644 (file)
@@ -21,10 +21,11 @@ import * as React from 'react';
 import { Button, ResetButtonLink } from '../../../components/controls/buttons';
 import { DropdownOverlay } from '../../../components/controls/Dropdown';
 import { PopupPlacement } from '../../../components/ui/popups';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { translate } from '../../../helpers/l10n';
 import FormattingTips from '../../common/FormattingTips';
 
-interface Props {
+export interface CommentPopupProps {
   comment?: Pick<T.IssueComment, 'markdown'>;
   onComment: (text: string) => void;
   toggleComment: (visible: boolean) => void;
@@ -37,8 +38,8 @@ interface State {
   textComment: string;
 }
 
-export default class CommentPopup extends React.PureComponent<Props, State> {
-  constructor(props: Props) {
+export default class CommentPopup extends React.PureComponent<CommentPopupProps, State> {
+  constructor(props: CommentPopupProps) {
     super(props);
     this.state = {
       textComment: props.comment ? props.comment.markdown : ''
@@ -60,10 +61,16 @@ export default class CommentPopup extends React.PureComponent<Props, State> {
   };
 
   handleKeyboard = (event: React.KeyboardEvent) => {
-    if (event.keyCode === 13 && (event.metaKey || event.ctrlKey)) {
-      // Ctrl + Enter
+    if (event.nativeEvent.code === KeyboardCodes.Enter && (event.metaKey || event.ctrlKey)) {
       this.handleCommentClick();
-    } else if ([37, 38, 39, 40].includes(event.keyCode)) {
+    } else if (
+      [
+        KeyboardCodes.UpArrow,
+        KeyboardCodes.DownArrow,
+        KeyboardCodes.LeftArrow,
+        KeyboardCodes.RightArrow
+      ].includes(event.nativeEvent.code as KeyboardCodes)
+    ) {
       // Arrow keys
       event.stopPropagation();
     }
index 3893fee0716fd8cd5eeb21ac9d8efe5f49211a8e..5de1a41accd14943a02365c74e8b006c09daa320 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
+import { mockEvent } from '../../../../helpers/testMocks';
 import { click } from '../../../../helpers/testUtils';
-import CommentPopup from '../CommentPopup';
+import CommentPopup, { CommentPopupProps } from '../CommentPopup';
 
 it('should render the comment popup correctly without existing comment', () => {
-  const element = shallow(
-    <CommentPopup onComment={jest.fn()} placeholder="placeholder test" toggleComment={jest.fn()} />
-  );
-  expect(element).toMatchSnapshot();
+  expect(shallowRender()).toMatchSnapshot();
 });
 
 it('should render the comment popup correctly when changing a comment', () => {
-  const element = shallow(
-    <CommentPopup
-      comment={{ markdown: '*test*' }}
-      onComment={jest.fn()}
-      placeholder=""
-      toggleComment={jest.fn()}
-    />
-  );
-  expect(element).toMatchSnapshot();
+  expect(shallowRender({ comment: { markdown: '*test*' } })).toMatchSnapshot();
 });
 
 it('should render not allow to send comment with only spaces', () => {
   const onComment = jest.fn();
-  const element = shallow(
-    <CommentPopup onComment={onComment} placeholder="placeholder test" toggleComment={jest.fn()} />
-  );
-  click(element.find('.js-issue-comment-submit'));
+  const wrapper = shallowRender({ onComment });
+  click(wrapper.find('.js-issue-comment-submit'));
   expect(onComment.mock.calls.length).toBe(0);
-  element.setState({ textComment: 'mycomment' });
-  click(element.find('.js-issue-comment-submit'));
+  wrapper.setState({ textComment: 'mycomment' });
+  click(wrapper.find('.js-issue-comment-submit'));
   expect(onComment.mock.calls.length).toBe(1);
 });
 
 it('should render the alternative cancel button label', () => {
-  const element = shallow(
-    <CommentPopup
-      autoTriggered={true}
-      onComment={jest.fn()}
-      placeholder=""
-      toggleComment={jest.fn()}
-    />
-  );
+  const wrapper = shallowRender({ autoTriggered: true });
   expect(
-    element
+    wrapper
       .find('.js-issue-comment-cancel')
       .childAt(0)
       .text()
   ).toBe('skip');
 });
+
+it('should handle ctrl+enter', () => {
+  const onComment = jest.fn();
+  const wrapper = shallowRender({ comment: { markdown: 'yes' }, onComment });
+
+  wrapper
+    .instance()
+    .handleKeyboard(mockEvent({ ctrlKey: true, nativeEvent: { code: KeyboardCodes.Enter } }));
+
+  expect(onComment).toBeCalled();
+});
+
+it('should stopPropagation for arrow keys events', () => {
+  const wrapper = shallowRender();
+
+  const event = mockEvent({
+    nativeEvent: { code: KeyboardCodes.UpArrow },
+    stopPropagation: jest.fn()
+  });
+  wrapper.instance().handleKeyboard(event);
+
+  expect(event.stopPropagation).toBeCalled();
+});
+
+function shallowRender(overrides: Partial<CommentPopupProps> = {}) {
+  return shallow<CommentPopup>(
+    <CommentPopup
+      onComment={jest.fn()}
+      placeholder="placeholder test"
+      toggleComment={jest.fn()}
+      {...overrides}
+    />
+  );
+}
index 9962424c1c3b33cf90ef0bb6ed2653aaa95b0de7..9fc6c4cc2284389e8dad8fc480f0865509c7fabb 100644 (file)
@@ -12,7 +12,7 @@ exports[`should render the comment popup correctly when changing a comment 1`] =
         autoFocus={true}
         onChange={[Function]}
         onKeyDown={[Function]}
-        placeholder=""
+        placeholder="placeholder test"
         rows={2}
         value="*test*"
       />
index c57d397ae89eea7291246a5e177610d6496fd1dd..2976b56ae771bf3361d179a7da8e0d70b244603f 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-export enum KeyCodes {
-  LeftArrow = 37,
-  UpArrow = 38,
-  RightArrow = 39,
-  DownArrow = 40,
-
-  Alt = 18,
-  Backspace = 8,
-  CapsLock = 20,
-  Command = 93,
-  Ctrl = 17,
-  Delete = 46,
-  End = 35,
-  Enter = 13,
-  Escape = 27,
-  Home = 36,
-  PageDown = 34,
-  PageUp = 33,
-  Shift = 16,
-  Space = 32,
-  Tab = 9,
+export enum KeyboardCodes {
+  LeftArrow = 'ArrowLeft',
+  UpArrow = 'ArrowUp',
+  RightArrow = 'ArrowRight',
+  DownArrow = 'ArrowDown',
+  Backspace = 'Backspace',
+  CapsLock = 'CapsLock',
+  Command = 'ContextMenu',
+  Delete = 'Delete',
+  End = 'End',
+  Enter = 'Enter',
+  Escape = 'Escape',
+  Home = 'Home',
+  PageDown = 'PageDown',
+  PageUp = 'PageUp',
+  Space = 'Space',
+  Tab = 'Tab'
+}
 
-  A = 65,
-  B = 66,
-  C = 67,
-  D = 68,
-  E = 69,
-  F = 70,
-  G = 71,
-  H = 72,
-  I = 73,
-  J = 74,
-  K = 75,
-  L = 76,
-  M = 77,
-  N = 78,
-  O = 79,
-  P = 80,
-  Q = 81,
-  R = 82,
-  S = 83,
-  T = 84,
-  U = 85,
-  V = 86,
-  W = 87,
-  X = 88,
-  Y = 89,
-  Z = 90
+export enum KeyboardKeys {
+  Alt = 'Alt'
 }
index 9a7b019eef1229958ed9a3c60650e0c7dfcb7ffe..32fa9d709bd9c93da16ee16582286bf7706ba18c 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { ReactWrapper, ShallowWrapper } from 'enzyme';
+import { KeyboardCodes, KeyboardKeys } from './keycodes';
 
 export function mockEvent(overrides = {}) {
   return {
@@ -68,37 +69,26 @@ export function change(element: ShallowWrapper | ReactWrapper, value: string, ev
   }
 }
 
-export const KEYCODE_MAP: { [keycode: number]: string } = {
-  13: 'enter',
-  37: 'left',
-  38: 'up',
-  39: 'right',
-  40: 'down'
+export const KEYCODE_MAP: { [code in KeyboardCodes]?: string } = {
+  [KeyboardCodes.Enter]: 'enter',
+  [KeyboardCodes.LeftArrow]: 'left',
+  [KeyboardCodes.UpArrow]: 'up',
+  [KeyboardCodes.RightArrow]: 'right',
+  [KeyboardCodes.DownArrow]: 'down'
 };
 
-export function keydown(key: number | string): void {
-  let keyCode;
-  if (typeof key === 'number') {
-    keyCode = key;
-  } else {
-    // eslint-disable-next-line no-console
-    console.warn('Using strings in keydown() is deprecated. Consider using the KeyCodes enum.');
-    const mapped = Object.entries(KEYCODE_MAP).find(([_, value]) => value === key);
-    if (!mapped) {
-      throw new Error(`Cannot map key "${key}" to a keyCode!`);
-    }
-    keyCode = mapped[0];
-  }
-
-  const event = new KeyboardEvent('keydown', { keyCode, which: keyCode } as KeyboardEventInit);
+export function keydown(args: { code?: KeyboardCodes; key?: KeyboardKeys }): void {
+  const event = new KeyboardEvent('keydown', args as KeyboardEventInit);
   document.dispatchEvent(event);
 }
 
-export function elementKeydown(element: ShallowWrapper, keyCode: number): void {
+export function elementKeydown(element: ShallowWrapper, code: KeyboardCodes): void {
   const event = {
     currentTarget: { element },
-    keyCode,
-    preventDefault() {}
+    nativeEvent: { code },
+    preventDefault() {
+      /*noop*/
+    }
   };
 
   if (typeof element.type() === 'string') {