From feab5766991ffe5abdb54da7a4e4c4f89f0b3792 Mon Sep 17 00:00:00 2001 From: Iishaan Tanwar Date: Fri, 2 Jan 2026 23:25:20 +0530 Subject: [PATCH 01/14] [deps] Upgraded React to 18.3 #870 --- .eslintrc.json | 13 +- .github/workflows/ci.yml | 2 +- babel.config.js | 6 +- browser-test/create-mobile-configuration.js | 1 + browser-test/js-file-inject.test.js | 41 +- browser-test/mobile-phone-change.test.js | 2 +- browser-test/utils.js | 2 + client/app.js | 25 +- client/components/404/404.test.js | 31 +- .../404/__snapshots__/404.test.js.snap | 156 +- .../__snapshots__/contact.test.js.snap | 140 +- client/components/contact-box/contact.test.js | 56 +- .../footer/__snapshots__/footer.test.js.snap | 91 +- client/components/footer/footer.test.js | 86 +- .../header/__snapshots__/header.test.js.snap | 401 +- client/components/header/header.test.js | 351 +- .../login/__snapshots__/login.test.js.snap | 1863 ++-- client/components/login/login.test.js | 1002 +- .../logout/__snapshots__/logout.test.js.snap | 70 +- client/components/logout/logout.test.js | 80 +- .../mobile-phone-change.test.js.snap | 409 +- .../mobile-phone-change.js | 11 +- .../mobile-phone-change.test.js | 527 +- .../mobile-phone-verification.test.js.snap | 496 +- .../mobile-phone-verification.js | 8 +- .../mobile-phone-verification.test.js | 678 +- .../modal/__snapshots__/modal.test.js.snap | 128 +- client/components/modal/modal.test.js | 258 +- .../organization-wrapper.test.js.snap | 1617 +-- .../organization-wrapper.js | 2 +- .../organization-wrapper.test.js | 940 +- .../password-change.test.js.snap | 894 +- .../password-change/password-change.js | 28 +- .../password-change/password-change.test.js | 399 +- .../password-confirm.test.js.snap | 434 +- .../password-confirm/password-confirm.js | 6 +- .../password-confirm/password-confirm.test.js | 574 +- .../__snapshots__/password-reset.test.js.snap | 268 +- .../password-reset/password-reset.js | 6 +- .../password-reset/password-reset.test.js | 280 +- .../payment-process.test.js.snap | 24 +- .../payment-process/payment-process.test.js | 438 +- .../__snapshots__/payment-status.test.js.snap | 303 +- .../payment-status/payment-status.js | 10 +- .../payment-status/payment-status.test.js | 459 +- .../__snapshots__/registration.test.js.snap | 890 +- .../components/registration/registration.js | 15 +- .../registration/registration.test.js | 949 +- .../registration/subscriptions.test.js | 648 +- client/components/registration/test-utils.js | 35 +- .../status/__snapshots__/status.test.js.snap | 1833 +--- client/components/status/status.js | 172 +- client/components/status/status.test.js | 3851 ++++--- .../status/status.test.js.enzyme-backup | 2889 +++++ .../status/status.test.js.rtl-backup | 2695 +++++ client/utils/__mocks__/get-config.js | 1 + client/utils/get-config.js | 1 + client/utils/get-plan-selection.js | 13 +- client/utils/load-translation.js | 2 + client/utils/load-translation.test.js | 2 + client/utils/log-error.js | 2 + client/utils/needs-verify.js | 6 +- client/utils/password-toggle.js | 1 + client/utils/tick.js | 11 +- client/utils/utils.test.js | 211 +- client/utils/with-route-props.js | 2 +- config/__tests__/add-org.test.js | 111 +- config/add-org.js | 2 + config/jest.config.js | 25 +- config/setupTests.js | 17 + config/webpack.config.js | 7 + package.json | 45 +- server/app.js | 26 + server/index.js | 25 +- yarn.lock | 9539 ++++++++--------- 75 files changed, 20410 insertions(+), 17232 deletions(-) create mode 100644 client/components/status/status.test.js.enzyme-backup create mode 100644 client/components/status/status.test.js.rtl-backup create mode 100644 config/setupTests.js create mode 100644 server/app.js diff --git a/.eslintrc.json b/.eslintrc.json index 08b181685..975112bcf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,7 @@ { - "extends": [ - "airbnb", - "prettier", - "prettier", - "jest-enzyme", - "plugin:jest/recommended" - ], + "extends": ["airbnb", "prettier", "plugin:jest/recommended"], "parser": "@babel/eslint-parser", - "plugins": ["prettier", "jest"], + "plugins": ["prettier", "jest", "testing-library", "jest-dom"], "parserOptions": { "ecmaVersion": 2020, "sourceType": "module", @@ -19,7 +13,8 @@ "env": { "es6": true, "browser": true, - "node": true + "node": true, + "jest": true }, "rules": { "array-bracket-spacing": [2, "never"], diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 035aac197..ec8ddd9cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: yarn install yarn setup sudo apt update -qq - sudo apt install -qq -y libssl-dev libffi-dev + sudo apt install -qq -y libssl-dev libffi-dev expect - name: Set up Python uses: actions/setup-python@v6 diff --git a/babel.config.js b/babel.config.js index 16b2ee7ef..329063cdc 100644 --- a/babel.config.js +++ b/babel.config.js @@ -12,11 +12,11 @@ module.exports = { ], plugins: [ ["@babel/plugin-transform-runtime"], - ["@babel/plugin-proposal-class-properties", {loose: true}], + ["@babel/plugin-transform-class-properties", {loose: true}], ["@babel/plugin-transform-arrow-functions"], ["@babel/plugin-syntax-dynamic-import"], - ["@babel/plugin-proposal-private-methods", {loose: true}], - ["@babel/plugin-proposal-private-property-in-object", {loose: true}], + ["@babel/plugin-transform-private-methods", {loose: true}], + ["@babel/plugin-transform-private-property-in-object", {loose: true}], ["@babel/plugin-transform-spread"], "transform-remove-strict-mode", ], diff --git a/browser-test/create-mobile-configuration.js b/browser-test/create-mobile-configuration.js index 6890d4067..59bdc25a0 100644 --- a/browser-test/create-mobile-configuration.js +++ b/browser-test/create-mobile-configuration.js @@ -60,5 +60,6 @@ try { fs.writeFileSync(configPath, yaml.dump(content)); fs.writeFileSync(path.join(jsDir, data.allOrgScript), ""); } catch (err) { + /* eslint-disable-next-line no-console */ console.log(err); } diff --git a/browser-test/js-file-inject.test.js b/browser-test/js-file-inject.test.js index a2e8320f3..1abef366d 100644 --- a/browser-test/js-file-inject.test.js +++ b/browser-test/js-file-inject.test.js @@ -20,25 +20,58 @@ describe("Selenium tests to check JS file injection in organization page", () => it("should load js file for entire application", async () => { const jsFile = initialData().allOrgScript; await driver.get(urls.login); + + // Wait for page to be fully loaded and stable + await driver.sleep(1000); + let scriptSources = []; + // Re-fetch scripts to avoid stale element reference let scripts = await getElementsByCss(driver, "script"); - await Promise.all( + + // Get all script sources in a single pass to avoid iterating over potentially stale elements + scriptSources = await Promise.all( scripts.map(async (script) => { - scriptSources.push(await script.getAttribute("src")); + try { + return await script.getAttribute("src"); + } catch (error) { + // If element becomes stale, return null and filter it out + return null; + } }), ); + + // Filter out null values from stale elements + scriptSources = scriptSources.filter((src) => src !== null); + expect(scriptSources.includes(`http://127.0.0.1:8080/${jsFile}`)).toEqual( true, ); + const data = initialData().mobileVerificationTestUser; await driver.get(urls.verificationLogin(data.organization)); + + // Wait for page to be fully loaded and stable after navigation + await driver.sleep(1000); + scriptSources = []; + // Re-fetch scripts after navigation to get fresh element references scripts = await getElementsByCss(driver, "script"); - await Promise.all( + + // Get all script sources in a single pass + scriptSources = await Promise.all( scripts.map(async (script) => { - scriptSources.push(await script.getAttribute("src")); + try { + return await script.getAttribute("src"); + } catch (error) { + // If element becomes stale, return null and filter it out + return null; + } }), ); + + // Filter out null values from stale elements + scriptSources = scriptSources.filter((src) => src !== null); + expect(scriptSources.includes(`http://127.0.0.1:8080/${jsFile}`)).toEqual( true, ); diff --git a/browser-test/mobile-phone-change.test.js b/browser-test/mobile-phone-change.test.js index 5b40a346b..00db31042 100644 --- a/browser-test/mobile-phone-change.test.js +++ b/browser-test/mobile-phone-change.test.js @@ -89,7 +89,7 @@ describe("Selenium tests for ", () => { submitBtn = await getElementByCss(driver, "input[type='submit']"); await driver.wait(until.elementIsVisible(submitBtn)); submitBtn.click(); - successToastDiv = await getElementByCss(driver, "div[role=alert]"); + successToastDiv = await getElementByCss(driver, successToastSelector); await driver.wait(until.elementIsVisible(successToastDiv)); expect(await successToastDiv.getText()).toEqual( "SMS verification code sent successfully.", diff --git a/browser-test/utils.js b/browser-test/utils.js index fa6c47c3f..403558566 100644 --- a/browser-test/utils.js +++ b/browser-test/utils.js @@ -32,6 +32,7 @@ export const getElementByCss = async (driver, css) => { try { el = await driver.wait(until.elementLocated(By.css(css)), waitTime); } catch (err) { + /* eslint-disable-next-line no-console */ console.log(err, css, await driver.getPageSource()); } return el; @@ -42,6 +43,7 @@ export const getElementsByCss = async (driver, css) => { try { el = await driver.wait(until.elementsLocated(By.css(css)), waitTime); } catch (err) { + /* eslint-disable-next-line no-console */ console.log(err, css, await driver.getPageSource()); } return el; diff --git a/client/app.js b/client/app.js index 05728750c..506628005 100644 --- a/client/app.js +++ b/client/app.js @@ -1,13 +1,12 @@ /* eslint-disable import/no-import-module-exports */ import "./index.css"; - +import {createRoot} from "react-dom/client"; import {Provider, connect} from "react-redux"; - +import {HelmetProvider} from "react-helmet-async"; import {CookiesProvider} from "react-cookie"; import PropTypes from "prop-types"; import React from "react"; import {Route, BrowserRouter as Router, Routes} from "react-router-dom"; -import {render} from "react-dom"; import {ToastContainer} from "react-toastify"; import OrganizationRoutes from "./routes"; import organizations from "./organizations.json"; @@ -25,7 +24,7 @@ class BaseApp extends React.Component { render() { return ( - + } /> @@ -48,13 +47,17 @@ const mapDispatchToProps = (dispatch) => ({ const App = connect(null, mapDispatchToProps)(BaseApp); export default function app() { - render( - - - - - , - document.getElementById("root"), + const container = document.getElementById("root"); + const root = createRoot(container); + + root.render( + + + + + + + , ); } diff --git a/client/components/404/404.test.js b/client/components/404/404.test.js index 0026cce9c..ab4df3c0c 100644 --- a/client/components/404/404.test.js +++ b/client/components/404/404.test.js @@ -1,6 +1,7 @@ import React from "react"; -import ShallowRenderer from "react-test-renderer/shallow"; -import {shallow} from "enzyme"; +import {render} from "@testing-library/react"; +import "@testing-library/jest-dom"; +import {MemoryRouter} from "react-router-dom"; import getConfig from "../../utils/get-config"; import loadTranslation from "../../utils/load-translation"; import DoesNotExist from "./404"; @@ -17,12 +18,14 @@ const createTestProps = (props) => ({ ...props, }); +const renderWithRouter = (component) => + render({component}); + describe(" rendering with placeholder translation tags", () => { const props = createTestProps(); it("should render translation placeholder correctly", () => { - const renderer = new ShallowRenderer(); - const wrapper = renderer.render(); - expect(wrapper).toMatchSnapshot(); + const {container} = renderWithRouter(); + expect(container).toMatchSnapshot(); }); }); @@ -32,22 +35,20 @@ describe(" rendering", () => { }); it("should render correctly default 404 page without props", () => { - const renderer = new ShallowRenderer(); - const component = renderer.render(); - expect(component).toMatchSnapshot(); + const {container} = renderWithRouter(); + expect(container).toMatchSnapshot(); }); it("should render correctly custom 404 page with props", () => { const props = createTestProps(); - const renderer = new ShallowRenderer(); - const component = renderer.render(); - expect(component).toMatchSnapshot(); + const {container} = renderWithRouter(); + expect(container).toMatchSnapshot(); }); it("should set title with organisation name", () => { const props = createTestProps(); - const wrapper = shallow(); - const setTitleMock = wrapper.instance().props.setTitle.mock; + renderWithRouter(); + const setTitleMock = props.setTitle.mock; expect(setTitleMock.calls.pop()).toEqual(["404 Not found", props.orgName]); }); @@ -55,8 +56,8 @@ describe(" rendering", () => { const props = createTestProps(); props.page = undefined; props.orgName = undefined; - const wrapper = shallow(); - const setTitleMock = wrapper.instance().props.setTitle.mock; + renderWithRouter(); + const setTitleMock = props.setTitle.mock; expect(setTitleMock.calls.length).toBe(0); }); }); diff --git a/client/components/404/__snapshots__/404.test.js.snap b/client/components/404/__snapshots__/404.test.js.snap index f378df454..b762fd19f 100644 --- a/client/components/404/__snapshots__/404.test.js.snap +++ b/client/components/404/__snapshots__/404.test.js.snap @@ -1,43 +1,45 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[` rendering should render correctly custom 404 page with props 1`] = ` -
+
- 404 -
-
- Not Found -
-
- Sorry, we couldn't find the page you were looking for. -
-
- - Go back to homepage - + 404 +
+
+ Not Found +
+
+ Sorry, we couldn't find the page you were looking for. +
+
@@ -46,33 +48,35 @@ exports[` rendering should render correctly custom 404 page with `; exports[` rendering should render correctly default 404 page without props 1`] = ` -
+
- Oops! -
-
- 404 Not Found -
-
- Sorry, an error has occurred, Requested page not found! +
+ Oops! +
+
+ 404 Not Found +
+
+ Sorry, an error has occurred, Requested page not found! +
@@ -81,43 +85,45 @@ exports[` rendering should render correctly default 404 page wit `; exports[` rendering with placeholder translation tags should render translation placeholder correctly 1`] = ` -
+
- 404_H -
-
- 404_SUBH -
-
- 404_MESSAGE -
-
- + 404_H +
+
+ 404_SUBH +
+
+ 404_MESSAGE +
+
- HOME_PG_LINK_TXT - + + HOME_PG_LINK_TXT + +
diff --git a/client/components/contact-box/__snapshots__/contact.test.js.snap b/client/components/contact-box/__snapshots__/contact.test.js.snap index 5a7064b0a..4641c77c0 100644 --- a/client/components/contact-box/__snapshots__/contact.test.js.snap +++ b/client/components/contact-box/__snapshots__/contact.test.js.snap @@ -1,142 +1,32 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` rendering with placeholder translation tags should render translation placeholder correctly 1`] = ` -
+exports[` rendering should render correctly 1`] = ` +
- - EMAIL - : - - - support@openwisp.co - -
-
- - HELPDESK - : - - - +789 948 564 - -
-
`; -exports[` rendering should render correctly 1`] = ` -
+exports[` rendering with placeholder translation tags should render translation placeholder correctly 1`] = ` +
- - Email - : - - - support@openwisp.co - -
-
- - Helpdesk - : - - - +789 948 564 - -
-
diff --git a/client/components/contact-box/contact.test.js b/client/components/contact-box/contact.test.js index f49a41233..3bdcc107f 100644 --- a/client/components/contact-box/contact.test.js +++ b/client/components/contact-box/contact.test.js @@ -1,13 +1,25 @@ -import {shallow} from "enzyme"; +import {render, screen} from "@testing-library/react"; +import "@testing-library/jest-dom"; import React from "react"; -import ShallowRenderer from "react-test-renderer/shallow"; + +// Mock modules BEFORE importing - jest.mock must be before imports +/* eslint-disable import/first */ +jest.mock("../../utils/get-config", () => ({ + __esModule: true, + default: jest.fn(() => ({ + components: { + contact_page: { + social_links: [], + }, + }, + })), +})); +jest.mock("../../utils/load-translation"); import getConfig from "../../utils/get-config"; import loadTranslation from "../../utils/load-translation"; import Contact from "./contact"; - -jest.mock("../../utils/get-config"); -jest.mock("../../utils/load-translation"); +/* eslint-enable import/first */ const defaultConfig = getConfig("default", true); const links = [ @@ -43,43 +55,43 @@ const createTestProps = (props) => ({ describe(" rendering with placeholder translation tags", () => { const props = createTestProps(); it("should render translation placeholder correctly", () => { - const renderer = new ShallowRenderer(); - const wrapper = renderer.render(); - expect(wrapper).toMatchSnapshot(); + const {container} = render(); + expect(container).toMatchSnapshot(); }); }); -describe(" rendering", () => { +describe(" rendering", () => { let props; beforeEach(() => { loadTranslation("en", "default"); }); it("should render correctly", () => { props = createTestProps(); - const renderer = new ShallowRenderer(); - const component = renderer.render(); - expect(component).toMatchSnapshot(); + const {container} = render(); + expect(container).toMatchSnapshot(); }); it("should render without authenticated links when not authenticated", () => { props = createTestProps(); props.contactPage.social_links = links; props.isAuthenticated = false; - const wrapper = shallow(); - expect(wrapper.find(".contact-image")).toHaveLength(2); - expect(wrapper.find(".link.google")).toHaveLength(1); - expect(wrapper.find(".link.facebook")).toHaveLength(1); - expect(wrapper.find(".link.twitter")).toHaveLength(0); + render(); + + // Check for google and facebook links, twitter should not be present + expect(screen.getByAltText("google")).toBeInTheDocument(); + expect(screen.getByAltText("facebook")).toBeInTheDocument(); + expect(screen.queryByAltText("twitter")).not.toBeInTheDocument(); }); it("should render with authenticated links when authenticated", () => { props = createTestProps(); props.contactPage.social_links = links; props.isAuthenticated = true; - const wrapper = shallow(); - expect(wrapper.find(".contact-image")).toHaveLength(2); - expect(wrapper.find(".link.google")).toHaveLength(1); - expect(wrapper.find(".link.twitter")).toHaveLength(1); - expect(wrapper.find(".link.facebook")).toHaveLength(0); + render(); + + // Check for google and twitter links, facebook should not be present + expect(screen.getByAltText("google")).toBeInTheDocument(); + expect(screen.getByAltText("twitter")).toBeInTheDocument(); + expect(screen.queryByAltText("facebook")).not.toBeInTheDocument(); }); }); diff --git a/client/components/footer/__snapshots__/footer.test.js.snap b/client/components/footer/__snapshots__/footer.test.js.snap index 0c53f3ba8..e457c26be 100644 --- a/client/components/footer/__snapshots__/footer.test.js.snap +++ b/client/components/footer/__snapshots__/footer.test.js.snap @@ -1,72 +1,69 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`