diff --git a/__tests__/src/components/SearchHit.test.js b/__tests__/src/components/SearchHit.test.js index 9fd9fdbf7b..7ba0eac5e8 100644 --- a/__tests__/src/components/SearchHit.test.js +++ b/__tests__/src/components/SearchHit.test.js @@ -82,6 +82,7 @@ describe('SearchHit', () => { wrapper.setProps({ selected: true }); expect(announcer).toHaveBeenCalledWith( 'pagination The Canvas Label The Annotation Label Light up the moose , and start the chai', + 'polite', ); }); diff --git a/__tests__/src/components/SearchResults.test.js b/__tests__/src/components/SearchResults.test.js index ce0603bc1d..73f0c8baa0 100644 --- a/__tests__/src/components/SearchResults.test.js +++ b/__tests__/src/components/SearchResults.test.js @@ -30,18 +30,16 @@ describe('SearchResults', () => { it('renders a SearchHit for each hit', () => { const selectContentSearchAnnotation = jest.fn(); const wrapper = createWrapper({ selectContentSearchAnnotation }); - const searchHits = wrapper.find('LiveMessenger').props().children({}); - + const searchHits = wrapper.find('Connect(WithStyles(WithPlugins(SearchHit)))'); expect(searchHits.length).toEqual(1); - expect(searchHits[0].type.displayName).toEqual('Connect(WithStyles(WithPlugins(SearchHit)))'); - expect(searchHits[0].props.index).toEqual(0); + expect(searchHits.first().props().index).toEqual(0); }); it('can focus on a single item', () => { const wrapper = createWrapper({}); - const searchHits = wrapper.find('LiveMessenger').props().children({}); + const searchHits = wrapper.find('Connect(WithStyles(WithPlugins(SearchHit)))'); - searchHits[0].props.showDetails(); + searchHits.first().props().showDetails(); expect(wrapper.state().focused).toEqual(true); }); @@ -54,14 +52,6 @@ describe('SearchResults', () => { expect(wrapper.state().focused).toEqual(false); }); - it('passes announcePolite function to the SearchHits', () => { - const announcePolite = jest.fn(); - const wrapper = createWrapper({}); - const searchHits = wrapper.find('LiveMessenger').props().children({ announcePolite }); - - expect(searchHits[0].props.announcer).toEqual(announcePolite); - }); - describe('annotation-only search results', () => { it('renders a SearchHit for each annotation', () => { const wrapper = createWrapper({ @@ -69,13 +59,13 @@ describe('SearchResults', () => { searchHits: [], }); - const searchHits = wrapper.find('LiveMessenger').props().children({}); + const searchHits = wrapper.find('Connect(WithStyles(WithPlugins(SearchHit)))'); expect(searchHits.length).toEqual(2); - expect(searchHits[0].props.index).toEqual(0); - expect(searchHits[0].props.annotationId).toEqual('x'); + expect(searchHits.get(0).props.index).toEqual(0); + expect(searchHits.get(0).props.annotationId).toEqual('x'); - expect(searchHits[1].props.index).toEqual(1); - expect(searchHits[1].props.annotationId).toEqual('y'); + expect(searchHits.get(1).props.index).toEqual(1); + expect(searchHits.get(1).props.annotationId).toEqual('y'); }); }); diff --git a/__tests__/src/components/Workspace.test.js b/__tests__/src/components/Workspace.test.js index fc71c76c83..2a9e50cfc0 100644 --- a/__tests__/src/components/Workspace.test.js +++ b/__tests__/src/components/Workspace.test.js @@ -1,6 +1,5 @@ import { shallow } from 'enzyme'; import Typography from '@material-ui/core/Typography'; -import WorkspaceMosaic from '../../../src/containers/WorkspaceMosaic'; import WorkspaceElastic from '../../../src/containers/WorkspaceElastic'; import Window from '../../../src/containers/Window'; import { Workspace } from '../../../src/components/Workspace'; @@ -17,7 +16,7 @@ function createWrapper(props) { isWorkspaceControlPanelVisible windowIds={['1', '2']} workspaceId="foo" - workspaceType="mosaic" + workspaceType="elastic" t={k => k} {...props} />, @@ -39,20 +38,6 @@ describe('Workspace', () => { )).toBe(true); }); }); - describe('if workspace type is mosaic', () => { - it('should render properly', () => { - const wrapper = createWrapper(); - - expect(wrapper.matchesElement( - -
- miradorViewer - -
-
, - )).toBe(true); - }); - }); describe('if workspace type is unknown', () => { it('should render components as list', () => { const wrapper = createWrapper({ workspaceType: 'bubu' }); diff --git a/__tests__/src/components/WorkspaceMosaic.test.js b/__tests__/src/components/WorkspaceMosaic.test.js deleted file mode 100644 index 9fde83e675..0000000000 --- a/__tests__/src/components/WorkspaceMosaic.test.js +++ /dev/null @@ -1,133 +0,0 @@ -import { shallow } from 'enzyme'; -import { MosaicWithoutDragDropContext } from 'react-mosaic-component'; -import MosaicRenderPreview from '../../../src/containers/MosaicRenderPreview'; -import { WorkspaceMosaic } from '../../../src/components/WorkspaceMosaic'; - -/** create wrapper */ -function createWrapper(props) { - return shallow( - {}} - {...props} - />, - ); -} - -describe('WorkspaceMosaic', () => { - const windowIds = ['1', '2']; - let wrapper; - beforeEach(() => { - wrapper = createWrapper({ windowIds }); - }); - it('should render properly with an initialValue', () => { - expect(wrapper.find(MosaicWithoutDragDropContext).length).toEqual(1); - expect(wrapper.find(MosaicWithoutDragDropContext).prop('initialValue')).toEqual({ - direction: 'row', first: '1', second: '2', - }); - }); - describe('componentDidUpdate', () => { - it('updates the workspace layout when windows change', () => { - const updateWorkspaceMosaicLayout = jest.fn(); - wrapper = createWrapper({ - updateWorkspaceMosaicLayout, - windowIds, - }); - - wrapper.setProps({ windowIds: [...windowIds, '3'] }); - - expect(updateWorkspaceMosaicLayout).toHaveBeenCalled(); - }); - it('updates the workspace layout when windows are removed', () => { - const updateWorkspaceMosaicLayout = jest.fn(); - wrapper = createWrapper({ - layout: { first: 1, second: 2 }, - updateWorkspaceMosaicLayout, - windowIds, - }); - wrapper.instance().windowPaths = { 2: ['second'] }; - wrapper.setProps({ windowIds: [1] }); - expect(updateWorkspaceMosaicLayout).toHaveBeenLastCalledWith(1); - }); - it('when no windows remain', () => { - const updateWorkspaceMosaicLayout = jest.fn(); - wrapper = createWrapper({ - updateWorkspaceMosaicLayout, - windowIds, - }); - wrapper.setProps({ windowIds: [] }); - expect(updateWorkspaceMosaicLayout).toHaveBeenLastCalledWith(null); - }); - it('when the new and old layouts are the same', () => { - const updateWorkspaceMosaicLayout = jest.fn(); - wrapper = createWrapper({ - layout: { first: 1, second: 2 }, - updateWorkspaceMosaicLayout, - windowIds, - }); - wrapper.setProps({ layout: { first: 1, second: 2 }, windowIds }); - expect(updateWorkspaceMosaicLayout).toHaveBeenCalledTimes(1); - }); - }); - describe('bookkeepPath', () => { - it('as windows are rendered keeps a reference to their path in binary tree', () => { - wrapper.instance().tileRenderer('1', 'foo'); - expect(wrapper.instance().windowPaths).toEqual({ 1: 'foo' }); - }); - }); - describe('determineWorkspaceLayout', () => { - it('when window ids do not match workspace layout', () => { - wrapper = createWrapper({ layout: {}, windowIds }); - expect(wrapper.instance().determineWorkspaceLayout()).toMatchObject({ - direction: 'row', first: '1', second: '2', - }); - }); - it('by default use workspace.layout', () => { - wrapper = createWrapper({ layout: {}, windowIds: ['foo'] }); - expect(wrapper.instance().determineWorkspaceLayout()).toEqual('foo'); - }); - it('generates a new layout if windows do not match current layout', () => { - wrapper = createWrapper({ layout: { first: 'foo', second: 'bark' }, windowIds: ['foo'] }); - expect(wrapper.instance().determineWorkspaceLayout()).toEqual('foo'); - }); - it('when window ids match workspace layout', () => { - wrapper = createWrapper({ layout: {}, windowIds: ['foo'] }); - expect(wrapper.instance().determineWorkspaceLayout()).toBe('foo'); - }); - }); - describe('tileRenderer', () => { - it('when window is available', () => { - const renderedTile = wrapper.instance().tileRenderer('1', 'foo'); - expect(renderedTile).not.toBeNull(); - expect(shallow(renderedTile).find('DropTarget(DragSource(InternalMosaicWindow))').length).toEqual(1); - expect(shallow(renderedTile).props()).toEqual(expect.objectContaining({ - additionalControls: [], - path: 'foo', - toolbarControls: [], - })); - - expect(shallow(shallow(renderedTile).props().renderPreview({ windowId: 1 })).matchesElement( -
- -
, - )).toBe(true); - }); - it('when window is not available', () => { - expect(wrapper.instance().tileRenderer('bar')).toBeNull(); - }); - }); - describe('mosaicChange', () => { - it('calls the provided prop to update layout', () => { - const updateWorkspaceMosaicLayout = jest.fn(); - wrapper = createWrapper({ - updateWorkspaceMosaicLayout, - windowIds, - }); - - wrapper.instance().mosaicChange(); - expect(updateWorkspaceMosaicLayout).toBeCalled(); - }); - }); -}); diff --git a/__tests__/src/lib/MosaicLayout.test.js b/__tests__/src/lib/MosaicLayout.test.js deleted file mode 100644 index a364e77393..0000000000 --- a/__tests__/src/lib/MosaicLayout.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import MosaicLayout from '../../../src/lib/MosaicLayout'; - -describe('MosaicLayout', () => { - describe('constructor', () => { - it('sets layout', () => { - expect(new MosaicLayout('foo').layout).toEqual('foo'); - }); - }); - describe('addWindows', () => { - let instance; - beforeEach(() => { - instance = new MosaicLayout('foo'); - }); - it('case 1 window: adds to the top right', () => { - expect(instance.layout).toEqual('foo'); - instance.addWindows(['bar']); - expect(instance.layout).toEqual({ - direction: 'row', - first: 'foo', - second: 'bar', - }); - }); - it('case 3 windows: adds to the top right', () => { - expect(instance.layout).toEqual('foo'); - instance.addWindows(['bar', 'bat', 'bark']); - expect(instance.layout).toEqual({ - direction: 'row', - first: 'foo', - second: { - direction: 'column', - first: { - direction: 'row', - first: 'bat', - second: 'bark', - }, - second: 'bar', - }, - }); - }); - }); - describe('removeWindows', () => { - let instance; - beforeEach(() => { - instance = new MosaicLayout({ first: 'foo', second: 'bar' }); - }); - it('case 1 window: returns a single window', () => { - instance.removeWindows(['bar'], { bar: ['second'] }); - expect(instance.layout).toEqual('foo'); - }); - }); -}); diff --git a/jest.json b/jest.json index 11e5bf3bf4..c0363c0df1 100644 --- a/jest.json +++ b/jest.json @@ -11,6 +11,9 @@ "\\.s?css$": "/__mocks__/css.js", "^uuid$": "uuid" }, + "transformIgnorePatterns": [ + "/node_modules/(?!@react-dnd|react-dnd|dnd-core|react-dnd-html5-backend|dnd-multi-backend|rdndmb-html5-to-touch)" + ], "setupFiles": [ "/setupJest.js" ], diff --git a/package.json b/package.json index b0fe3f6342..bc710ec317 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.53", + "@react-aria/live-announcer": "^3.1.2", "classnames": "^2.2.6", "clsx": "^1.0.4", "deepmerge": "^4.2.2", @@ -46,21 +47,21 @@ "jss-rtl": "^0.3.0", "lodash": "^4.17.11", "manifesto.js": "^4.2.0", + "mirador-mosaic": "^0.0.1", "normalize-url": "^4.5.0", "openseadragon": "^2.4.2 || ^3.0.0 || ^4.0.0", "prop-types": "^15.6.2", + "rdndmb-html5-to-touch": "^8.0.0", "re-reselect": "^4.0.0", - "react-aria-live": "^2.0.5", "react-copy-to-clipboard": "^5.0.1", - "react-dnd": "^10.0.2", - "react-dnd-html5-backend": "^10.0.2", - "react-dnd-multi-backend": "^5.0.0", - "react-dnd-touch-backend": "^10.0.2", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dnd-multi-backend": "^8.0.0", + "react-dnd-touch-backend": "^16.0.1", "react-full-screen": "^1.1.1", "react-i18next": "^11.7.0 || ^12.0.0", "react-image": "^4.0.1", "react-intersection-observer": "^9.0.0", - "react-mosaic-component": "^4.0.1", "react-redux": "^7.1.0 || ^8.0.0", "react-resize-observer": "^1.1.1", "react-rnd": "^10.1", @@ -87,6 +88,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/parser": "^5.15.0", + "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", "babel-jest": "^29.3.1", "babel-loader": "^9.1.0", "babel-plugin-lodash": "^3.3.4", @@ -96,7 +98,6 @@ "chalk": "^4.1.0", "core-js": "^3.21.1", "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.0", "eslint": "^8.11.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-react-app": "^7.0.0", @@ -114,8 +115,8 @@ "jsdom": "^21.0.0", "puppeteer": "^19.0.0", "raf": "^3.4.1", - "react": "^16.14.0", - "react-dom": "^16.14.0", + "react": "^17.0.0", + "react-dom": "^17.0.0", "react-refresh": "^0.14.0", "redux-mock-store": "^1.5.1", "redux-saga-test-plan": "^4.0.0-rc.3", @@ -125,7 +126,12 @@ "webpack-dev-server": "^4.7.4" }, "peerDependencies": { - "react": "^16.14.0", - "react-dom": "^16.14.0" + "react": "^17.0.0", + "react-dom": "^17.0.0" + }, + "overrides": { + "babel-plugin-lodash": { + "@babel/types": "~7.20.0" + } } } diff --git a/setupJest.js b/setupJest.js index 23d77f5f6a..4ee5ca971b 100644 --- a/setupJest.js +++ b/setupJest.js @@ -4,7 +4,7 @@ import { JSDOM } from 'jsdom'; // eslint-disable-line import/no-extraneous-depen import raf from 'raf'; // eslint-disable-line import/no-extraneous-dependencies import fetchMock from 'jest-fetch-mock'; // eslint-disable-line import/no-extraneous-dependencies import Enzyme from 'enzyme'; // eslint-disable-line import/no-extraneous-dependencies -import Adapter from 'enzyme-adapter-react-16'; // eslint-disable-line import/no-extraneous-dependencies +import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; // eslint-disable-line import/no-extraneous-dependencies const jsdom = new JSDOM('
', { url: 'https://localhost' }); const { window } = jsdom; diff --git a/src/components/AppProviders.js b/src/components/AppProviders.js index aab7a26ed7..1ef4cf2b29 100644 --- a/src/components/AppProviders.js +++ b/src/components/AppProviders.js @@ -2,13 +2,12 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; import { FullScreen, useFullScreenHandle } from 'react-full-screen'; import { I18nextProvider } from 'react-i18next'; -import { LiveAnnouncer } from 'react-aria-live'; import { ThemeProvider, StylesProvider, createTheme, jssPreset, createGenerateClassName, } from '@material-ui/core/styles'; import { DndContext, DndProvider } from 'react-dnd'; -import MultiBackend from 'react-dnd-multi-backend'; -import HTML5toTouch from 'react-dnd-multi-backend/dist/cjs/HTML5toTouch'; +import { MultiBackend } from 'react-dnd-multi-backend'; +import { HTML5toTouch } from 'rdndmb-html5-to-touch'; import { create } from 'jss'; import rtl from 'jss-rtl'; import createI18nInstance from '../i18n'; @@ -115,20 +114,18 @@ export class AppProviders extends Component { return ( - - + - - - {children} - - - - + + {children} + + + ); diff --git a/src/components/SearchHit.js b/src/components/SearchHit.js index 7feac2d974..70f4d2b640 100644 --- a/src/components/SearchHit.js +++ b/src/components/SearchHit.js @@ -55,17 +55,20 @@ export class SearchHit extends Component { const { annotation, annotationLabel, announcer, canvasLabel, hit, index, t, total, } = this.props; - if (!hit) return; + if (!hit || !announcer) return; const truncatedHit = new TruncatedHit(hit, annotation); - announcer([ - t('pagination', { current: index + 1, total }), - canvasLabel, - annotationLabel, - truncatedHit.before, - truncatedHit.match, - truncatedHit.after, - ].join(' ')); + announcer( + [ + t('pagination', { current: index + 1, total }), + canvasLabel, + annotationLabel, + truncatedHit.before, + truncatedHit.match, + truncatedHit.after, + ].join(' '), + 'polite', + ); } /** */ @@ -157,7 +160,7 @@ SearchHit.propTypes = { }), annotationId: PropTypes.string, annotationLabel: PropTypes.string, - announcer: PropTypes.func.isRequired, + announcer: PropTypes.func, canvasLabel: PropTypes.string, classes: PropTypes.objectOf(PropTypes.string), companionWindowId: PropTypes.string, @@ -186,6 +189,7 @@ SearchHit.defaultProps = { annotation: undefined, annotationId: undefined, annotationLabel: undefined, + announcer: undefined, canvasLabel: undefined, classes: {}, companionWindowId: undefined, diff --git a/src/components/SearchResults.js b/src/components/SearchResults.js index 912f908474..c38e5c082a 100644 --- a/src/components/SearchResults.js +++ b/src/components/SearchResults.js @@ -4,7 +4,7 @@ import Button from '@material-ui/core/Button'; import List from '@material-ui/core/List'; import Typography from '@material-ui/core/Typography'; import BackIcon from '@material-ui/icons/ArrowBackSharp'; -import { LiveMessenger } from 'react-aria-live'; +import { announce } from '@react-aria/live-announcer'; import SearchHit from '../containers/SearchHit'; import { ScrollTo } from './ScrollTo'; @@ -32,7 +32,7 @@ export class SearchResults extends Component { * Return SearchHits for every hit in the response * Return SearchHits for every annotation in the response if there are no hits */ - renderSearchHitsAndAnnotations(announcer) { + renderSearchHitsAndAnnotations() { const { companionWindowId, containerRef, @@ -47,7 +47,7 @@ export class SearchResults extends Component { if (searchHits.length === 0 && searchAnnotations.length > 0) { return searchAnnotations.map((anno, index) => ( ( )} - - {({ announcePolite }) => this.renderSearchHitsAndAnnotations(announcePolite) } - + { this.renderSearchHitsAndAnnotations() } { nextSearch && (