import { afterAll, beforeAll, expect, test } from "bun:test"; import { mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"; import { join, resolve } from "node:path"; import { routeNeedsIslands } from "../src/analysis.ts"; import { buildIslands, type IslandBuildResult } from "../src/island-bundle.ts"; const COUNTER = resolve(import.meta.dir, "../../../examples/basic-app/app/islands/Counter.tsx"); const created: string[] = []; afterAll(() => { for (const dir of created) rmSync(dir, { recursive: true, force: true }); }); function outDir(label: string): string { const dir = mkdtempSync(join(process.cwd(), `.island-int-${label}-`)); created.push(dir); return dir; } // Every assertion that needs a real bundle shares this one build. // // Not just for speed: `bun test` interferes with Bun.build's module reads once // several build calls have run across test files in the same process, while the // same calls succeed repeatedly outside the runner. Production is unaffected — // the dev server's rebuild loop was verified separately — but tests must keep // their build count low to stay reliable in the full suite. let dir: string; let result: IslandBuildResult; let bundles: string[]; beforeAll(async () => { dir = outDir("shared"); result = await buildIslands({ islands: [ { name: "CounterA", sourcePath: COUNTER }, { name: "CounterB", sourcePath: COUNTER }, ], outDir: dir, }); bundles = readdirSync(dir) .filter((file) => file.endsWith(".js")) .map((file) => readFileSync(join(dir, file), "utf8")); }); test("a route with no islands ships zero framework JavaScript", async () => { const empty = outDir("nojs"); const none = await buildIslands({ islands: [], outDir: empty }); expect(none.assets).toHaveLength(0); expect(none.sharedChunks).toHaveLength(0); expect(readdirSync(empty)).toHaveLength(0); expect(routeNeedsIslands([])).toBe(false); }); test("a page with multiple islands ships React exactly once", () => { // React's internals must appear in at most one emitted file — the shared // chunk. If splitting regresses, every island inlines its own copy. const withReactInternals = bundles.filter( (source) => source.includes("REACT_ELEMENT_TYPE") || source.includes("react.development"), ); expect(result.assets).toHaveLength(2); expect(withReactInternals.length).toBeLessThanOrEqual(1); }); test("two islands sharing one source get distinct, correctly named assets", () => { // Passing component sources as entrypoints deduped them, so the second island // silently lost its bundle and names could bind to the wrong output. expect(result.assets.map((asset) => asset.name).sort()).toEqual(["CounterA", "CounterB"]); expect(new Set(result.assets.map((asset) => asset.path)).size).toBe(2); }); test("a real island builds against React and never pulls in the WRNexus renderer", () => { const built = bundles.join("\n"); expect(built).not.toContain("wrnexus"); expect(built).not.toContain("react-dom/server"); expect(built).toContain("useState"); }); test("the generated entry directory is not left behind in the output", () => { expect(readdirSync(dir)).not.toContain(".entries"); });