]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16338 Replace keymaster in Measures
authorGuillaume Peoc'h <guillaume.peoch@sonarsource.com>
Tue, 3 May 2022 13:06:43 +0000 (15:06 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 10 May 2022 20:02:47 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx

index c44ff041d5fde632c750de03d9e9e9474fbf3c60..280b6076506ce5ed5d5fa2a4908ffc8f1a8645a3 100644 (file)
@@ -18,7 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import styled from '@emotion/styled';
-import key from 'keymaster';
 import { debounce, keyBy } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
@@ -104,7 +103,6 @@ export class App extends React.PureComponent<Props, State> {
   componentDidMount() {
     this.mounted = true;
 
-    key.setScope('measures-files');
     getAllMetrics().then(
       metrics => {
         const byKey = keyBy(metrics, 'key');
@@ -137,7 +135,6 @@ export class App extends React.PureComponent<Props, State> {
     this.mounted = false;
     removeWhitePageClass();
     removeSideBarClass();
-    key.deleteScope('measures-files');
   }
 
   fetchMeasures(metrics: State['metrics']) {
index 98d6a79748c98efd1d9ec67aafa65824847afd4f..9fd2ddcbf7afdeffbff65675e5d071a512957142 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.
  */
-import key from 'keymaster';
 import * as React from 'react';
 import { getBreadcrumbs } from '../../../api/components';
 import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { BranchLike } from '../../../types/branch-like';
 import { ComponentMeasure, ComponentMeasureIntern } from '../../../types/types';
 import Breadcrumb from './Breadcrumb';
@@ -45,7 +45,7 @@ export default class Breadcrumbs extends React.PureComponent<Props, State> {
   componentDidMount() {
     this.mounted = true;
     this.fetchBreadcrumbs();
-    this.attachShortcuts();
+    document.addEventListener('keydown', this.handleKeyDown);
   }
 
   componentDidUpdate(prevProps: Props) {
@@ -59,23 +59,19 @@ export default class Breadcrumbs extends React.PureComponent<Props, State> {
 
   componentWillUnmount() {
     this.mounted = false;
-    this.detachShortcuts();
+    document.removeEventListener('keydown', this.handleKeyDown);
   }
 
-  attachShortcuts() {
-    key('left', 'measures-files', () => {
+  handleKeyDown = (event: KeyboardEvent) => {
+    if (event.code === KeyboardCodes.LeftArrow) {
+      event.preventDefault();
       const { breadcrumbs } = this.state;
       if (breadcrumbs.length > 1) {
         const idx = this.props.backToFirst ? 0 : breadcrumbs.length - 2;
         this.props.handleSelect(breadcrumbs[idx]);
       }
-      return false;
-    });
-  }
-
-  detachShortcuts() {
-    key.unbind('left', 'measures-files');
-  }
+    }
+  };
 
   fetchBreadcrumbs = () => {
     const { branchLike, component, rootComponent } = this.props;
index ad8796fb13e69eebc6c262e4a1bf3a93c49bbc31..0a5167a2723f2bb54676131cc18fd0adc6296ccf 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.
  */
-import { mount } from 'enzyme';
+import { mount, shallow } from 'enzyme';
 import * as React from 'react';
 import { getBreadcrumbs } from '../../../../api/components';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
+import { keydown, waitAndUpdate } from '../../../../helpers/testUtils';
 import Breadcrumbs from '../Breadcrumbs';
 
 jest.mock('../../../../api/components', () => ({
@@ -60,26 +61,32 @@ it('should display correctly for the list view', () => {
 });
 
 it('should display only the root component', () => {
-  const wrapper = mount(
-    <Breadcrumbs
-      backToFirst={false}
-      component={componentFoo}
-      handleSelect={() => {}}
-      rootComponent={componentFoo}
-    />
-  );
+  const wrapper = shallowRender({ component: componentFoo });
   expect(wrapper.state()).toMatchSnapshot();
 });
 
 it('should load the breadcrumb from the api', async () => {
-  const wrapper = mount(
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(getBreadcrumbs).toHaveBeenCalled();
+});
+
+it('should correctly handle keyboard action', async () => {
+  const handleSelect = jest.fn();
+  const wrapper = shallowRender({ handleSelect });
+  await waitAndUpdate(wrapper);
+  keydown({ code: KeyboardCodes.LeftArrow });
+  expect(handleSelect).toHaveBeenCalled();
+});
+
+function shallowRender(props: Partial<Breadcrumbs['props']> = {}) {
+  return shallow<Breadcrumbs>(
     <Breadcrumbs
       backToFirst={false}
       component={componentBar}
       handleSelect={() => {}}
       rootComponent={componentFoo}
+      {...props}
     />
   );
-  await waitAndUpdate(wrapper);
-  expect(getBreadcrumbs).toHaveBeenCalled();
-});
+}
index 719c32deda61e66ee738e2c2cd5dca75c2d4f378..74c2a9195ab01a50ecd191311d094c40b40b87c5 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.
  */
-import key from 'keymaster';
 import { throttle } from 'lodash';
 import * as React from 'react';
 import { Button } from '../../../components/controls/buttons';
 import ListFooter from '../../../components/controls/ListFooter';
 import { Alert } from '../../../components/ui/Alert';
+import { KeyboardCodes } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { formatMeasure, isDiffMetric, isPeriodBestValue } from '../../../helpers/measures';
 import { scrollToElement } from '../../../helpers/scrolling';
@@ -58,8 +58,6 @@ interface State {
   showBestMeasures: boolean;
 }
 
-const keyScope = 'measures-files';
-
 export default class FilesView extends React.PureComponent<Props, State> {
   listContainer?: HTMLElement | null;
 
@@ -71,7 +69,7 @@ export default class FilesView extends React.PureComponent<Props, State> {
   }
 
   componentDidMount() {
-    this.attachShortcuts();
+    document.addEventListener('keydown', this.handleKeyDown);
     if (this.props.selectedComponent !== undefined) {
       this.scrollToElement();
     }
@@ -90,27 +88,21 @@ export default class FilesView extends React.PureComponent<Props, State> {
   }
 
   componentWillUnmount() {
-    this.detachShortcuts();
+    document.removeEventListener('keydown', this.handleKeyDown);
   }
 
-  attachShortcuts() {
-    key('up', keyScope, () => {
+  handleKeyDown = (event: KeyboardEvent) => {
+    if (event.code === KeyboardCodes.UpArrow) {
+      event.preventDefault();
       this.selectPrevious();
-      return false;
-    });
-    key('down', keyScope, () => {
+    } else if (event.code === KeyboardCodes.DownArrow) {
+      event.preventDefault();
       this.selectNext();
-      return false;
-    });
-    key('right', keyScope, () => {
+    } else if (event.code === KeyboardCodes.RightArrow) {
+      event.preventDefault();
       this.openSelected();
-      return false;
-    });
-  }
-
-  detachShortcuts() {
-    ['up', 'down', 'right'].forEach(action => key.unbind(action, keyScope));
-  }
+    }
+  };
 
   getVisibleComponents = () => {
     const { components } = this.props;
index b07c8937873ce3a147c2919d4c9cc579cf029684..53eb6307322eb06ef59c10515e7be53103bf902d 100644 (file)
@@ -19,6 +19,9 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { KeyboardCodes } from '../../../../helpers/keycodes';
+import { mockMetric } from '../../../../helpers/testMocks';
+import { keydown } from '../../../../helpers/testUtils';
 import FilesView from '../FilesView';
 
 const COMPONENTS = [
@@ -33,17 +36,17 @@ const COMPONENTS = [
 const METRICS = { coverage: { id: '1', key: 'coverage', type: 'PERCENT', name: 'Coverage' } };
 
 it('should renders correctly', () => {
-  expect(getWrapper()).toMatchSnapshot();
+  expect(shallowRender()).toMatchSnapshot();
 });
 
 it('should render with best values hidden', () => {
   expect(
-    getWrapper({
+    shallowRender({
       components: [
         ...COMPONENTS,
         {
           key: 'bar',
-          measures: [{ bestValue: true, metric: { key: 'coverage' } }],
+          measures: [{ bestValue: true, metric: mockMetric({ key: 'coverage' }) }],
           name: 'Bar',
           qualifier: 'TRK'
         }
@@ -52,8 +55,49 @@ it('should render with best values hidden', () => {
   ).toMatchSnapshot();
 });
 
-function getWrapper(props = {}) {
-  return shallow(
+it('should correctly bind key events for file navigation', () => {
+  const handleSelect = jest.fn();
+  const handleOpen = jest.fn();
+  const FILES = [
+    {
+      key: 'foo',
+      measures: [],
+      name: 'Foo',
+      qualifier: 'TRK'
+    },
+    {
+      key: 'bar',
+      measures: [],
+      name: 'Bar',
+      qualifier: 'TRK'
+    },
+    {
+      key: 'yoo',
+      measures: [],
+      name: 'Yoo',
+      qualifier: 'TRK'
+    }
+  ];
+
+  shallowRender({
+    handleSelect,
+    handleOpen,
+    selectedComponent: FILES[0],
+    components: FILES
+  });
+
+  keydown({ code: KeyboardCodes.DownArrow });
+  expect(handleSelect).toBeCalledWith(FILES[0]);
+
+  keydown({ code: KeyboardCodes.UpArrow });
+  expect(handleSelect).toBeCalledWith(FILES[2]);
+
+  keydown({ code: KeyboardCodes.RightArrow });
+  expect(handleOpen).toBeCalled();
+});
+
+function shallowRender(props: Partial<FilesView['props']> = {}) {
+  return shallow<FilesView>(
     <FilesView
       components={COMPONENTS}
       defaultShowBestMeasures={false}