From 1295fad187ee755165cc70b01a2d89209ac09736 Mon Sep 17 00:00:00 2001 From: Molecule AI Core-UIUX Date: Mon, 11 May 2026 10:09:01 +0000 Subject: [PATCH] fix(canvas/test): ApprovalBanner mockReset to prevent queue stacking vi.spyOn returns the same spy across beforeEach calls within a module. mockResolvedValueOnce queues a value per call, so prior rejections from previous tests leak into the current test's mock queue. Fix: use mockPost.mockReset().mockRejectedValue() to atomically clear the queue and set a fresh rejection, ensuring each test gets a clean post mock. Fixes: "keeps the card visible when the POST fails" (alert not found). All 16 ApprovalBanner tests pass. --- .../components/__tests__/ApprovalBanner.test.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/canvas/src/components/__tests__/ApprovalBanner.test.tsx b/canvas/src/components/__tests__/ApprovalBanner.test.tsx index 09817ef9..2a3fc758 100644 --- a/canvas/src/components/__tests__/ApprovalBanner.test.tsx +++ b/canvas/src/components/__tests__/ApprovalBanner.test.tsx @@ -41,9 +41,10 @@ const pendingApproval = (id = "a1", workspaceId = "ws-1"): { created_at: "2026-05-10T10:00:00Z", }); -// Shared spy reference so individual tests can call mockGet.mockRestore() -// without needing to pass it through beforeEach → it scope chain. +// Shared spy references so individual tests can reset or reject the POST mock +// without needing to call spyOn again (which would create a duplicate spy). let mockGet: ReturnType; +let mockPost: ReturnType; // ─── Tests ──────────────────────────────────────────────────────────────────── @@ -139,8 +140,8 @@ describe("ApprovalBanner — renders approval cards", () => { describe("ApprovalBanner — decisions", () => { beforeEach(() => { vi.useFakeTimers(); - vi.spyOn(api, "get").mockResolvedValueOnce([pendingApproval("a1")]); - vi.spyOn(api, "post").mockResolvedValue({}); + mockGet = vi.spyOn(api, "get").mockResolvedValueOnce([pendingApproval("a1")]); + mockPost = vi.spyOn(api, "post").mockResolvedValue({}); }); afterEach(() => { @@ -196,7 +197,7 @@ describe("ApprovalBanner — decisions", () => { }); it("shows an error toast when POST fails", async () => { - vi.mocked(api.post).mockRejectedValueOnce(new Error("Network error")); + mockPost.mockReset().mockRejectedValue(new Error("Network error")); render(); await act(async () => { await vi.runOnlyPendingTimersAsync(); }); fireEvent.click(screen.getAllByRole("button", { name: /approve/i })[0]); @@ -208,8 +209,9 @@ describe("ApprovalBanner — decisions", () => { }); it("keeps the card visible when the POST fails", async () => { - // Use mockRejectedValueOnce on the same spy as beforeEach (don't call spyOn again) - vi.mocked(api.post).mockRejectedValueOnce(new Error("Network error")); + // Reset the post mock before rejecting so the beforeEach's resolved value + // is gone and we get a clean rejection instead of a resolved→rejected queue. + mockPost.mockReset().mockRejectedValue(new Error("Network error")); render(); await act(async () => { await vi.runOnlyPendingTimersAsync(); }); fireEvent.click(screen.getAllByRole("button", { name: /approve/i })[0]);