diff --git a/build/jest/envSetup.js b/build/jest/envSetup.js index ba566446e..55e273e4b 100644 --- a/build/jest/envSetup.js +++ b/build/jest/envSetup.js @@ -1,5 +1,5 @@ import '@testing-library/jest-dom'; -import Adapter from 'enzyme-adapter-react-16'; +import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; import Enzyme, { mount, shallow } from 'enzyme'; expect.extend({ diff --git a/package.json b/package.json index bf5904ce1..c21213e85 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,11 @@ "@types/classnames": "^2.2.11", "@types/enzyme": "^3.10.8", "@types/lodash": "^4.14.149", - "@types/react": "^16.9.0", - "@types/react-dom": "^16.9.0", + "@types/react": "^17.0.2", + "@types/react-dom": "^17.0.1", "@typescript-eslint/eslint-plugin": "^2.13.0", "@typescript-eslint/parser": "^2.13.0", + "@wojtekmaj/enzyme-adapter-react-17": "^0.4.1", "autoprefixer": "^9.6.1", "axios": "^0.19.0", "babel-eslint": "^10.0.3", @@ -44,7 +45,6 @@ "cssnano-cli": "^1.0.5", "cypress": "^3.8.1", "enzyme": "^3.10.0", - "enzyme-adapter-react-16": "^1.15.1", "eslint": "^6.7.2", "eslint-config-airbnb": "^18.0.1", "eslint-config-prettier": "^6.7.0", @@ -82,11 +82,11 @@ "postcss-sass": "^0.4.1", "prettier": "^1.19.1", "raw-loader": "^3.1.0", - "react": "^16.9.0", - "react-dom": "^16.9.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", "react-intl": "^2.9.0", "react-tether": "^1.0.0", - "react-virtualized": "^9.13.0", + "react-virtualized": "^9.22.3", "sass-loader": "^7.1.0", "sinon": "^9.0.3", "sinon-chai": "^3.5.0", diff --git a/src/lib/viewers/controls/annotations/__tests__/AnnotationsControls-test.tsx b/src/lib/viewers/controls/annotations/__tests__/AnnotationsControls-test.tsx index 44d030ffc..c5c096c32 100644 --- a/src/lib/viewers/controls/annotations/__tests__/AnnotationsControls-test.tsx +++ b/src/lib/viewers/controls/annotations/__tests__/AnnotationsControls-test.tsx @@ -15,23 +15,33 @@ describe('AnnotationsControls', () => { }); describe('lifecycle', () => { + let unmount = (): void => { + // placeholder + }; + + beforeEach(() => { + jest.spyOn(React, 'useEffect').mockImplementation(cb => { + unmount = cb() as () => void; // Enzyme unmount helper does not currently invoke useEffect cleanup + }); + }); + test('should add and remove its event handlers on mount and unmount', () => { - const wrapper = getWrapper({ + getWrapper({ annotationMode: AnnotationMode.REGION, hasHighlight: true, hasRegion: true, }); expect(document.addEventListener).toBeCalledWith('keydown', expect.any(Function)); - wrapper.unmount(); + unmount(); expect(document.removeEventListener).toBeCalledWith('keydown', expect.any(Function)); }); test('should not add a handler if the annotation mode is set to none', () => { - const wrapper = getWrapper({ hasHighlight: true, hasRegion: true }); + getWrapper({ hasHighlight: true, hasRegion: true }); expect(document.addEventListener).not.toBeCalledWith('keydown', expect.any(Function)); - wrapper.unmount(); + unmount(); expect(document.removeEventListener).toBeCalledWith('keydown', expect.any(Function)); }); }); diff --git a/src/lib/viewers/controls/controls-layer/__tests__/ControlsLayer-test.tsx b/src/lib/viewers/controls/controls-layer/__tests__/ControlsLayer-test.tsx index 7018b6555..516a43994 100644 --- a/src/lib/viewers/controls/controls-layer/__tests__/ControlsLayer-test.tsx +++ b/src/lib/viewers/controls/controls-layer/__tests__/ControlsLayer-test.tsx @@ -90,14 +90,21 @@ describe('ControlsLayer', () => { describe('unmount', () => { test('should destroy any existing hide timeout', () => { - jest.spyOn(window, 'clearTimeout'); - - const onMount = (helpers: Helpers): void => { - helpers.hide(); // Kick off the hide timeout + let unmount = (): void => { + // placeholder }; - const wrapper = getWrapper({ onMount }); - wrapper.unmount(); + jest.spyOn(window, 'clearTimeout'); + jest.spyOn(React, 'useEffect').mockImplementation(cb => { + unmount = cb() as () => void; // Enzyme unmount helper does not currently invoke useEffect cleanup + }); + + getWrapper({ + onMount: (helpers: Helpers): void => { + helpers.hide(); // Kick off the hide timeout + }, + }); + unmount(); expect(window.clearTimeout).toBeCalledTimes(2); // Once on hide, once on unmount }); diff --git a/src/lib/viewers/controls/hooks/__tests__/usePreventKey-test.tsx b/src/lib/viewers/controls/hooks/__tests__/usePreventKey-test.tsx deleted file mode 100644 index f7bdd51f2..000000000 --- a/src/lib/viewers/controls/hooks/__tests__/usePreventKey-test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; -import usePreventKey from '../usePreventKey'; - -describe('usePreventKey', () => { - function TestComponent({ keys }: { keys?: string[] }): JSX.Element { - const ref = React.useRef(null); - usePreventKey(ref, keys); - return
; - } - - const getElement = (wrapper: ReactWrapper): HTMLDivElement => wrapper.childAt(0).getDOMNode(); - const getEvent = (options = {}): KeyboardEvent => { - const event = new KeyboardEvent('keydown', options); - event.stopPropagation = jest.fn(); - return event; - }; - const getWrapper = (props = {}): ReactWrapper => mount(); - - test('should stop propagation of a matching event triggered on the provided element', () => { - const wrapper = getWrapper({ keys: ['Enter'] }); - const element = getElement(wrapper); - const enterEvent = getEvent({ key: 'Enter' }); - const escapeEvent = getEvent({ key: 'Escape' }); - - element.dispatchEvent(enterEvent); - expect(enterEvent.stopPropagation).toBeCalled(); - - element.dispatchEvent(escapeEvent); - expect(escapeEvent.stopPropagation).not.toBeCalled(); - }); -}); diff --git a/src/lib/viewers/controls/hooks/index.ts b/src/lib/viewers/controls/hooks/index.ts index 8b27d74f0..e9c736cef 100644 --- a/src/lib/viewers/controls/hooks/index.ts +++ b/src/lib/viewers/controls/hooks/index.ts @@ -1,3 +1,2 @@ export { default as useAttention } from './useAttention'; export { default as useFullscreen } from './useFullscreen'; -export { default as usePreventKey } from './usePreventKey'; diff --git a/src/lib/viewers/controls/hooks/usePreventKey.ts b/src/lib/viewers/controls/hooks/usePreventKey.ts deleted file mode 100644 index 61f63806e..000000000 --- a/src/lib/viewers/controls/hooks/usePreventKey.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; -import { decodeKeydown } from '../../../util'; - -export default function usePreventKey(ref: React.RefObject, keys: string[] = []): void { - React.useEffect(() => { - const { current: element } = ref; - - const handleKeydown = (event: KeyboardEvent): void => { - const key = decodeKeydown(event); - - if (keys.includes(key)) { - event.stopPropagation(); // Prevents global key handling. Can be simplified with React v17. - } - }; - - if (element) { - element.addEventListener('keydown', handleKeydown); - } - - return (): void => { - if (element) { - element.removeEventListener('keydown', handleKeydown); - } - }; - }, [keys, ref]); -} diff --git a/src/lib/viewers/controls/media/MediaToggle.tsx b/src/lib/viewers/controls/media/MediaToggle.tsx index c5325e557..857d4819a 100644 --- a/src/lib/viewers/controls/media/MediaToggle.tsx +++ b/src/lib/viewers/controls/media/MediaToggle.tsx @@ -1,12 +1,16 @@ import React from 'react'; -import usePreventKey from '../hooks/usePreventKey'; +import { decodeKeydown } from '../../../util'; export type Props = React.ButtonHTMLAttributes; export default function MediaToggle(props: Props): JSX.Element { - const buttonElRef = React.useRef(null); + const handleKeydown = (event: React.KeyboardEvent): void => { + const key = decodeKeydown(event); - usePreventKey(buttonElRef, ['Enter', 'Space']); + if (key === 'Enter' || key === 'Space') { + event.stopPropagation(); + } + }; - return