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