diff --git a/.eslintrc b/.eslintrc index 167c168efb7b04a6b7ee0581e4956c7a14a7bd25..596e1115f248c58964c343262d6dadec57910f3a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -46,7 +46,7 @@ "react/react-in-jsx-scope": "off", "react-hooks/exhaustive-deps": "error", "testing-library/render-result-naming-convention": "off", - "testing-library/no-render-in-setup": [ + "testing-library/no-render-in-lifecycle": [ "error", { "allowTestingFrameworkSetupHook": "beforeEach" diff --git a/__tests__/integration/mirador/invalid-api-response.test.js b/__tests__/integration/mirador/invalid-api-response.test.js index d0edd9de16a0702f5c1f3c14ad784f12079a09d4..a2f191d2a120f446abc4398a9506b75c6d5634ab 100644 --- a/__tests__/integration/mirador/invalid-api-response.test.js +++ b/__tests__/integration/mirador/invalid-api-response.test.js @@ -1,6 +1,7 @@ describe('Mirador Invalid API Response Handler Test', () => { /** */ async function fetchManifest(uri) { + await expect(page).toMatchElement('button'); await page.evaluate(() => { document.querySelector('#addBtn').click(); }); diff --git a/__tests__/src/actions/canvas.test.js b/__tests__/src/actions/canvas.test.js index 4d6d92e4ab76b7d5da3bac0921f12238a0b4ffdf..fead977eedba259af0b4d8b61e945762a6e115c0 100644 --- a/__tests__/src/actions/canvas.test.js +++ b/__tests__/src/actions/canvas.test.js @@ -1,5 +1,5 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; +import { thunk } from 'redux-thunk'; import * as actions from '../../../src/state/actions'; import ActionTypes from '../../../src/state/actions/action-types'; diff --git a/__tests__/src/components/App.test.js b/__tests__/src/components/App.test.js index 868c73ad9c59d9a71a54fdfaee10aad7a43fe2be..53a7236ff7b3019b20327945b9f25f67a49894a4 100644 --- a/__tests__/src/components/App.test.js +++ b/__tests__/src/components/App.test.js @@ -16,8 +16,6 @@ describe('App', () => { createWrapper(); expect(screen.queryByRole('main')).not.toBeInTheDocument(); - await screen.findByText('welcome'); - - expect(screen.getByRole('main')).toBeInTheDocument(); + expect(await screen.findByRole('main')).toBeInTheDocument(); }); }); diff --git a/__tests__/src/components/PrimaryWindow.test.js b/__tests__/src/components/PrimaryWindow.test.js index ad78ebf4b5cf39c5f7433721471429ded5b96237..1eef93a4f83fc09861041ea0bd58958c977e36bb 100644 --- a/__tests__/src/components/PrimaryWindow.test.js +++ b/__tests__/src/components/PrimaryWindow.test.js @@ -1,4 +1,4 @@ -import { render, screen } from 'test-utils'; +import { render, screen, waitFor } from 'test-utils'; import { PrimaryWindow } from '../../../src/components/PrimaryWindow'; /** create wrapper */ @@ -31,7 +31,9 @@ describe('PrimaryWindow', () => { it('should render <GalleryView> if fetching is complete and view is gallery', async () => { createWrapper({ isFetching: false, view: 'gallery' }); await screen.findByTestId('test-window'); - expect(document.querySelector('#xyz-gallery')).toBeInTheDocument(); // eslint-disable-line testing-library/no-node-access + await waitFor(() => { + expect(document.querySelector('#xyz-gallery')).toBeInTheDocument(); // eslint-disable-line testing-library/no-node-access + }); }); it('should render <CollectionDialog> and <SelectCollection> if manifest is collection and isCollectionDialogVisible', async () => { render(<div id="xyz" />); diff --git a/__tests__/src/lib/MiradorViewer.test.js b/__tests__/src/lib/MiradorViewer.test.js index d5e22093d19936c3e0a943eabb3e917ff2a60957..3103eea54361c22b7707aa8fd5f901688c39aca2 100644 --- a/__tests__/src/lib/MiradorViewer.test.js +++ b/__tests__/src/lib/MiradorViewer.test.js @@ -1,4 +1,4 @@ -import { render, screen } from 'test-utils'; +import { act, render, screen } from 'test-utils'; import MiradorViewer from '../../../src/lib/MiradorViewer'; jest.unmock('react-i18next'); @@ -23,7 +23,7 @@ describe('MiradorViewer', () => { expect(instance.store.dispatch).toBeDefined(); }); it('renders via ReactDOM', () => { - const instance = new MiradorViewer({ id: 'mirador' }); // eslint-disable-line no-unused-vars + act(() => { new MiradorViewer({ id: 'mirador' }); }); // eslint-disable-line no-new expect(screen.getByTestId('container')).not.toBeEmptyDOMElement(); }); @@ -137,9 +137,11 @@ describe('MiradorViewer', () => { describe('unmount', () => { it('unmounts via ReactDOM', () => { - const instance = new MiradorViewer({ id: 'mirador' }); + let instance; + + act(() => { instance = new MiradorViewer({ id: 'mirador' }); }); expect(screen.getByTestId('container')).not.toBeEmptyDOMElement(); - instance.unmount(); + act(() => { instance.unmount(); }); expect(screen.getByTestId('container')).toBeEmptyDOMElement(); }); }); diff --git a/__tests__/utils/test-utils.js b/__tests__/utils/test-utils.js index 7104dec6b038df0a91a8744b5378e22aeab848e9..2e30b405cb5d81590b3a50bd17911daace7811fe 100644 --- a/__tests__/utils/test-utils.js +++ b/__tests__/utils/test-utils.js @@ -2,7 +2,7 @@ import { Provider } from 'react-redux'; import { render } from '@testing-library/react'; import PropTypes from 'prop-types'; import { createStore, applyMiddleware } from 'redux'; -import thunkMiddleware from 'redux-thunk'; +import { thunk } from 'redux-thunk'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import createRootReducer from '../../src/state/reducers/rootReducer'; import settings from '../../src/config/settings'; @@ -18,7 +18,7 @@ function renderWithProviders( { preloadedState = {}, // Automatically create a store instance if no store was passed in - store = createStore(rootReducer, preloadedState, applyMiddleware(thunkMiddleware)), + store = createStore(rootReducer, preloadedState, applyMiddleware(thunk)), ...renderOptions } = {}, ) { diff --git a/package.json b/package.json index 5fc129601be086a358ecb6c5b017cc88e1941fae..fabab5cbbb76b2677bb572345c4900d3b2bbd0fe 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "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", + "re-reselect": "^5.0.0", "react-copy-to-clipboard": "^5.0.1", "react-dnd": "^16.0.0", "react-dnd-html5-backend": "^16.0.0", @@ -66,17 +66,16 @@ "react-image": "^4.0.1", "react-intersection-observer": "^9.0.0", "react-mosaic-component": "^6.0.0", - "react-redux": "^7.1.0 || ^8.0.0", + "react-redux": "^8.0.0 || ^9.0.0", "react-resize-observer": "^1.1.1", "react-rnd": "^10.1", "react-sizeme": "^2.6.7 || ^3.0.0", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.8.5", - "redux": "^4.0.5", - "redux-devtools-extension": "^2.13.2", + "redux": "^5.0.0", "redux-saga": "^1.1.3", - "redux-thunk": "^2.3.0", - "reselect": "^4.0.0", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.0", "stylis": "^4.3.0", "stylis-plugin-rtl": "^2.1.1", "url": "^0.11.0", @@ -95,10 +94,10 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@testing-library/dom": "^9.2.0", "@testing-library/jest-dom": "^6.1.5", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.4.3", - "@typescript-eslint/eslint-plugin": "^5.15.0", - "@typescript-eslint/parser": "^5.15.0", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", "babel-jest": "^29.3.1", "babel-loader": "^9.1.0", "babel-plugin-lodash": "^3.3.4", @@ -118,7 +117,7 @@ "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-testing-library": "^5.10.2", + "eslint-plugin-testing-library": "^6.2.0", "glob": "^10.3.0", "http-server": "^14.1.0", "jest": "^29.3.1", @@ -127,9 +126,9 @@ "jest-puppeteer": "^9.0.2", "jsdom": "^23.0.0", "puppeteer": "^21.0.0", - "react": "^17.0.0", + "react": "^18.0.0", "react-dnd-test-backend": "^16.0.1", - "react-dom": "^17.0.0", + "react-dom": "^18.0.0", "react-refresh": "^0.14.0", "redux-mock-store": "^1.5.1", "redux-saga-test-plan": "^4.0.0-rc.3", @@ -139,7 +138,7 @@ "webpack-dev-server": "^4.7.4" }, "peerDependencies": { - "react": "^17.0.0", - "react-dom": "^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" } } diff --git a/src/lib/MiradorViewer.js b/src/lib/MiradorViewer.js index c6fcf583e23f75e07bb8c9c6feac892c15973f13..8c9c23bb78c95dd7bd327121dfa5577a15c6a022 100644 --- a/src/lib/MiradorViewer.js +++ b/src/lib/MiradorViewer.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import HotApp from '../components/App'; import { @@ -20,10 +20,9 @@ class MiradorViewer { if (config.id) { this.container = document.getElementById(config.id); - config.id && ReactDOM.render( - this.render(), - this.container, - ); + this.root = createRoot(this.container); + + this.root.render(this.render()); } } @@ -42,7 +41,9 @@ class MiradorViewer { * Cleanup method to unmount Mirador from the dom */ unmount() { - this.container && ReactDOM.unmountComponentAtNode(this.container); + if (!this.root) return; + + this.root.unmount(); } } diff --git a/src/state/createStore.js b/src/state/createStore.js index c115859260c6db7cd5398b22b308bf9572cf5b32..b593f54e9b5a014b7b3d38916d088a656bb43a85 100644 --- a/src/state/createStore.js +++ b/src/state/createStore.js @@ -3,7 +3,7 @@ // state normalisation // (normalizer library) -import thunkMiddleware from 'redux-thunk'; +import { thunk } from 'redux-thunk'; import createSagaMiddleware from 'redux-saga'; import { combineReducers, createStore, applyMiddleware } from 'redux'; import { composeWithDevTools } from '@redux-devtools/extension'; @@ -27,7 +27,7 @@ function configureStore(pluginReducers, pluginSagas = []) { const store = createStore( rootReducer, composeWithDevTools( - applyMiddleware(thunkMiddleware, sagaMiddleware), + applyMiddleware(thunk, sagaMiddleware), ), ); diff --git a/src/state/selectors/manifests.js b/src/state/selectors/manifests.js index 496d8456216a94e999cce9b7940b7e2fe9d6cf59..aefe164ce328406d0dc19728b9feca7faa9bcacf 100644 --- a/src/state/selectors/manifests.js +++ b/src/state/selectors/manifests.js @@ -1,5 +1,5 @@ import { createSelector } from 'reselect'; -import createCachedSelector from 're-reselect'; +import { createCachedSelector } from 're-reselect'; import { PropertyValue, Utils, Resource } from 'manifesto.js'; import getThumbnail from '../../lib/ThumbnailFactory'; import asArray from '../../lib/asArray'; diff --git a/webpack.config.js b/webpack.config.js index f31af73184ed2f9dd047042324a2a217d45e07ff..6e3c1e838f5f3634cd6320ea9da8dac72b16bc17 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -45,10 +45,6 @@ const baseConfig = mode => ({ }), ], resolve: { - alias: { - 'react/jsx-dev-runtime': 'react/jsx-dev-runtime.js', - 'react/jsx-runtime': 'react/jsx-runtime.js', - }, extensions: ['.js'], }, });