diff --git a/apps/web/app/settings/workspace/[id]/automations/page.test.tsx b/apps/web/app/settings/workspace/[id]/automations/page.test.tsx new file mode 100644 index 0000000000..532630ce36 --- /dev/null +++ b/apps/web/app/settings/workspace/[id]/automations/page.test.tsx @@ -0,0 +1,49 @@ +import { cleanup, render, screen } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +// Regression coverage for the workspace-scoped +// /settings/workspace/[id]/automations page. +// +// The original implementation was an async server-style component that awaited +// a `params` promise in its render body (`async function AutomationsPage`). +// Rendered on the client under , React logged two React errors: +// " is an async Client Component. Only Server Components can be +// async" and "A component was suspended by an uncached promise". The page must +// be a plain synchronous client component that takes `workspaceId` directly and +// delegates data loading to . + +const { listPageSpy } = vi.hoisted(() => ({ listPageSpy: vi.fn() })); + +vi.mock("@/components/automations/automations-list-page", () => ({ + AutomationsListPage: (props: { workspaceId: string }) => { + listPageSpy(props); + return
{props.workspaceId}
; + }, +})); + +import AutomationsPage from "./page"; + +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +describe("AutomationsPage (workspace-scoped)", () => { + it("is a synchronous component, not an async one", () => { + // An async function's constructor name is "AsyncFunction"; a plain one is + // "Function". This is the core regression: an async client component throws. + expect(AutomationsPage.constructor.name).toBe("Function"); + }); + + it("renders the automations list for the given workspace without suspending", () => { + const element = render(); + + // Renders synchronously (no thrown promise / suspense fallback) and forwards + // the workspace id straight through to the list page. + expect(screen.getByTestId("automations-list-page").textContent).toBe("ws-42"); + expect(listPageSpy).toHaveBeenCalledTimes(1); + expect(listPageSpy).toHaveBeenCalledWith({ workspaceId: "ws-42" }); + + element.unmount(); + }); +}); diff --git a/apps/web/app/settings/workspace/[id]/automations/page.tsx b/apps/web/app/settings/workspace/[id]/automations/page.tsx index c3d929c175..fc80607658 100644 --- a/apps/web/app/settings/workspace/[id]/automations/page.tsx +++ b/apps/web/app/settings/workspace/[id]/automations/page.tsx @@ -1,10 +1,11 @@ +"use client"; + import { AutomationsListPage } from "@/components/automations/automations-list-page"; type Props = { - params: Promise<{ id: string }>; + workspaceId: string; }; -export default async function AutomationsPage({ params }: Props) { - const { id } = await params; - return ; +export default function AutomationsPage({ workspaceId }: Props) { + return ; } diff --git a/apps/web/src/settings-routes.tsx b/apps/web/src/settings-routes.tsx index bf6a512f78..d075263c27 100644 --- a/apps/web/src/settings-routes.tsx +++ b/apps/web/src/settings-routes.tsx @@ -355,7 +355,7 @@ function renderWorkspaceSettingsRoute(pathname: string) { if (section === "workflows") { return ; } - return ; + return ; } const workspaceId = matchSingle(pathname, /^\/settings\/workspace\/([^/]+)$/);