]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16340 Replace keymaster in Search
authorGuillaume Peoc'h <guillaume.peoch@sonarsource.com>
Wed, 11 May 2022 12:28:30 +0000 (14:28 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 13 May 2022 20:02:50 +0000 (20:02 +0000)
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/helpers/keycodes.ts
server/sonar-web/src/main/js/helpers/testUtils.ts

index 4ddad49b88e4b73218d61296ee356aad9cb0254c..47549c7d890411912f58d6f52e674188ce6e9e86 100644 (file)
@@ -17,7 +17,6 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import key from 'keymaster';
 import { debounce, keyBy, uniqBy } from 'lodash';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
@@ -29,7 +28,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 { KeyboardKeys } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { scrollToElement } from '../../../helpers/scrolling';
 import { getComponentOverviewUrl } from '../../../helpers/urls';
@@ -77,11 +76,7 @@ export class Search extends React.PureComponent<WithRouterProps, State> {
 
   componentDidMount() {
     this.mounted = true;
-    key('s', () => {
-      this.focusInput();
-      this.openSearch();
-      return false;
-    });
+    document.addEventListener('keydown', this.handleSKeyDown);
   }
 
   componentDidUpdate(_prevProps: WithRouterProps, prevState: State) {
@@ -92,7 +87,7 @@ export class Search extends React.PureComponent<WithRouterProps, State> {
 
   componentWillUnmount() {
     this.mounted = false;
-    key.unbind('s');
+    document.removeEventListener('keydown', this.handleSKeyDown);
   }
 
   focusInput = () => {
@@ -227,9 +222,8 @@ export class Search extends React.PureComponent<WithRouterProps, State> {
         const list = this.getPlainComponentsList(results, more);
         const index = list.indexOf(selected);
         return index > 0 ? { selected: list[index - 1] } : null;
-      } else {
-        return null;
       }
+      return null;
     });
   };
 
@@ -239,9 +233,8 @@ export class Search extends React.PureComponent<WithRouterProps, State> {
         const list = this.getPlainComponentsList(results, more);
         const index = list.indexOf(selected);
         return index >= 0 && index < list.length - 1 ? { selected: list[index + 1] } : null;
-      } else {
-        return null;
       }
+      return null;
     });
   };
 
@@ -278,22 +271,38 @@ export class Search extends React.PureComponent<WithRouterProps, State> {
     }
   };
 
+  handleSKeyDown = (event: KeyboardEvent) => {
+    const { tagName } = event.target as HTMLElement;
+    const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
+    if (event.key === KeyboardKeys.KeyS && !isInput) {
+      event.preventDefault();
+      this.focusInput();
+      this.openSearch();
+    }
+  };
+
   handleKeyDown = (event: React.KeyboardEvent) => {
-    switch (event.nativeEvent.code) {
-      case KeyboardCodes.Enter:
+    switch (event.nativeEvent.key) {
+      case KeyboardKeys.Enter:
         event.preventDefault();
+        event.nativeEvent.stopImmediatePropagation();
         this.openSelected();
-        return;
-      case KeyboardCodes.UpArrow:
+        break;
+      case KeyboardKeys.UpArrow:
         event.preventDefault();
+        event.nativeEvent.stopImmediatePropagation();
         this.selectPrevious();
-        return;
-      case KeyboardCodes.DownArrow:
+        break;
+      case KeyboardKeys.Escape:
+        event.preventDefault();
+        event.nativeEvent.stopImmediatePropagation();
+        this.closeSearch();
+        break;
+      case KeyboardKeys.DownArrow:
         event.preventDefault();
+        event.nativeEvent.stopImmediatePropagation();
         this.selectNext();
-        // keep this return to prevent fall-through in case more cases will be adder later
-        // eslint-disable-next-line no-useless-return
-        return;
+        break;
     }
   };
 
index 77f6e7b2fbdff7537525abadff9ef869fc46fe8d..39d4e1d3895c98b1a7bae427b5b81101233a42d9 100644 (file)
@@ -19,9 +19,9 @@
  */
 import { shallow, ShallowWrapper } from 'enzyme';
 import * as React from 'react';
-import { KeyboardCodes } from '../../../../helpers/keycodes';
+import { KeyboardKeys } from '../../../../helpers/keycodes';
 import { mockRouter } from '../../../../helpers/testMocks';
-import { elementKeydown } from '../../../../helpers/testUtils';
+import { elementKeydown, keydown } from '../../../../helpers/testUtils';
 import { ComponentQualifier } from '../../../../types/component';
 import { Search } from '../Search';
 
@@ -57,7 +57,7 @@ it('opens selected project on enter', () => {
     selected: selectedKey
   });
 
-  elementKeydown(form.find('SearchBox'), KeyboardCodes.Enter);
+  elementKeydown(form.find('SearchBox'), KeyboardKeys.Enter);
   expect(router.push).toBeCalledWith({ pathname: '/dashboard', query: { id: selectedKey } });
 });
 
@@ -73,7 +73,7 @@ it('opens selected portfolio on enter', () => {
     selected: selectedKey
   });
 
-  elementKeydown(form.find('SearchBox'), KeyboardCodes.Enter);
+  elementKeydown(form.find('SearchBox'), KeyboardKeys.Enter);
   expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
 });
 
@@ -89,7 +89,7 @@ it('opens selected subportfolio on enter', () => {
     selected: selectedKey
   });
 
-  elementKeydown(form.find('SearchBox'), KeyboardCodes.Enter);
+  elementKeydown(form.find('SearchBox'), KeyboardKeys.Enter);
   expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
 });
 
@@ -101,6 +101,15 @@ it('shows warning about short input', () => {
   expect(form.find('.navbar-search-input-hint')).toMatchSnapshot();
 });
 
+it('should open the results when pressing key S and close it when pressing Escape', () => {
+  const router = mockRouter();
+  const form = shallowRender({ router });
+  keydown({ key: KeyboardKeys.KeyS });
+  expect(form.state().open).toBe(true);
+  elementKeydown(form.find('SearchBox'), KeyboardKeys.Escape);
+  expect(form.state().open).toBe(false);
+});
+
 function shallowRender(props: Partial<Search['props']> = {}) {
   return shallow<Search>(
     // @ts-ignore
@@ -113,12 +122,12 @@ function component(key: string, qualifier = ComponentQualifier.Project) {
 }
 
 function next(form: ShallowWrapper<Search['props'], Search['state']>, expected: string) {
-  elementKeydown(form.find('SearchBox'), KeyboardCodes.DownArrow);
+  elementKeydown(form.find('SearchBox'), KeyboardKeys.DownArrow);
   expect(form.state().selected).toBe(expected);
 }
 
 function prev(form: ShallowWrapper<Search['props'], Search['state']>, expected: string) {
-  elementKeydown(form.find('SearchBox'), KeyboardCodes.UpArrow);
+  elementKeydown(form.find('SearchBox'), KeyboardKeys.UpArrow);
   expect(form.state().selected).toBe(expected);
 }
 
index eb3229aebd2e4b7934085f054443d1a5926478b5..04c3b908fac896e180f197500aa756a1ede35ffe 100644 (file)
@@ -37,6 +37,11 @@ export enum KeyboardCodes {
 }
 
 export enum KeyboardKeys {
+  Escape = 'Escape',
+  UpArrow = 'ArrowUp',
+  DownArrow = 'ArrowDown',
+  Enter = 'Enter',
+  Space = ' ',
   Alt = 'Alt',
   KeyF = 'f',
   KeyA = 'a',
@@ -44,6 +49,5 @@ export enum KeyboardKeys {
   KeyI = 'i',
   KeyC = 'c',
   KeyT = 't',
-  Space = ' ',
-  Escape = 'Escape'
+  KeyS = 's'
 }
index e43030383a44c6f8bca51e27e0765acf309083c3..1b1abba7dbe11798cc329f6b94d67968543d9fdb 100644 (file)
@@ -87,10 +87,15 @@ export function keydown(args: { code?: KeyboardCodes; key?: KeyboardKeys }): voi
   document.dispatchEvent(event);
 }
 
-export function elementKeydown(element: ShallowWrapper, code: KeyboardCodes): void {
+export function elementKeydown(element: ShallowWrapper, key: KeyboardKeys): void {
   const event = {
     currentTarget: { element },
-    nativeEvent: { code },
+    nativeEvent: {
+      key,
+      stopImmediatePropagation: () => {
+        /* noop */
+      }
+    },
     preventDefault() {
       /*noop*/
     }