From 0413912c7be0a224929f86de6039642d56457b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Thu, 9 Mar 2023 10:42:03 +0000 Subject: [PATCH] [web] Better react-router-dom mocking --- web/src/App.test.jsx | 13 +++---- web/src/Main.test.jsx | 12 +++---- .../core/ChangeProductButton.test.jsx | 6 +--- web/src/components/core/Page.test.jsx | 5 --- web/src/components/core/Section.test.jsx | 19 +++------- web/src/components/l10n/L10nPage.test.jsx | 14 +++----- .../components/network/NetworkPage.test.jsx | 5 --- .../components/overview/L10nSection.test.jsx | 8 +---- .../overview/NetworkSection.test.jsx | 6 ---- web/src/components/overview/Overview.test.jsx | 8 ++--- .../overview/StorageSection.test.jsx | 6 ---- .../components/overview/UsersSection.test.jsx | 8 +---- .../software/ProductSelectionPage.test.jsx | 14 +++----- .../components/storage/ProposalPage.test.jsx | 6 ---- web/src/test-utils.js | 36 ++++++++++++++++--- 15 files changed, 58 insertions(+), 108 deletions(-) diff --git a/web/src/App.test.jsx b/web/src/App.test.jsx index 113113ba3d..9adae67c95 100644 --- a/web/src/App.test.jsx +++ b/web/src/App.test.jsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2022] SUSE LLC + * Copyright (c) [2022-2023] SUSE LLC * * All Rights Reserved. * @@ -28,11 +28,6 @@ import { STARTUP, CONFIG, INSTALL } from "~/client/phase"; import { IDLE, BUSY } from "~/client/status"; jest.mock("~/client"); - -jest.mock('react-router-dom', () => ({ - Outlet: mockComponent("Content"), -})); - jest.mock("~/components/layout/Layout", () => mockLayout()); // Mock some components, @@ -147,7 +142,7 @@ describe("App", () => { it("renders the application content", async () => { installerRender(); - await screen.findByText("Content"); + await screen.findByText(/Outlet Content/); }); }); @@ -169,7 +164,7 @@ describe("App", () => { it("renders the Installation component on the INSTALL phase", async () => { installerRender(); - await screen.findByText("Content"); + await screen.findByText(/Outlet Content/); changePhaseTo(INSTALL); await screen.findByText("Installation Mock"); }); @@ -183,7 +178,7 @@ describe("App", () => { it("renders the application's content", async () => { installerRender(); - await screen.findByText("Content"); + await screen.findByText(/Outlet Content/); }); }); }); diff --git a/web/src/Main.test.jsx b/web/src/Main.test.jsx index fe874a3783..eb8e337a5a 100644 --- a/web/src/Main.test.jsx +++ b/web/src/Main.test.jsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2022] SUSE LLC + * Copyright (c) [2022-2023] SUSE LLC * * All Rights Reserved. * @@ -21,18 +21,16 @@ import React from "react"; import { screen } from "@testing-library/react"; -import { installerRender, mockComponent } from "~/test-utils"; +import { plainRender, mockComponent } from "~/test-utils"; import Main from "~/Main"; jest.mock("~/components/questions/Questions", () => mockComponent("Questions Mock")); -jest.mock('react-router-dom', () => ({ - Outlet: mockComponent("Content"), -})); it("renders the Questions component and the content", async () => { - installerRender(
); + plainRender(
); await screen.findByText("Questions Mock"); - await screen.findByText("Content"); + // react-router-dom Outlet is mocked. See test-utils for more details + await screen.findByText("Outlet Content"); }); diff --git a/web/src/components/core/ChangeProductButton.test.jsx b/web/src/components/core/ChangeProductButton.test.jsx index b83471ad51..384e5d1331 100644 --- a/web/src/components/core/ChangeProductButton.test.jsx +++ b/web/src/components/core/ChangeProductButton.test.jsx @@ -21,12 +21,11 @@ import React from "react"; import { screen, waitFor } from "@testing-library/react"; -import { plainRender } from "~/test-utils"; +import { plainRender, mockNavigateFn } from "~/test-utils"; import { createClient } from "~/client"; import { ChangeProductButton } from "~/components/core"; let mockProducts; -const mockNavigateFn = jest.fn(); jest.mock("~/client"); jest.mock("~/context/software", () => ({ @@ -37,9 +36,6 @@ jest.mock("~/context/software", () => ({ }; } })); -jest.mock('react-router-dom', () => ({ - useNavigate: () => mockNavigateFn, -})); beforeEach(() => { createClient.mockImplementation(() => { diff --git a/web/src/components/core/Page.test.jsx b/web/src/components/core/Page.test.jsx index 75ef659e76..e9199bbc3b 100644 --- a/web/src/components/core/Page.test.jsx +++ b/web/src/components/core/Page.test.jsx @@ -24,11 +24,6 @@ import { screen } from "@testing-library/react"; import { installerRender, mockLayout } from "~/test-utils"; import { Page } from "~/components/core"; -const mockNavigateFn = jest.fn(); - -jest.mock('react-router-dom', () => ({ - useNavigate: () => mockNavigateFn, -})); jest.mock("~/components/layout/Layout", () => mockLayout()); describe("Page", () => { diff --git a/web/src/components/core/Section.test.jsx b/web/src/components/core/Section.test.jsx index 56af194095..3f3410316d 100644 --- a/web/src/components/core/Section.test.jsx +++ b/web/src/components/core/Section.test.jsx @@ -21,13 +21,9 @@ import React from "react"; import { screen, within } from "@testing-library/react"; -import { plainRender } from "~/test-utils"; +import { plainRender, installerRender } from "~/test-utils"; import { Section } from "~/components/core"; -jest.mock('react-router-dom', () => ({ - Link: ({ to, children }) => {children} -})); - describe("Section", () => { it("renders given title", () => { plainRender(
); @@ -74,10 +70,9 @@ describe("Section", () => { describe("when path is given", () => { it("renders a link for navigating to it", async () => { - plainRender(
); + installerRender(
); const heading = screen.getByRole("heading", { name: "Settings" }); const link = within(heading).getByRole("link", { name: "Settings" }); - // NOTE: ReactRouter#Link is mocked at the top of file. expect(link).toHaveAttribute("href", "/settings"); }); }); @@ -86,7 +81,7 @@ describe("Section", () => { describe("and path is not present", () => { it("triggers it when the user click on the section title", async () => { const openDialog = jest.fn(); - const { user } = plainRender( + const { user } = installerRender(
); const button = screen.getByRole("button", { name: "Settings" }); @@ -96,15 +91,9 @@ describe("Section", () => { }); describe("but path is present too", () => { - // Silence "Error: Not Implemented: navigation..." from jsdom when clicking a link - // https://github.com/jsdom/jsdom/issues/2112 - const eventListener = (e) => e.preventDefault(); - beforeEach(() => window.addEventListener("click", eventListener)); - afterEach(() => window.removeEventListener("click", eventListener, true)); - it("does not triggers it when the user click on the section title", async () => { const openDialog = jest.fn(); - const { user } = plainRender( + const { user } = installerRender(
); const link = screen.getByRole("link", { name: "Settings" }); diff --git a/web/src/components/l10n/L10nPage.test.jsx b/web/src/components/l10n/L10nPage.test.jsx index d046a88ad5..19617c5c46 100644 --- a/web/src/components/l10n/L10nPage.test.jsx +++ b/web/src/components/l10n/L10nPage.test.jsx @@ -21,24 +21,18 @@ import React from "react"; import { screen } from "@testing-library/react"; -import { installerRender, mockLayout } from "~/test-utils"; +import { installerRender, mockLayout, mockNavigateFn } from "~/test-utils"; import { L10nPage } from "~/components/l10n"; import { createClient } from "~/client"; -const mockNavigateFn = jest.fn(); - -jest.mock("~/client"); -jest.mock('react-router-dom', () => ({ - useNavigate: () => mockNavigateFn, -})); -jest.mock("~/components/layout/Layout", () => mockLayout()); - +const setLanguagesFn = jest.fn(); const languages = [ { id: "en_US", name: "English" }, { id: "de_DE", name: "German" } ]; -const setLanguagesFn = jest.fn(); +jest.mock("~/client"); +jest.mock("~/components/layout/Layout", () => mockLayout()); beforeEach(() => { // if defined outside, the mock is cleared automatically diff --git a/web/src/components/network/NetworkPage.test.jsx b/web/src/components/network/NetworkPage.test.jsx index 37fa8fbd89..e982c1c2dc 100644 --- a/web/src/components/network/NetworkPage.test.jsx +++ b/web/src/components/network/NetworkPage.test.jsx @@ -26,11 +26,6 @@ import NetworkPage from "~/components/network/NetworkPage"; import { ConnectionTypes } from "~/client/network"; import { createClient } from "~/client"; -const mockNavigateFn = jest.fn(); - -jest.mock('react-router-dom', () => ({ - useNavigate: () => mockNavigateFn, -})); jest.mock("~/client"); const wiredConnection = { diff --git a/web/src/components/overview/L10nSection.test.jsx b/web/src/components/overview/L10nSection.test.jsx index 8a1c86a73c..ec282d6fc2 100644 --- a/web/src/components/overview/L10nSection.test.jsx +++ b/web/src/components/overview/L10nSection.test.jsx @@ -21,17 +21,11 @@ import React from "react"; import { act, screen } from "@testing-library/react"; -import { installerRender, createCallbackMock, mockComponent } from "~/test-utils"; +import { installerRender, createCallbackMock } from "~/test-utils"; import { L10nSection } from "~/components/overview"; import { createClient } from "~/client"; -const mockNavigateFn = jest.fn(); - jest.mock("~/client"); -jest.mock('react-router-dom', () => ({ - useNavigate: () => mockNavigateFn, - Link: mockComponent("Link") -})); const languages = [ { id: "en_US", name: "English" }, diff --git a/web/src/components/overview/NetworkSection.test.jsx b/web/src/components/overview/NetworkSection.test.jsx index dd6ecf8087..6885db3dff 100644 --- a/web/src/components/overview/NetworkSection.test.jsx +++ b/web/src/components/overview/NetworkSection.test.jsx @@ -26,13 +26,7 @@ import { NetworkSection } from "~/components/overview"; import { ConnectionTypes, NetworkEventTypes } from "~/client/network"; import { createClient } from "~/client"; -const mockNavigateFn = jest.fn(); - jest.mock("~/client"); -jest.mock('react-router-dom', () => ({ - useNavigate: () => mockNavigateFn, - Link: mockComponent("Link") -})); jest.mock('~/components/core/SectionSkeleton', () => mockComponent("Section Skeleton")); diff --git a/web/src/components/overview/Overview.test.jsx b/web/src/components/overview/Overview.test.jsx index 8c0a6a9fd3..fb4da63b92 100644 --- a/web/src/components/overview/Overview.test.jsx +++ b/web/src/components/overview/Overview.test.jsx @@ -44,11 +44,6 @@ jest.mock("~/context/software", () => ({ } })); -jest.mock('react-router-dom', () => ({ - Navigate: mockComponent("Navigate"), - useNavigate: () => jest.fn() -})); - jest.mock("~/components/layout/Layout", () => mockLayout()); jest.mock("~/components/overview/L10nSection", () => mockComponent("Localization Section")); jest.mock("~/components/overview/StorageSection", () => mockComponent("Storage Section")); @@ -92,7 +87,8 @@ describe("when no product is selected", () => { it("redirects to the product selection page", async () => { installerRender(); - await screen.findByText("Navigate"); + // react-router-dom Navigate is mocked. See test-utils for more details. + await screen.findByText("Navigating to /products"); }); }); diff --git a/web/src/components/overview/StorageSection.test.jsx b/web/src/components/overview/StorageSection.test.jsx index de8d9ec7e6..b66d7c738b 100644 --- a/web/src/components/overview/StorageSection.test.jsx +++ b/web/src/components/overview/StorageSection.test.jsx @@ -26,14 +26,8 @@ import { createClient } from "~/client"; import { BUSY, IDLE } from "~/client/status"; import { StorageSection } from "~/components/overview"; -const mockUseNavigate = jest.fn(); jest.mock("~/client"); jest.mock("~/components/core/SectionSkeleton", () => mockComponent("Loading storage")); -jest.mock("react-router-dom", () => ({ - ...jest.requireActual("react-router-dom"), - useNavigate: () => mockUseNavigate, - Link: mockComponent("Link") -})); let status = IDLE; let proposal = { diff --git a/web/src/components/overview/UsersSection.test.jsx b/web/src/components/overview/UsersSection.test.jsx index 3144d6c624..e9da0f525d 100644 --- a/web/src/components/overview/UsersSection.test.jsx +++ b/web/src/components/overview/UsersSection.test.jsx @@ -21,17 +21,11 @@ import React from "react"; import { screen } from "@testing-library/react"; -import { installerRender, mockComponent } from "~/test-utils"; +import { installerRender } from "~/test-utils"; import { UsersSection } from "~/components/overview"; import { createClient } from "~/client"; -const mockNavigateFn = jest.fn(); - jest.mock("~/client"); -jest.mock('react-router-dom', () => ({ - useNavigate: () => mockNavigateFn, - Link: mockComponent("Link") -})); const user = { fullName: "Jane Doe", diff --git a/web/src/components/software/ProductSelectionPage.test.jsx b/web/src/components/software/ProductSelectionPage.test.jsx index b018cc792f..b726a37b45 100644 --- a/web/src/components/software/ProductSelectionPage.test.jsx +++ b/web/src/components/software/ProductSelectionPage.test.jsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2022] SUSE LLC + * Copyright (c) [2022-2023] SUSE LLC * * All Rights Reserved. * @@ -21,7 +21,7 @@ import React from "react"; import { screen } from "@testing-library/react"; -import { installerRender, mockLayout } from "~/test-utils"; +import { installerRender, mockLayout, mockNavigateFn } from "~/test-utils"; import { ProductSelectionPage } from "~/components/software"; import { createClient } from "~/client"; @@ -39,12 +39,6 @@ const products = [ ]; jest.mock("~/client"); -const mockUseNavigate = jest.fn(); -jest.mock("react-router-dom", () => ({ - ...jest.requireActual("react-router-dom"), - useNavigate: () => mockUseNavigate -})); - jest.mock("~/context/software", () => ({ ...jest.requireActual("~/context/software"), useSoftware: () => { @@ -78,7 +72,7 @@ describe("when the user chooses a product", () => { const button = await screen.findByRole("button", { name: "Select" }); await user.click(button); expect(softwareMock.selectProduct).toHaveBeenCalledWith("MicroOS"); - expect(mockUseNavigate).toHaveBeenCalledWith("/"); + expect(mockNavigateFn).toHaveBeenCalledWith("/"); }); }); @@ -89,6 +83,6 @@ describe("when the user chooses does not change the product", () => { const button = await screen.findByRole("button", { name: "Select" }); await user.click(button); expect(softwareMock.selectProduct).not.toHaveBeenCalled(); - expect(mockUseNavigate).toHaveBeenCalledWith("/"); + expect(mockNavigateFn).toHaveBeenCalledWith("/"); }); }); diff --git a/web/src/components/storage/ProposalPage.test.jsx b/web/src/components/storage/ProposalPage.test.jsx index 5e9162bbd5..bfef1a791e 100644 --- a/web/src/components/storage/ProposalPage.test.jsx +++ b/web/src/components/storage/ProposalPage.test.jsx @@ -35,14 +35,8 @@ const FakeProposalTargetSection = ({ calculateProposal }) => { }; jest.mock("~/client"); -jest.mock("react-router-dom", () => ({ - ...jest.requireActual("react-router-dom"), - useNavigate: () => jest.fn() -})); - jest.mock("~/components/core/SectionSkeleton", () => mockComponent("Loading proposal")); jest.mock("~/components/storage/ProposalTargetSection", () => FakeProposalTargetSection); - jest.mock("~/components/storage/ProposalSettingsSection", () => mockComponent("Settings section")); jest.mock("~/components/storage/ProposalActionsSection", () => mockComponent("Actions section")); diff --git a/web/src/test-utils.js b/web/src/test-utils.js index 66561a3a83..253e90dc19 100644 --- a/web/src/test-utils.js +++ b/web/src/test-utils.js @@ -26,17 +26,38 @@ */ import React from "react"; +import { MemoryRouter } from "react-router-dom"; import userEvent from "@testing-library/user-event"; import { render } from "@testing-library/react"; import { createClient } from "~/client/index"; import { InstallerClientProvider } from "~/context/installer"; -const InstallerProvider = ({ children }) => { +/** + * Allows checking when react-router-dom navigate function was + * called with certain path + * + * @example + * expect(mockNavigateFn).toHaveBeenCalledWith("/") + */ +const mockNavigateFn = jest.fn(); + +// Centralize the react-router-dom mock here +jest.mock('react-router-dom', () => ({ + ...jest.requireActual("react-router-dom"), + useNavigate: () => mockNavigateFn, + Navigate: ({ to: route }) => <>Navigating to {route}, + Outlet: () => <>Outlet Content +})); + +const Providers = ({ children }) => { const client = createClient(); + return ( - {children} + + {children} + ); }; @@ -45,7 +66,7 @@ const installerRender = (ui, options = {}) => { return ( { user: userEvent.setup(), - ...render(ui, { wrapper: InstallerProvider, ...options }) + ...render(ui, { wrapper: Providers, ...options }) } ); }; @@ -111,4 +132,11 @@ const mockLayout = () => ({ AdditionalInfo: ({ children }) => children, }); -export { installerRender, plainRender, createCallbackMock, mockComponent, mockLayout }; +export { + plainRender, + installerRender, + createCallbackMock, + mockComponent, + mockLayout, + mockNavigateFn +};