From 94fba5a3ac37c6f39a430d65e46583279b4445d3 Mon Sep 17 00:00:00 2001 From: rare-magma Date: Mon, 25 Dec 2023 13:58:07 +0100 Subject: [PATCH] test: add more assertions Signed-off-by: rare-magma --- src/App.test.tsx | 12 ++- .../CalculateButton/CalculateButton.test.tsx | 4 +- src/components/Chart/Chart.test.tsx | 5 +- src/components/ChartsPage/ChartsPage.test.tsx | 5 -- .../ItemForm/ItemFormGroup.test.tsx | 53 ++++++++++++- .../LandingPage/LandingPage.test.tsx | 6 +- src/components/NavBar/NavBar.test.tsx | 13 +++- .../Notification/Notification.test.tsx | 5 ++ src/components/StatCard/StatCard.test.tsx | 21 ++++++ src/components/TableCard/TableCard.test.tsx | 75 +++++++++++++++++-- src/setupTests.ts | 13 ++++ 11 files changed, 188 insertions(+), 24 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index dca10b6b..abaf4d37 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,4 +1,4 @@ -import { cleanup, render, screen } from "@testing-library/react"; +import { act, cleanup, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import App from "./App"; import { budgetsDB, calcHistDB, optionsDB } from "./db"; @@ -44,7 +44,11 @@ describe("App", () => { }); it("deletes budget when clicking delete button", async () => { - await expect(budgetsDB.getItem(testBudget.id)).resolves.toEqual(testBudget); + await act(async () => { + await expect(budgetsDB.getItem(testBudget.id)).resolves.toEqual( + testBudget, + ); + }); const newButton = screen.getAllByRole("button", { name: "new budget" }); await userEvent.click(newButton[0]); await screen @@ -57,6 +61,8 @@ describe("App", () => { screen.getByRole("button", { name: /confirm budget deletion/i }), ); - await expect(budgetsDB.getItem(testBudget.id)).resolves.toBeNull(); + await act(async () => { + await expect(budgetsDB.getItem(testBudget.id)).resolves.toBeNull(); + }); }); }); diff --git a/src/components/CalculateButton/CalculateButton.test.tsx b/src/components/CalculateButton/CalculateButton.test.tsx index 0d07a134..1e5a00a5 100644 --- a/src/components/CalculateButton/CalculateButton.test.tsx +++ b/src/components/CalculateButton/CalculateButton.test.tsx @@ -22,9 +22,9 @@ describe("CalculateButton", () => { expect(comp).toMatchSnapshot(); }); - it("renders initial state", () => { + it("renders initial state", async () => { expect( - screen.getByLabelText("select operation type to item value"), + await screen.findByLabelText("select operation type to item value"), ).toBeInTheDocument(); }); diff --git a/src/components/Chart/Chart.test.tsx b/src/components/Chart/Chart.test.tsx index 023f7879..9f8803c5 100644 --- a/src/components/Chart/Chart.test.tsx +++ b/src/components/Chart/Chart.test.tsx @@ -18,10 +18,6 @@ describe("Chart", () => { })} /> ); - beforeAll(() => { - vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(800); - vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(800); - }); beforeEach(() => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -44,6 +40,7 @@ describe("Chart", () => { it("matches snapshot", () => { expect(comp).toMatchSnapshot(); }); + it("renders initial state", () => { expect(screen.getByText("chart header")).toBeInTheDocument(); expect(screen.getByText("median revenue")).toBeInTheDocument(); diff --git a/src/components/ChartsPage/ChartsPage.test.tsx b/src/components/ChartsPage/ChartsPage.test.tsx index 25866086..7a0d0330 100644 --- a/src/components/ChartsPage/ChartsPage.test.tsx +++ b/src/components/ChartsPage/ChartsPage.test.tsx @@ -7,11 +7,6 @@ describe("ChartsPage", () => { const onShowGraphs = vi.fn(); const comp = ; - beforeAll(() => { - vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(800); - vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(800); - }); - beforeEach(() => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore diff --git a/src/components/ItemForm/ItemFormGroup.test.tsx b/src/components/ItemForm/ItemFormGroup.test.tsx index 13010db2..362198ae 100644 --- a/src/components/ItemForm/ItemFormGroup.test.tsx +++ b/src/components/ItemForm/ItemFormGroup.test.tsx @@ -4,6 +4,8 @@ import React from "react"; import { configContextSpy, itemForm1, + setBudgetMock, + testBudget, testSpanishConfigContext, } from "../../setupTests"; import { ItemFormGroup } from "./ItemFormGroup"; @@ -26,26 +28,71 @@ describe("ItemFormGroup", () => { it("matches snapshot", () => { expect(comp).toMatchSnapshot(); }); - it("renders initial state", () => { - expect(screen.getByDisplayValue("name1")).toBeInTheDocument(); - expect(screen.getByDisplayValue("$10")).toBeInTheDocument(); + + it("renders initial state", async () => { + expect(await screen.findByDisplayValue("name1")).toBeInTheDocument(); + expect(await screen.findByDisplayValue("$10")).toBeInTheDocument(); }); it("reacts to user changing input", async () => { + setBudgetMock.mockClear(); await userEvent.type(screen.getByDisplayValue("name1"), "change name"); + expect(screen.getByDisplayValue("name1change name")).toBeInTheDocument(); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + expenses: { + items: [{ id: 1, name: "name1change name", value: 10 }], + total: 10, + }, + }, + false, + ); + + setBudgetMock.mockClear(); await userEvent.type(screen.getByDisplayValue("$10"), "123"); + expect(screen.getByDisplayValue("$123")).toBeInTheDocument(); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + expenses: { + items: [{ id: 1, name: "expense1", value: 123 }], + total: 123, + }, + stats: { + ...testBudget.stats, + available: -23, + withGoal: -33, + }, + }, + false, + ); }); it("removes item when user clicks delete confirmation button", async () => { + setBudgetMock.mockClear(); await userEvent.click( screen.getByRole("button", { name: "delete item 1" }), ); await userEvent.click( screen.getByRole("button", { name: "confirm item 1 deletion" }), ); + + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + expenses: { items: [], total: 0 }, + stats: { + ...testBudget.stats, + available: 100, + withGoal: 90, + }, + }, + true, + ); }); it("shows tooltip when user hovers over", async () => { diff --git a/src/components/LandingPage/LandingPage.test.tsx b/src/components/LandingPage/LandingPage.test.tsx index cee6d690..2626691f 100644 --- a/src/components/LandingPage/LandingPage.test.tsx +++ b/src/components/LandingPage/LandingPage.test.tsx @@ -3,10 +3,12 @@ import userEvent from "@testing-library/user-event"; import { budgetContextSpy, generalContextSpy, + setBudgetMock, testBudget, testEmptyBudgetContext, testGeneralContext, } from "../../setupTests"; +import { createNewBudget } from "../../utils"; import { LandingPage } from "./LandingPage"; describe("LandingPage", () => { @@ -30,14 +32,16 @@ describe("LandingPage", () => { }); it("triggers new budget", async () => { + setBudgetMock.mockClear(); const newButton = screen.getAllByRole("button", { name: "new budget" })[0]; await userEvent.click(newButton); + expect(setBudgetMock).toHaveBeenCalledWith(createNewBudget(), true); }); it("triggers upload", async () => { await userEvent.upload( screen.getByTestId("import-form-control-landing-page"), - new File([testBudget as unknown as Blob], "test"), + new File([JSON.stringify(testBudget)], "test"), ); }); diff --git a/src/components/NavBar/NavBar.test.tsx b/src/components/NavBar/NavBar.test.tsx index f1dcd9b0..5252193e 100644 --- a/src/components/NavBar/NavBar.test.tsx +++ b/src/components/NavBar/NavBar.test.tsx @@ -2,7 +2,9 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { budgetContextSpy, + setBudgetMock, testBudget, + testBudgetClone, testEmptyBudgetContext, } from "../../setupTests"; import { NavBar } from "./NavBar"; @@ -46,14 +48,18 @@ describe("NavBar", () => { }); it("triggers event when clone button is pressed", async () => { + setBudgetMock.mockClear(); await userEvent.click(screen.getByLabelText("clone budget")); + expect(setBudgetMock).toHaveBeenCalledWith(testBudgetClone, true); }); it("triggers event when import button is pressed", async () => { await userEvent.click(screen.getByLabelText("import budget")); await userEvent.upload( screen.getByTestId("import-form-control"), - new File([testBudget as unknown as Blob], "budget"), + new File([JSON.stringify(testBudget)], "budget", { + type: "application/json", + }), ); }); @@ -81,9 +87,14 @@ describe("NavBar", () => { }); it("triggers event when user changes budget name input", async () => { + setBudgetMock.mockClear(); await userEvent.type(screen.getByDisplayValue("2023-03"), "change name"); expect(screen.getByDisplayValue("2023-03change name")).toBeInTheDocument(); + expect(setBudgetMock).toHaveBeenCalledWith( + { ...testBudget, name: "2023-03change name" }, + false, + ); }); it("triggers event when user clicks delete budget button", async () => { diff --git a/src/components/Notification/Notification.test.tsx b/src/components/Notification/Notification.test.tsx index 8a9f008e..024164c0 100644 --- a/src/components/Notification/Notification.test.tsx +++ b/src/components/Notification/Notification.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { BudgetNotification } from "../../context/GeneralContext"; +import { setNotificationsMock, undoMock } from "../../setupTests"; import { Notification } from "./Notification"; describe("Notification", () => { @@ -26,18 +27,22 @@ describe("Notification", () => { }); it("closes when close button is clicked", async () => { + setNotificationsMock.mockClear(); await userEvent.click( screen.getByRole("button", { name: "dismiss notification", }), ); + expect(setNotificationsMock).toHaveBeenCalledWith([]); }); it("closes when undo button is clicked", async () => { + undoMock.mockClear(); await userEvent.click( screen.getByRole("button", { name: "undo budget deletion", }), ); + expect(undoMock).toHaveBeenCalled(); }); }); diff --git a/src/components/StatCard/StatCard.test.tsx b/src/components/StatCard/StatCard.test.tsx index bdecd8cf..edf44121 100644 --- a/src/components/StatCard/StatCard.test.tsx +++ b/src/components/StatCard/StatCard.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { vi } from "vitest"; +import { setBudgetMock, testBudget } from "../../setupTests"; import { StatCard } from "./StatCard"; describe("StatCard", () => { @@ -24,20 +25,40 @@ describe("StatCard", () => { }); it("triggers onChange when user changes input", async () => { + setBudgetMock.mockClear(); await userEvent.type(screen.getByLabelText("reserves"), "2"); + expect(setBudgetMock).toHaveBeenCalledWith( + { ...testBudget, stats: { ...testBudget.stats, reserves: 2 } }, + false, + ); expect(screen.getByDisplayValue("$2")).toBeInTheDocument(); await userEvent.clear(screen.getByTestId("goal-input")); await userEvent.type(screen.getByTestId("goal-input"), "95"); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + stats: { ...testBudget.stats, goal: 95, saved: 95, withGoal: -5 }, + }, + false, + ); expect(screen.getByDisplayValue("95")).toBeInTheDocument(); }); it("triggers onAutoGoal when user clicks button", async () => { + setBudgetMock.mockClear(); await userEvent.click( screen.getByRole("button", { name: "calculate savings goal" }), ); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + stats: { ...testBudget.stats, goal: 90, saved: 90, withGoal: 0 }, + }, + true, + ); }); it("triggers onShowGraphs when user clicks button", async () => { diff --git a/src/components/TableCard/TableCard.test.tsx b/src/components/TableCard/TableCard.test.tsx index 47a45774..bee0bfa6 100644 --- a/src/components/TableCard/TableCard.test.tsx +++ b/src/components/TableCard/TableCard.test.tsx @@ -1,26 +1,28 @@ import { cleanup, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { setBudgetMock, testBudget } from "../../setupTests"; import { TableCard } from "./TableCard"; describe("TableCard", () => { const comp = ; beforeEach(() => { + setBudgetMock.mockClear(); render(comp); }); it("matches snapshot", () => { expect(comp).toMatchSnapshot(); }); - it("renders initial Expenses state", () => { - expect(screen.getByDisplayValue("expense1")).toBeInTheDocument(); + it("renders initial Expenses state", async () => { + expect(await screen.findByDisplayValue("expense1")).toBeInTheDocument(); expect(screen.getByDisplayValue("$10")).toBeInTheDocument(); }); - it("renders initial Revenue state", () => { + it("renders initial Revenue state", async () => { render(); - expect(screen.getByDisplayValue("income1")).toBeInTheDocument(); - expect(screen.getByDisplayValue("$100")).toBeInTheDocument(); + expect(await screen.findByDisplayValue("income1")).toBeInTheDocument(); + expect(await screen.findByDisplayValue("$100")).toBeInTheDocument(); }); it("responds when user changes input", async () => { @@ -28,9 +30,36 @@ describe("TableCard", () => { expect(screen.getByDisplayValue("expense1change name")).toBeInTheDocument(); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + expenses: { + items: [{ id: 1, name: "expense1change name", value: 10 }], + total: 10, + }, + }, + false, + ); + setBudgetMock.mockClear(); + await userEvent.type(screen.getByDisplayValue("$10"), "123"); expect(screen.getByDisplayValue("$123")).toBeInTheDocument(); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + expenses: { + items: [{ id: 1, name: "expense1", value: 123 }], + total: 123, + }, + stats: { + ...testBudget.stats, + available: -23, + withGoal: -33, + }, + }, + false, + ); }); it("adds new Expense when user clicks adds new item button", async () => { @@ -38,6 +67,16 @@ describe("TableCard", () => { screen.getByRole("button", { name: "add item to Expenses" }), ); expect(screen.getByDisplayValue("$10")).toBeInTheDocument(); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + expenses: { + items: [...testBudget.expenses.items, { id: 2, name: "", value: 0 }], + total: 10, + }, + }, + true, + ); }); it("adds new Revenue when user clicks adds new item button", async () => { @@ -47,6 +86,16 @@ describe("TableCard", () => { screen.getByRole("button", { name: "add item to Revenue" }), ); expect(screen.getByDisplayValue("$100")).toBeInTheDocument(); + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + incomes: { + items: [...testBudget.incomes.items, { id: 3, name: "", value: 0 }], + total: 100, + }, + }, + true, + ); }); it("removes item when user clicks delete item button", async () => { @@ -56,5 +105,21 @@ describe("TableCard", () => { await userEvent.click( screen.getByRole("button", { name: "confirm item 1 deletion" }), ); + + expect(setBudgetMock).toHaveBeenCalledWith( + { + ...testBudget, + expenses: { + items: [], + total: 0, + }, + stats: { + ...testBudget.stats, + available: 100, + withGoal: 90, + }, + }, + true, + ); }); }); diff --git a/src/setupTests.ts b/src/setupTests.ts index 8bfc2d3a..55adece1 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -6,6 +6,7 @@ import "@testing-library/jest-dom"; import * as matchers from "@testing-library/jest-dom/matchers"; import { cleanup } from "@testing-library/react"; import { randomUUID } from "node:crypto"; +import { createElement } from "react"; import { afterEach, expect, vi } from "vitest"; import { Budget } from "./components/Budget/Budget"; import { ItemForm } from "./components/ItemForm/ItemForm"; @@ -20,6 +21,18 @@ vi.mock("crypto", () => ({ randomUUID: () => "035c2de4-00a4-403c-8f0e-f81339be9a4e", })); +// silence recharts ResponsiveContainer error +vi.mock("recharts", async (importOriginal) => { + const originalModule = await importOriginal(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + ...originalModule, + ResponsiveContainer: () => createElement("div"), + }; +}); + // extends Vitest's expect method with methods from react-testing-library expect.extend(matchers);