import { afterEach, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { parse } from "@wrnexus/syntax"; import { generate } from "../src/codegen.ts"; const ROOT_TSCONFIG = join(import.meta.dir, "../../../tsconfig.json").replace(/\\/g, "/"); // The repo's own tsc, not a `bunx`-fetched one — `bunx tsc` can resolve an // unrelated TypeScript version that doesn't understand this repo's tsconfig // options (observed: it rejected `ignoreDeprecations: "6.0"` and couldn't // find the `bun` type-definition entry point), unlike `bun run typecheck`, // which uses this same local binary. const LOCAL_TSC = join(import.meta.dir, "../../../node_modules/.bin/tsc").replace(/\\/g, "/"); // `types`/`typeRoots` in an extended tsconfig resolve relative to the config // file that's actually invoked (our temp one), not the base file — so the // ambient `bun` types need an explicit path back to the repo's node_modules. const TYPE_ROOTS = join(import.meta.dir, "../../../node_modules/@types").replace(/\\/g, "/"); const roots: string[] = []; afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); }); /** * Runs the real TypeScript compiler over a generated server module. Proves * the emitted `__wrnexusSsrBindings` annotation (and everything else in the * module) actually type-checks — string-containment assertions alone can't * catch a declared type that omits a field every emitted object literal has. */ function typecheckGenerated(source: string): { ok: boolean; output: string } { const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-tsc-")); roots.push(root); const file = join(root, "page.ts"); writeFileSync(file, source); // Reuse the repo's own tsconfig (paths, lib, types, jsx, ...) so this only // checks the one file we care about instead of hand-duplicating the whole // compiler configuration (and drifting from it over time). writeFileSync( join(root, "tsconfig.json"), JSON.stringify({ extends: ROOT_TSCONFIG, compilerOptions: { noEmit: true, typeRoots: [TYPE_ROOTS] }, include: ["page.ts"], }), ); const result = Bun.spawnSync([LOCAL_TSC, "--project", join(root, "tsconfig.json")], { cwd: root, stdout: "pipe", stderr: "pipe", }); const output = `${result.stdout?.toString() ?? ""}${result.stderr?.toString() ?? ""}`; return { ok: result.exitCode === 0, output }; } function serverModule(inner: string): string { return generate( parse(`page Repro { ssr { ${inner} } view {

loading

} } `), ); } test("a sectioned ssr block binds the payload to data", () => { const generated = serverModule(` api ssrUsers GET /api/users { response { return data.users.length } }`); expect(generated).toContain("data.users.length"); }); test("a legacy ssr block is unchanged", () => { const generated = serverModule(` api ssrUsers GET /api/users { return users.length }`); expect(generated).toContain("users.length"); }); test("an ssr block with an error section emits the error body and binds status/message/data", () => { const generated = serverModule(` api ssrUsers GET /api/users { response { return data.users.length } error { return message + status + data } }`); expect(generated).toContain('"errorBody"'); expect(generated).toContain("return message + status + data"); expect(generated).toContain("const status = $status"); expect(generated).toContain("const message = $message"); expect(generated).toContain("const data = $data"); expect(generated).toContain("__wrnexusEvalError"); }); test("an ssr block without an error section emits no catch entry for that binding", () => { const generated = serverModule(` api ssrUsers GET /api/users { response { return data.users.length } }`); expect(generated).not.toContain('"errorBody"'); }); test("tsc: a sectioned ssr block's generated module has no diagnostics", () => { const generated = serverModule(` api ssrUsers GET /api/users { response { return data.users.length } error { return message + status + data } }`); const { ok, output } = typecheckGenerated(generated); expect(output.trim()).toBe(""); expect(ok).toBe(true); }); test("an ssr block used in {#each} with an error section runs the error body on failure", async () => { const generated = generate( parse(`page Repro { ssr { api ssrUsers GET /api/users { response { return data.users } error { return ["fallback"] } } } view {
{#each ssrUsers as u}

{u}

{/each}
} } `), ); const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-each-")); roots.push(root); mkdirSync(root, { recursive: true }); const file = join(root, "page.ts"); writeFileSync(file, generated); const mod = await import(`${file.replace(/\\/g, "/")}?t=${Date.now()}`); const html = await mod.default({ req: { url: "http://localhost/", headers: new Headers() }, cookies: {}, session: {}, localStorage: {}, __wrnexusCallApi: async () => { throw new Error("boom"); }, }); expect(html).toContain("fallback"); }); test("an ssr block's response body error is not swallowed by the error section", async () => { const generated = generate( parse(`page Repro { ssr { api ssrUsers GET /api/users { response { return data.users.missing.length } error { return ["fallback"] } } } view {
{#each ssrUsers as u}

{u}

{/each}
} } `), ); const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-response-throws-")); roots.push(root); mkdirSync(root, { recursive: true }); const file = join(root, "page.ts"); writeFileSync(file, generated); const mod = await import(`${file.replace(/\\/g, "/")}?t=${Date.now()}`); await expect( mod.default({ req: { url: "http://localhost/", headers: new Headers() }, cookies: {}, session: {}, localStorage: {}, __wrnexusCallApi: async () => ({ users: [] }), }), ).rejects.toThrow(); }); test("an ssr block still runs the error body on a genuine transport failure", async () => { const generated = generate( parse(`page Repro { ssr { api ssrUsers GET /api/users { response { return data.users.length } error { return ["fallback"] } } } view {
{#each ssrUsers as u}

{u}

{/each}
} } `), ); const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-transport-fails-")); roots.push(root); mkdirSync(root, { recursive: true }); const file = join(root, "page.ts"); writeFileSync(file, generated); const mod = await import(`${file.replace(/\\/g, "/")}?t=${Date.now()}`); const html = await mod.default({ req: { url: "http://localhost/", headers: new Headers() }, cookies: {}, session: {}, localStorage: {}, __wrnexusCallApi: async () => { throw new Error("boom"); }, }); expect(html).toContain("fallback"); }); test("an ssr block used in {#each} without an error section still propagates a failure", async () => { const generated = generate( parse(`page Repro { ssr { api ssrUsers GET /api/users { response { return data.users } } } view {
{#each ssrUsers as u}

{u}

{/each}
} } `), ); const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-each-propagate-")); roots.push(root); mkdirSync(root, { recursive: true }); const file = join(root, "page.ts"); writeFileSync(file, generated); const mod = await import(`${file.replace(/\\/g, "/")}?t=${Date.now()}`); await expect( mod.default({ req: { url: "http://localhost/", headers: new Headers() }, cookies: {}, session: {}, localStorage: {}, __wrnexusCallApi: async () => { throw new Error("boom"); }, }), ).rejects.toThrow("boom"); });