* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
-import key from 'keymaster';
import { debounce, keyBy, omit, without } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
}
attachShortcuts() {
- key.setScope('issues');
- key('up', 'issues', () => {
- this.selectPreviousIssue();
- return false;
- });
- key('down', 'issues', () => {
- this.selectNextIssue();
- return false;
- });
- key('right', 'issues', () => {
- this.openSelectedIssue();
- return false;
- });
- key('left', 'issues', () => {
- if (this.state.query.issues.length !== 1) {
- this.closeIssue();
- }
- return false;
- });
- window.addEventListener('keydown', this.handleKeyDown);
- window.addEventListener('keyup', this.handleKeyUp);
+ document.addEventListener('keydown', this.handleKeyDown);
+ document.addEventListener('keyup', this.handleKeyUp);
}
detachShortcuts() {
- key.deleteScope('issues');
- window.removeEventListener('keydown', this.handleKeyDown);
- window.removeEventListener('keyup', this.handleKeyUp);
+ document.removeEventListener('keydown', this.handleKeyDown);
+ document.removeEventListener('keyup', this.handleKeyUp);
}
handleKeyDown = (event: KeyboardEvent) => {
- if (key.getScope() !== 'issues') {
+ // Ignore if modal is open
+ if (this.state.bulkChangeModal) {
return;
}
+
if (event.key === KeyboardKeys.Alt) {
event.preventDefault();
this.setState(actions.enableLocationsNavigator);
- } else if (event.code === KeyboardCodes.DownArrow && event.altKey) {
- event.preventDefault();
- this.selectNextLocation();
- } else if (event.code === KeyboardCodes.UpArrow && event.altKey) {
- event.preventDefault();
- this.selectPreviousLocation();
- } else if (event.code === KeyboardCodes.LeftArrow && event.altKey) {
- event.preventDefault();
- this.selectPreviousFlow();
- } else if (event.code === KeyboardCodes.RightArrow && event.altKey) {
- event.preventDefault();
- this.selectNextFlow();
+ return;
+ }
+
+ switch (event.code) {
+ case KeyboardCodes.DownArrow: {
+ event.preventDefault();
+ if (event.altKey) {
+ this.selectNextLocation();
+ } else {
+ this.selectNextIssue();
+ }
+ break;
+ }
+ case KeyboardCodes.UpArrow: {
+ event.preventDefault();
+ if (event.altKey) {
+ this.selectPreviousLocation();
+ } else {
+ this.selectPreviousIssue();
+ }
+ break;
+ }
+ case KeyboardCodes.LeftArrow: {
+ event.preventDefault();
+ if (event.altKey) {
+ this.selectPreviousFlow();
+ } else {
+ this.closeIssue();
+ }
+ break;
+ }
+ case KeyboardCodes.RightArrow: {
+ event.preventDefault();
+ if (event.altKey) {
+ this.selectNextFlow();
+ } else {
+ this.openSelectedIssue();
+ }
+ break;
+ }
}
};
handleKeyUp = (event: KeyboardEvent) => {
- if (key.getScope() !== 'issues') {
- return;
- }
if (event.key === KeyboardKeys.Alt) {
this.setState(actions.disableLocationsNavigator);
}
};
handleOpenBulkChange = () => {
- key.setScope('issues-bulk-change');
this.setState({ bulkChangeModal: true });
};
handleCloseBulkChange = () => {
- key.setScope('issues');
this.setState({ bulkChangeModal: false });
};
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
-import key from 'keymaster';
import * as React from 'react';
import { searchIssues } from '../../../../api/issues';
import handleRequiredAuthentication from '../../../../helpers/handleRequiredAuthentication';
mockRawIssue,
mockRouter
} from '../../../../helpers/testMocks';
-import { KEYCODE_MAP, keydown, waitAndUpdate } from '../../../../helpers/testUtils';
+import { keydown, waitAndUpdate } from '../../../../helpers/testUtils';
import { ComponentQualifier } from '../../../../types/component';
import { ReferencedComponent } from '../../../../types/issues';
import { Issue, Paging } from '../../../../types/types';
jest.mock('../../../../helpers/handleRequiredAuthentication', () => jest.fn());
-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 = 'issues';
-
- key.getScope = () => scope;
- key.setScope = (newScope: string) => {
- scope = newScope;
- };
- key.deleteScope = jest.fn();
-
- return key;
-});
-
jest.mock('../../../../api/issues', () => ({
searchIssues: jest.fn().mockResolvedValue({ facets: [], issues: [] })
}));
const referencedComponent: ReferencedComponent = { key: 'foo-key', name: 'bar', uuid: 'foo-uuid' };
-const originalAddEventListener = window.addEventListener;
-const originalRemoveEventListener = window.removeEventListener;
-
beforeEach(() => {
- Object.defineProperty(window, 'addEventListener', {
- value: jest.fn()
- });
- Object.defineProperty(window, 'removeEventListener', {
- value: jest.fn()
- });
-
(searchIssues as jest.Mock).mockResolvedValue({
components: [referencedComponent],
effortTotal: 1,
});
afterEach(() => {
- Object.defineProperty(window, 'addEventListener', {
- value: originalAddEventListener
- });
- Object.defineProperty(window, 'removeEventListener', {
- value: originalRemoveEventListener
- });
-
jest.clearAllMocks();
(searchIssues as jest.Mock).mockReset();
});
it('should correctly bind key events for issue navigation', async () => {
const push = jest.fn();
+ const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
const wrapper = shallowRender({ router: mockRouter({ push }) });
await waitAndUpdate(wrapper);
+ expect(addEventListenerSpy).toBeCalledTimes(2);
+
expect(wrapper.state('selected')).toBe(ISSUES[0].key);
keydown({ code: KeyboardCodes.DownArrow });
keydown({ code: KeyboardCodes.LeftArrow });
expect(push).toBeCalledTimes(2);
- expect(window.addEventListener).toBeCalledTimes(2);
+
+ addEventListenerSpy.mockReset();
});
it('should correctly clean up on unmount', () => {
+ const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener');
const wrapper = shallowRender();
wrapper.unmount();
- expect(key.deleteScope).toBeCalled();
expect(removeSideBarClass).toBeCalled();
expect(removeWhitePageClass).toBeCalled();
- expect(window.removeEventListener).toBeCalledTimes(2);
+ expect(removeEventListenerSpy).toBeCalledTimes(2);
+
+ removeEventListenerSpy.mockReset();
});
it('should be able to bulk change specific issues', async () => {
jest.resetAllMocks();
});
- afterEach(() => {
- key.setScope('issues');
- });
-
it('should handle alt', () => {
instance.handleKeyDown(mockEvent({ key: KeyboardKeys.Alt }));
expect(instance.setState).toHaveBeenCalledWith(enableLocationsNavigator);
instance.handleKeyDown(mockEvent({ altKey: true, code: KeyboardCodes.RightArrow }));
expect(instance.setState).toHaveBeenCalledWith(selectNextFlow);
});
- it('should ignore different scopes', () => {
- key.setScope('notissues');
+ it('should ignore if modal is open', () => {
+ wrapper.setState({ bulkChangeModal: true });
instance.handleKeyDown(mockEvent({ key: KeyboardKeys.Alt }));
expect(instance.setState).not.toHaveBeenCalled();
});
jest.resetAllMocks();
});
- afterEach(() => {
- key.setScope('issues');
- });
-
it('should handle alt', () => {
instance.handleKeyUp(mockEvent({ key: KeyboardKeys.Alt }));
expect(instance.setState).toHaveBeenCalledWith(disableLocationsNavigator);
});
-
- it('should ignore different scopes', () => {
- key.setScope('notissues');
- instance.handleKeyUp(mockEvent({ key: KeyboardKeys.Alt }));
- expect(instance.setState).not.toHaveBeenCalled();
- });
});
it('should fetch more issues', async () => {
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { Location } from 'history';
-import key from 'keymaster';
import { flatMap, range } from 'lodash';
import * as React from 'react';
import { getMeasures } from '../../api/measures';
import './styles.css';
import { getLocations, SECURITY_STANDARDS } from './utils';
-const HOTSPOT_KEYMASTER_SCOPE = 'hotspots-list';
const PAGE_SIZE = 500;
interface DispatchProps {
fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => void;
}
registerKeyboardEvents() {
- key.setScope(HOTSPOT_KEYMASTER_SCOPE);
- key('up', HOTSPOT_KEYMASTER_SCOPE, () => {
- this.selectNeighboringHotspot(-1);
- return false;
- });
- key('down', HOTSPOT_KEYMASTER_SCOPE, () => {
- this.selectNeighboringHotspot(+1);
- return false;
- });
- window.addEventListener('keydown', this.handleKeyDown);
+ document.addEventListener('keydown', this.handleKeyDown);
}
handleKeyDown = (event: KeyboardEvent) => {
if (event.key === KeyboardKeys.Alt) {
event.preventDefault();
- } else if (event.code === KeyboardCodes.DownArrow && event.altKey) {
- event.preventDefault();
- this.selectNextLocation();
- } else if (event.code === KeyboardCodes.UpArrow && event.altKey) {
- event.preventDefault();
- this.selectPreviousLocation();
+ return;
+ }
+
+ switch (event.code) {
+ case KeyboardCodes.DownArrow: {
+ event.preventDefault();
+ if (event.altKey) {
+ this.selectNextLocation();
+ } else {
+ this.selectNeighboringHotspot(+1);
+ }
+ break;
+ }
+ case KeyboardCodes.UpArrow: {
+ event.preventDefault();
+ if (event.altKey) {
+ this.selectPreviousLocation();
+ } else {
+ this.selectNeighboringHotspot(-1);
+ }
+ break;
+ }
}
};
};
unregisterKeyboardEvents() {
- key.deleteScope(HOTSPOT_KEYMASTER_SCOPE);
- window.removeEventListener('keydown', this.handleKeyDown);
+ document.removeEventListener('keydown', this.handleKeyDown);
}
constructFiltersFromProps(
mockLoggedInUser,
mockRouter
} from '../../../helpers/testMocks';
-import { KEYCODE_MAP, waitAndUpdate } from '../../../helpers/testUtils';
+import { waitAndUpdate } from '../../../helpers/testUtils';
import { SecurityStandard } from '../../../types/security';
import {
HotspotResolution,
import { SecurityHotspotsApp } from '../SecurityHotspotsApp';
import SecurityHotspotsAppRenderer from '../SecurityHotspotsAppRenderer';
-const originalAddEventListener = window.addEventListener;
-const originalRemoveEventListener = window.removeEventListener;
-
beforeEach(() => {
- Object.defineProperty(window, 'addEventListener', {
- value: jest.fn()
- });
- Object.defineProperty(window, 'removeEventListener', {
- value: jest.fn()
- });
jest.clearAllMocks();
});
-afterEach(() => {
- Object.defineProperty(window, 'addEventListener', {
- value: originalAddEventListener
- });
- Object.defineProperty(window, 'removeEventListener', {
- value: originalRemoveEventListener
- });
-});
-
jest.mock('../../../api/measures', () => ({
getMeasures: jest.fn().mockResolvedValue([])
}));
scrollToElement: jest.fn()
}));
-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 = 'hotspots-list';
-
- key.getScope = () => scope;
- key.setScope = (newScope: string) => {
- scope = newScope;
- };
- key.deleteScope = jest.fn();
-
- return key;
-});
-
const branch = mockBranch();
it('should render correctly', () => {
}
const noop = () => undefined;
-const SCROLL_INTERVAL_DELAY = 10;
const SCROLL_DELAY = 100;
const EXPAND_ANIMATION_SPEED = 200;
};
}
-function scrollToFollowAnimation(
- scrollableRef: React.RefObject<HTMLDivElement>,
- targetHeight: number
-) {
- const scrollable = scrollableRef.current;
- if (scrollable) {
- const handler = setInterval(() => {
- scrollable.scrollTo({ top: targetHeight, behavior: 'smooth' });
- }, SCROLL_INTERVAL_DELAY);
- setTimeout(() => {
- clearInterval(handler);
- }, EXPAND_ANIMATION_SPEED);
- }
-}
-
/* Exported for testing */
export async function animateExpansion(
scrollableRef: React.RefObject<HTMLDivElement>,
// False positive:
// eslint-disable-next-line require-atomic-updates
wrapper.style.maxHeight = `${targetHeight}px`;
- scrollToFollowAnimation(scrollableRef, targetHeight);
}
// after the animation is done, clear the applied styles
renderAdditionalChildInLine={renderHotspotBoxInLine}
renderDuplicationPopup={noop}
snippet={sourceLines}
+ scroll={getScrollHandler(scrollableRef)}
/>
)}
</DeferredSpinner>
expect(onExpandBlock).toBeCalledWith('down');
expect(snippet.style.maxHeight).toBe('112px');
- jest.advanceTimersByTime(250);
- expect(scrollableNode.scrollTo).toBeCalled();
-
jest.useRealTimers();
});
});
openIssuesByLine={Object {}}
renderAdditionalChildInLine={[Function]}
renderDuplicationPopup={[Function]}
+ scroll={[Function]}
snippet={
Array [
Object {