import { expect, test } from "bun:test"; import { parse } from "@wrnexus/syntax"; import { generateTargets } from "../src/targets.ts"; import { stripBrowserTypes } from "../src/browser-transpile.ts"; /** Build the browser module for a page whose client function body is TypeScript. */ function browserModuleFor(body: string): string { const source = `page Repro { functions { client async function run(): Promise { ${body} } } view {
} } `; return generateTargets(parse(source)).browser; } /** The artifact is written as .mjs, so this is how the runtime reads it back. */ function parsesAsJavaScript(code: string): boolean { try { new Function(code.replace(/^\s*import[^\n]*$/gm, "").replace(/\bexport\s+/g, "")); return true; } catch { return false; } } test("a client function body keeps its TypeScript in the generated module", () => { // Codegen strips the signature's types but copies the body verbatim, which is // what made this easy to miss. Guarding the premise the fix rests on. const generated = browserModuleFor(` const requestBody: Record = {}`); expect(generated).toContain("const requestBody: Record"); expect(parsesAsJavaScript(generated)).toBe(false); }); test("stripping types makes an annotated client function body valid JavaScript", () => { const stripped = stripBrowserTypes( browserModuleFor(` const requestBody: Record = {} requestBody.q = "x"`), ); expect(parsesAsJavaScript(stripped)).toBe(true); expect(stripped).not.toContain("Record"); expect(stripped).toContain("requestBody.q"); }); test("casts, generics and local interfaces survive stripping", () => { const stripped = stripBrowserTypes( browserModuleFor(` interface Local { a: string } const names: string[] = ["a"] const typed = { a: "x" } as Local const total = (1 as number) + names.length console.log(typed.a, total)`), ); expect(parsesAsJavaScript(stripped)).toBe(true); expect(stripped).toContain("console.log"); expect(stripped).not.toContain("interface Local"); }); test("the module's exported bindings are preserved", () => { // A transpile that dropped one of these would break hydration silently. const stripped = stripBrowserTypes( browserModuleFor(` const value: number = 1 console.log(value)`), ); for (const binding of [ "__wrnexusClientFunctions", "__wrnexusClientState", "__wrnexusOutputs", "__wrnexusImportedBindings", "bindClientScope", ]) { expect(stripped).toContain(binding); } }); test("a body with no TypeScript is left working", () => { const stripped = stripBrowserTypes( browserModuleFor(` const plain = { a: 1 } console.log(plain.a)`), ); expect(parsesAsJavaScript(stripped)).toBe(true); expect(stripped).toContain("console.log"); });