fix(canvas/test): consistent fake-timer state — fix ApprovalBanner test flakiness (#479)
CI / Canvas Deploy Reminder (push) Blocked by required conditions
Block internal-flavored paths / Block forbidden paths (push) Successful in 11s
Harness Replays / detect-changes (push) Successful in 15s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 10s
Harness Replays / Harness Replays (push) Successful in 5s
publish-canvas-image / Build & push canvas image (push) Failing after 39s
CI / Detect changes (push) Successful in 52s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 50s
Handlers Postgres Integration / detect-changes (push) Successful in 53s
E2E API Smoke Test / detect-changes (push) Successful in 55s
CI / Platform (Go) (push) Successful in 7s
CI / Shellcheck (E2E scripts) (push) Successful in 6s
CI / Python Lint & Test (push) Successful in 6s
Runtime PR-Built Compatibility / detect-changes (push) Successful in 58s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 6s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 10s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (push) Successful in 7s
main-red-watchdog / watchdog (push) Successful in 1m29s
CI / Canvas (Next.js) (push) Has been cancelled
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Has been cancelled
publish-workspace-server-image / build-and-push (push) Successful in 5m37s

Co-authored-by: Molecule AI App-FE <app-fe@agents.moleculesai.app>
Co-committed-by: Molecule AI App-FE <app-fe@agents.moleculesai.app>
This commit was merged in pull request #479.
This commit is contained in:
2026-05-11 14:04:04 +00:00
parent 7a731f6b42
commit 9ca86bee85
2 changed files with 9 additions and 18 deletions
@@ -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 () => {
@@ -44,7 +44,6 @@ async function waitForDialog() {
describe("PurchaseSuccessModal — render conditions", () => {
afterEach(() => {
cleanup();
vi.restoreAllMocks();
clearSearch();
});
@@ -113,8 +112,6 @@ describe("PurchaseSuccessModal — dismiss", () => {
afterEach(() => {
cleanup();
vi.restoreAllMocks();
vi.useRealTimers(); // ensure no fake timer leak
clearSearch();
});
@@ -170,7 +167,6 @@ describe("PurchaseSuccessModal — URL stripping", () => {
afterEach(() => {
cleanup();
vi.restoreAllMocks();
clearSearch();
});
@@ -196,13 +192,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();
});