From 93fbaf65c248207edeb4d939b54aff05691a401c Mon Sep 17 00:00:00 2001 From: Jeffrey Carl Faden Date: Wed, 10 May 2023 11:40:50 -0700 Subject: [PATCH 1/5] Upgrade to React 18, switch from Enzyme to React Testing Library --- package.json | 19 +- src/client.tsx | 33 +- .../AddUserForm/AddUserForm.test.js | 103 -- .../AddUserForm/AddUserForm.test.tsx | 107 ++ .../{AddUserForm.js => AddUserForm.tsx} | 34 +- .../ConfirmModal/ConfirmModal.test.js | 13 +- src/components/Layout/Layout.test.js | 90 +- .../RestaurantAddTagForm.test.js | 13 +- .../RestaurantVoteButton.test.js | 8 +- .../RestaurantVoteCount.test.js | 30 +- src/routes/team/home/Home.js | 4 +- src/routes/team/home/Home.test.js | 51 +- test/mocha-setup.js | 4 - test/setup.js | 11 +- test/test-utils.tsx | 12 + tsconfig.json | 1 + yarn.lock | 931 +++++++++++------- 17 files changed, 908 insertions(+), 556 deletions(-) delete mode 100644 src/components/AddUserForm/AddUserForm.test.js create mode 100644 src/components/AddUserForm/AddUserForm.test.tsx rename src/components/AddUserForm/{AddUserForm.js => AddUserForm.tsx} (78%) create mode 100644 test/test-utils.tsx diff --git a/package.json b/package.json index c838ff5e9..28f231872 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@babel/polyfill": "^7.0.0", "@googlemaps/js-api-loader": "^1.15.1", "@honeybadger-io/js": "^5.0.0", + "@popperjs/core": "^2.11.7", "@reduxjs/toolkit": "^1.9.2", "bcrypt": "^5.1.0", "body-parser": "^1.18.3", @@ -58,10 +59,10 @@ "pretty-error": "^3.0.4", "prop-types": "^15.8.1", "query-string": "^7.1.3", - "react": "^16.14.0", + "react": "^18.2.0", "react-autosuggest": "^10.0.2", "react-bootstrap": "^2.7.0", - "react-dom": "^16.14.0", + "react-dom": "^18.2.0", "react-flip-move": "^3.0.5", "react-flip-toolkit": "^7.0.17", "react-geosuggest": "^2.14.1", @@ -101,6 +102,9 @@ "@jedmao/redux-mock-store": "^3.0.5", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", "@redux-devtools/extension": "^3.2.5", + "@testing-library/dom": "^9.2.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.4.3", "@types/bcrypt": "^5.0.0", "@types/chai": "^4.3.4", "@types/compression": "^1.7.2", @@ -120,7 +124,8 @@ "@types/passport": "^1.0.11", "@types/passport-google-oauth20": "^2.0.11", "@types/passport-local": "^1.0.35", - "@types/react-dom": "16.9.8", + "@types/proxyquire": "^1.3.28", + "@types/react-dom": "^18.2.4", "@types/uuid": "^9.0.0", "@types/webpack-env": "^1.18.0", "@typescript-eslint/eslint-plugin": "^5.59.1", @@ -140,14 +145,13 @@ "babel-types": "^6.25.0", "browser-sync": "2.29.1", "chai": "4.3.7", + "chai-jsdom": "^0.2.3", "chokidar": "^3.5.3", "cross-env": "^5.0.1", "css-loader": "^6.7.3", "custom-event-polyfill": "^0.3.0", "cypress": "^12.5.1", "del": "^2.2.2", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.7", "es6-promise": "^4.1.0", "eslint": "^8.34.0", "eslint-config-airbnb": "^19.0.4", @@ -157,12 +161,15 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", "file-loader": "^6.2.0", "fork-ts-checker-webpack-plugin": "^7.3.0", "git-repository": "^0.1.4", "glob": "^7.1.3", + "global-jsdom": "^9.0.1", "husky": "^8.0.3", "identity-obj-proxy": "^3.0.0", + "jsdom": "^22.0.0", "json-loader": "^0.5.7", "lint-staged": "^13.2.2", "mkdirp": "^2.1.3", @@ -178,7 +185,7 @@ "react-dev-utils": "^12.0.1", "react-error-overlay": "^4.0.1", "react-refresh": "^0.14.0", - "react-test-renderer": "^16.14.0", + "react-test-renderer": "^18.2.0", "rimraf": "^2.6.2", "sass": "^1.58.0", "sass-loader": "^13.2.0", diff --git a/src/client.tsx b/src/client.tsx index 49f33119a..8fde89dd2 100644 --- a/src/client.tsx +++ b/src/client.tsx @@ -9,17 +9,17 @@ import 'whatwg-fetch'; import es6Promise from 'es6-promise'; -import React from 'react'; -import ReactDOM from 'react-dom'; +import React, { useEffect } from 'react'; +import { createRoot, hydrateRoot } from 'react-dom/client'; import queryString from 'query-string'; import { Action, createPath, Location } from 'history'; +import { ResolveContext } from 'universal-router'; import App from './components/App'; import createFetch from './createFetch'; import configureStore from './store/configureStore'; import history from './history'; import { updateMeta } from './DOMUtils'; import routerCreator from './router'; -import { ResolveContext } from 'universal-router'; import { Style } from '../global'; es6Promise.polyfill(); @@ -82,13 +82,15 @@ const scrollPositionsHistory: {[index: string]: { scrollX: number, scrollY: numb let routes; if (subdomain) { - routes = require('./routes/team').default; // eslint-disable-line global-require + routes = require('./routes/team').default; // eslint-disable-line global-require, @typescript-eslint/no-var-requires } else { - routes = require('./routes/main').default; // eslint-disable-line global-require + routes = require('./routes/main').default; // eslint-disable-line global-require, @typescript-eslint/no-var-requires } const router = routerCreator(routes); +const root = createRoot(container!); + // Re-render the app when window.location changes const onLocationChange = async ({ action, location }: { action?: Action, location: Location }) => { // Remember the latest scroll position for the previous location @@ -129,11 +131,8 @@ const onLocationChange = async ({ action, location }: { action?: Action, locatio return; } - const renderReactApp = isInitialRender ? ReactDOM.hydrate : ReactDOM.render; - renderReactApp( - {route.component}, - container, - () => { + const AppWithCallbackAfterRender = () => { + useEffect(() => { if (isInitialRender) { // Switch off the native scroll restoration behavior and handle it manually // https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration @@ -182,8 +181,16 @@ const onLocationChange = async ({ action, location }: { action?: Action, locatio if (window.ga) { window.ga('send', 'pageview', createPath(location)); } - } - ); + }); + + return {route.component}; + }; + + if (isInitialRender) { + hydrateRoot(container!, ); + } else { + root.render(); + } } catch (error) { if (__DEV__) { throw error; @@ -197,7 +204,7 @@ const onLocationChange = async ({ action, location }: { action?: Action, locatio window.location.reload(); } } -} +}; // Handle client-side navigation by using HTML5 History API // For more information visit https://github.com/mjackson/history#readme diff --git a/src/components/AddUserForm/AddUserForm.test.js b/src/components/AddUserForm/AddUserForm.test.js deleted file mode 100644 index 3a209016f..000000000 --- a/src/components/AddUserForm/AddUserForm.test.js +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-env mocha */ -/* eslint-disable no-unused-expressions */ -import React from 'react'; -import { expect } from 'chai'; -import { shallow } from 'enzyme'; -import proxyquire from 'proxyquire'; -import PropTypes from 'prop-types'; - -const proxyquireStrict = proxyquire.noCallThru(); - -const AddUserForm = proxyquireStrict('./AddUserForm', { - 'react-intl': { - intlShape: { - isRequired: PropTypes.shape().isRequired, - }, - } -}).default; - -describe('AddUserForm', () => { - let props; - - beforeEach(() => { - props = { - addUserToTeam: () => {}, - hasGuestRole: false, - hasMemberRole: false, - hasOwnerRole: false, - intl: { - formatMessage: () => {}, - }, - }; - }); - - /* eslint-disable jsx-a11y/control-has-associated-label */ - describe('the options for the User Type form', () => { - it('includes an option for guest if the hasGuestRole prop is true', () => { - props.hasGuestRole = true; - - const wrapper = shallow(); - - expect(wrapper.contains(