import { describe, expect, it } from "bun:test"; import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { __ManduClientBoundary, renderWithManduClientBoundaryManifest, } from "../../internal/client-boundary"; import { transformClientBoundaries, validateClientBoundaryExport, validateClientBoundaryServerOnlyImports, } from "../client-boundary-transform"; describe("transformClientBoundaries", () => { it("rewrites named client component JSX into an internal boundary", () => { const result = transformClientBoundaries( ` import { CommentsSection } from "./CommentsSection.client"; export default async function PledgePage({ comments }) { return
; } `, { routeId: "pledges-$id", fileName: "app/pledges/[id]/page.tsx", }, ); expect(result.transformed).toBe(true); expect(result.diagnostics).toEqual([]); expect(result.boundaries).toMatchObject([ { id: "pledges-$id--0", routeId: "pledges-$id", module: "./CommentsSection.client", importSpecifier: "./CommentsSection.client", exportName: "CommentsSection", localName: "CommentsSection", hydrate: "visible", ordinal: 0, propsSource: "inline", propsKeys: ["initialComments"], hasSpreadProps: false, }, ]); expect(result.code).toContain('import { __ManduClientBoundary } from "@mandujs/core/internal/client-boundary";'); expect(result.code).not.toContain("import { CommentsSection }"); expect(result.code).toContain("<__ManduClientBoundary"); expect(result.code).toContain('boundaryId="pledges-$id--0"'); expect(result.code).toContain('module="./CommentsSection.client"'); expect(result.code).toContain('exportName="CommentsSection"'); expect(result.code).toContain("initialComments: comments"); }); it("tracks default and namespace client exports in JSX order", () => { const result = transformClientBoundaries( ` import Profile from "./Profile.client"; import * as Widgets from "./widgets.client"; export default function Dashboard({ user }) { return (
); } `, { routeId: "dashboard", fileName: "app/dashboard/page.tsx", hydrate: "idle", }, ); expect(result.diagnostics).toEqual([]); expect(result.boundaries.map((boundary) => ({ id: boundary.id, module: boundary.module, importSpecifier: boundary.importSpecifier, exportName: boundary.exportName, localName: boundary.localName, hydrate: boundary.hydrate, propsKeys: boundary.propsKeys, hasSpreadProps: boundary.hasSpreadProps, }))).toEqual([ { id: "dashboard--0", module: "./Profile.client", importSpecifier: "./Profile.client", exportName: "default", localName: "Profile", hydrate: "idle", propsKeys: ["user"], hasSpreadProps: false, }, { id: "dashboard--1", module: "./widgets.client", importSpecifier: "./widgets.client", exportName: "ActivityFeed", localName: "Widgets.ActivityFeed", hydrate: "idle", propsKeys: ["userId"], hasSpreadProps: false, }, ]); expect(result.code).not.toContain("import Profile"); expect(result.code).not.toContain("import * as Widgets"); expect(result.code).toContain('boundaryId="dashboard--0"'); expect(result.code).toContain('boundaryId="dashboard--1"'); expect(result.code).toContain('exportName="default"'); expect(result.code).toContain('exportName="ActivityFeed"'); expect(result.code).toContain("userId: user.id"); }); it("reports unsupported children while still making the boundary explicit", () => { const result = transformClientBoundaries( ` import Card from "./Card.client"; export default function Page() { return server child; } `, { routeId: "card", fileName: "app/card/page.tsx", }, ); expect(result.transformed).toBe(true); expect(result.diagnostics).toMatchObject([ { code: "MANDU_BOUNDARY_UNSUPPORTED_CHILDREN", }, ]); expect(result.code).toContain("<__ManduClientBoundary"); expect(result.code).not.toContain(""); }); it("reports invalid HTML host contexts for compiler-owned boundaries", () => { const result = transformClientBoundaries( ` import RowActions from "./RowActions.client"; export default function Page() { return (
); } `, { routeId: "table-route", fileName: "app/table/page.tsx", }, ); expect(result.transformed).toBe(true); expect(result.diagnostics).toMatchObject([ { code: "MANDU_BOUNDARY_INVALID_HOST_CONTEXT", module: "./RowActions.client", exportName: "default", }, ]); expect(result.diagnostics[0]?.message).toContain(""); expect(result.diagnostics[0]?.suggestion).toContain("explicit island API"); expect(result.code).toContain("<__ManduClientBoundary"); }); it("reports unsupported function props and refs", () => { const result = transformClientBoundaries( ` import Button from "./Button.client"; export default function Page({ actionRef }) { return