From bd94a8be98d4b6d9fbd0b10769048735cacbfa21 Mon Sep 17 00:00:00 2001 From: Molecule AI Core-FE Date: Fri, 15 May 2026 12:21:10 +0000 Subject: [PATCH] fix(canvas/test): wrap render() in act() for SettingsPanel open/close tests React state updates triggered by store subscriptions are not guaranteed to flush before the next synchronous assertion. On slower runners (CI cold start), getByTestId("secrets-tab") fires before the Dialog.Root open={isPanelOpen} effect resolves, causing a 5000ms timeout. Wrapping every render() that sets isPanelOpen=true inside act() ensures all pending effects and state updates flush before the assertion runs. Co-Authored-By: Claude Opus 4.7 --- .../settings/__tests__/SettingsPanel.test.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/canvas/src/components/settings/__tests__/SettingsPanel.test.tsx b/canvas/src/components/settings/__tests__/SettingsPanel.test.tsx index 35264aff..443cb341 100644 --- a/canvas/src/components/settings/__tests__/SettingsPanel.test.tsx +++ b/canvas/src/components/settings/__tests__/SettingsPanel.test.tsx @@ -122,33 +122,33 @@ describe("SettingsPanel — closed by default", () => { describe("SettingsPanel — open / close", () => { it("renders SecretsTab when panel is open", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); expect(screen.getByTestId("secrets-tab")).toBeTruthy(); expect(screen.getByText(/workspaceId=ws-xyz/i)).toBeTruthy(); }); it("renders TokensTab tab in tabs list", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); expect(screen.getByRole("tab", { name: /workspace tokens/i })).toBeTruthy(); }); it("renders Org API Keys tab in tabs list", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); expect(screen.getByRole("tab", { name: /org api keys/i })).toBeTruthy(); }); it("Secrets tab is default active", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); expect(screen.getByTestId("secrets-tab")).toBeTruthy(); expect(screen.getByRole("tab", { name: /secrets/i }).getAttribute("data-state")).toBe("active"); }); it("Tokens tab trigger exists with correct aria attributes", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); const tab = screen.getByRole("tab", { name: /workspace tokens/i }); // Radix Tabs.Trigger has role="tab" and aria-selected expect(tab).toBeTruthy(); @@ -161,14 +161,14 @@ describe("SettingsPanel — open / close", () => { it("Close button calls closePanel", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); fireEvent.click(screen.getByRole("button", { name: /close settings/i })); expect(mockClosePanel).toHaveBeenCalled(); }); it("calls fetchSecrets(workspaceId) when panel opens", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); expect(mockFetchSecrets).toHaveBeenCalledWith("ws-fetch-test"); }); }); @@ -179,7 +179,7 @@ describe("SettingsPanel — unsaved changes guard", () => { it("shows guard when panel closing with isAddFormOpen=true", () => { storeState.isPanelOpen = true; storeState.isAddFormOpen = true; - render(); + act(() => { render(); }); fireEvent.click(screen.getByRole("button", { name: /close settings/i })); expect(screen.getByTestId("unsaved-guard")).toBeTruthy(); }); @@ -187,7 +187,7 @@ describe("SettingsPanel — unsaved changes guard", () => { it("guard shows when editingKey is set (dirty form)", () => { storeState.isPanelOpen = true; storeState.editingKey = "GITHUB_TOKEN"; - render(); + act(() => { render(); }); fireEvent.click(screen.getByRole("button", { name: /close settings/i })); expect(screen.getByTestId("unsaved-guard")).toBeTruthy(); }); @@ -195,7 +195,7 @@ describe("SettingsPanel — unsaved changes guard", () => { it("'Keep editing' closes guard but panel stays open", () => { storeState.isPanelOpen = true; storeState.editingKey = "GITHUB_TOKEN"; - render(); + act(() => { render(); }); // Trigger close attempt fireEvent.click(screen.getByRole("button", { name: /close settings/i })); expect(screen.getByTestId("unsaved-guard")).toBeTruthy(); @@ -209,7 +209,7 @@ describe("SettingsPanel — unsaved changes guard", () => { it("'Discard' button on guard calls closePanel", () => { storeState.isPanelOpen = true; storeState.isAddFormOpen = true; - render(); + act(() => { render(); }); fireEvent.click(screen.getByRole("button", { name: /close settings/i })); fireEvent.click(screen.getByTestId("guard-discard")); expect(mockClosePanel).toHaveBeenCalled(); @@ -221,13 +221,13 @@ describe("SettingsPanel — unsaved changes guard", () => { describe("SettingsPanel — accessibility", () => { it("Dialog.Content has aria-label='Settings: API Keys'", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); expect(document.querySelector('[aria-label="Settings: API Keys"]')).toBeTruthy(); }); it("TabList has aria-label='Settings sections'", () => { storeState.isPanelOpen = true; - render(); + act(() => { render(); }); expect(document.querySelector('[aria-label="Settings sections"]')).toBeTruthy(); }); }); -- 2.52.0