fix(canvas/test): remove unnecessary vi.restoreAllMocks from PurchaseSuccessModal
Some checks failed
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 8s
Harness Replays / detect-changes (pull_request) Failing after 10s
Harness Replays / Harness Replays (pull_request) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 11s
CI / Detect changes (pull_request) Successful in 22s
E2E API Smoke Test / detect-changes (pull_request) Successful in 25s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 27s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 27s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 25s
CI / Platform (Go) (pull_request) Successful in 6s
sop-tier-check / tier-check (pull_request) Successful in 13s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 7s
CI / Python Lint & Test (pull_request) Successful in 8s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 8s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 5s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 10s
CI / Canvas (Next.js) (pull_request) Failing after 4m59s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 7m22s

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 <noreply@anthropic.com>
This commit is contained in:
Molecule AI · app-fe 2026-05-11 12:53:15 +00:00
parent 05e6443e2c
commit 4beceb44a3
2 changed files with 9 additions and 18 deletions

View File

@ -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 () => {

View File

@ -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();
});