Browse Source

SONAR-16340 Replace keymaster in Search

tags/9.5.0.56709
Guillaume Peoc'h 2 years ago
parent
commit
b49e9d58f1

+ 30
- 21
server/sonar-web/src/main/js/app/components/search/Search.tsx View 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;
}
};


+ 16
- 7
server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx View 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);
}


+ 6
- 2
server/sonar-web/src/main/js/helpers/keycodes.ts View 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'
}

+ 7
- 2
server/sonar-web/src/main/js/helpers/testUtils.ts View 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*/
}

Loading…
Cancel
Save