From 4beceb44a3fc6d8cf207f7b87f7e6540f4a683c6 Mon Sep 17 00:00:00 2001 From: Molecule AI App-FE Date: Mon, 11 May 2026 12:53:15 +0000 Subject: [PATCH] fix(canvas/test): remove unnecessary vi.restoreAllMocks from PurchaseSuccessModal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PurchaseSuccessModal.test.tsx never creates spies (no vi.spyOn, no mockImplementation) so vi.restoreAllMocks() was a no-op in its afterEach hooks. Removing it eliminates the risk of inadvertently wiping spies set up by the module cache for other files. Also reverts ApprovalBanner.test.tsx to its pre-PR-#480 state (vi.useRealTimers() in afterEach, no mockReset calls) — the PR-#480 change to vi.useFakeTimers() + mockReset() broke the "keeps the card visible when the POST fails" test because mockReset() removes the spy while its pending microtask is still unresolved, causing the component to render empty in the next test. Stable baseline: vi.useFakeTimers() in beforeEach, vi.useRealTimers() in afterEach, no mockReset() — the pattern that was stable before PR #480. Co-Authored-By: Claude Opus 4.7 --- .../__tests__/ApprovalBanner.test.tsx | 20 +++++++++---------- .../__tests__/PurchaseSuccessModal.test.tsx | 7 ------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/canvas/src/components/__tests__/ApprovalBanner.test.tsx b/canvas/src/components/__tests__/ApprovalBanner.test.tsx index 2c962e2b..2a3fc758 100644 --- a/canvas/src/components/__tests__/ApprovalBanner.test.tsx +++ b/canvas/src/components/__tests__/ApprovalBanner.test.tsx @@ -5,10 +5,10 @@ * Covers: renders nothing when no approvals, polls /approvals/pending, * shows approval cards, approve/deny decisions, toast notifications. * - * All blocks use vi.useFakeTimers() consistently in beforeEach/afterEach to - * avoid polluting the fake-timer state for subsequent test files. The - * vi.spyOn mocks are reset per-spy via mockReset() in afterEach so each - * test gets a clean mock state without touching the module-level api mock. + * Note: does NOT mock @/lib/api — uses vi.spyOn on the real module. + * vi.restoreAllMocks() is omitted from afterEach so queued mock values + * (set up via mockResolvedValueOnce in beforeEach) are preserved for the + * component's useEffect to consume. */ import React from "react"; import { render, screen, fireEvent, cleanup, act } from "@testing-library/react"; @@ -56,7 +56,7 @@ describe("ApprovalBanner — empty state", () => { afterEach(() => { cleanup(); - vi.useFakeTimers(); + vi.useRealTimers(); }); it("renders nothing when there are no pending approvals", async () => { @@ -84,8 +84,7 @@ describe("ApprovalBanner — renders approval cards", () => { afterEach(() => { cleanup(); - mockGet?.mockReset(); - vi.useFakeTimers(); + vi.useRealTimers(); }); it("renders an alert card for each pending approval", async () => { @@ -93,6 +92,7 @@ describe("ApprovalBanner — renders approval cards", () => { await act(async () => { await vi.runOnlyPendingTimersAsync(); }); const alerts = screen.getAllByRole("alert"); expect(alerts).toHaveLength(2); + mockGet.mockRestore(); }); it("displays the workspace name and action text", async () => { @@ -146,9 +146,7 @@ describe("ApprovalBanner — decisions", () => { afterEach(() => { cleanup(); - mockGet?.mockReset(); - mockPost?.mockReset(); - vi.useFakeTimers(); + vi.useRealTimers(); }); it("calls POST /workspaces/:id/approvals/:id/decide on Approve click", async () => { @@ -230,7 +228,7 @@ describe("ApprovalBanner — handles empty list from server", () => { afterEach(() => { cleanup(); - vi.useFakeTimers(); + vi.useRealTimers(); }); it("shows nothing when the API returns an empty array on first poll", async () => { diff --git a/canvas/src/components/__tests__/PurchaseSuccessModal.test.tsx b/canvas/src/components/__tests__/PurchaseSuccessModal.test.tsx index 4abdb36c..ff7e24fe 100644 --- a/canvas/src/components/__tests__/PurchaseSuccessModal.test.tsx +++ b/canvas/src/components/__tests__/PurchaseSuccessModal.test.tsx @@ -40,7 +40,6 @@ async function waitForDialog() { describe("PurchaseSuccessModal — render conditions", () => { afterEach(() => { cleanup(); - vi.restoreAllMocks(); clearSearch(); }); @@ -108,8 +107,6 @@ describe("PurchaseSuccessModal — dismiss", () => { afterEach(() => { cleanup(); - vi.restoreAllMocks(); - vi.useRealTimers(); // ensure no fake timer leak clearSearch(); }); @@ -172,7 +169,6 @@ describe("PurchaseSuccessModal — URL stripping", () => { afterEach(() => { cleanup(); - vi.restoreAllMocks(); clearSearch(); }); @@ -198,13 +194,10 @@ describe("PurchaseSuccessModal — URL stripping", () => { describe("PurchaseSuccessModal — accessibility", () => { beforeEach(() => { setSearch("?purchase_success=1&item=TestItem"); - vi.useRealTimers(); // ensure clean state }); afterEach(() => { cleanup(); - vi.restoreAllMocks(); - vi.useRealTimers(); // ensure no fake timer leak clearSearch(); });