From: Guillaume Peoc'h Date: Tue, 3 May 2022 13:06:43 +0000 (+0200) Subject: SONAR-16338 Replace keymaster in Measures X-Git-Tag: 9.5.0.56709~156 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=02519d93219660a558c9ac1837f30e869ce005a5;p=sonarqube.git SONAR-16338 Replace keymaster in Measures --- diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx index c44ff041d5f..280b6076506 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx @@ -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 { 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 { this.mounted = false; removeWhitePageClass(); removeSideBarClass(); - key.deleteScope('measures-files'); } fetchMeasures(metrics: State['metrics']) { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx index 98d6a79748c..9fd2ddcbf7a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx @@ -17,10 +17,10 @@ * 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 { 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 { 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; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx index ad8796fb13e..0a5167a2723 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx @@ -17,10 +17,11 @@ * 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( - {}} - 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 = {}) { + return shallow( {}} rootComponent={componentFoo} + {...props} /> ); - await waitAndUpdate(wrapper); - expect(getBreadcrumbs).toHaveBeenCalled(); -}); +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx index 719c32deda6..74c2a9195ab 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx @@ -17,12 +17,12 @@ * 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 { listContainer?: HTMLElement | null; @@ -71,7 +69,7 @@ export default class FilesView extends React.PureComponent { } 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 { } 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; diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx index b07c8937873..53eb6307322 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx @@ -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 = {}) { + return shallow(