import { afterEach, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { basename, join } from "node:path"; import { tmpdir } from "node:os"; import { buildRouter } from "@wrnexus/router"; import { resolveI18n } from "@wrnexus/i18n"; import { createHandlers, type RuntimeDeps } from "../src/runtime.ts"; const roots: string[] = []; afterEach(() => roots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true }))); test("a component nested in another component's slot keeps its boundary", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-component-slot-")); roots.push(root); const app = join(root, "app"); mkdirSync(join(app, "pages"), { recursive: true }); mkdirSync(join(app, "components"), { recursive: true }); writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n"); writeFileSync(join(app, "components/Card.wrn"), "component Card { view { } }\n"); writeFileSync(join(app, "components/Badge.wrn"), "component Badge { view { } }\n"); const handlers = createHandlers({ mode: "development", hmr: false, router: buildRouter(app), loadModule: async (file) => { if (basename(file) === "Card.wrn") { return { render: () => '
' }; } if (basename(file) === "Badge.wrn") { return { render: (props: Record) => `${props.label}` }; } return { default: () => '
', }; }, getMiddleware: async () => [], assets: { serve: async () => null }, } satisfies RuntimeDeps); const response = await handlers.fetch(new Request("https://example.test/"), { upgrade: () => false, }); const html = await response!.text(); expect(html).toContain('
'); expect(html).toContain('x'); expect(html).not.toContain('data-component="Badge"'); }); test("a component in an initially hidden client branch is server-prepared for instantiation", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-component-branch-")); roots.push(root); const app = join(root, "app"); mkdirSync(join(app, "pages"), { recursive: true }); mkdirSync(join(app, "components"), { recursive: true }); writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n"); writeFileSync(join(app, "components/Input.wrn"), "component Input { view { } }\n"); const definition = Buffer.from( JSON.stringify([{ cond: "open", body: '
' }]), "utf8", ).toString("base64"); const handlers = createHandlers({ mode: "development", hmr: false, router: buildRouter(app), loadModule: async (file) => basename(file) === "Input.wrn" ? { render: (props: Record) => ``, } : { default: () => ``, }, getMiddleware: async () => [], assets: { serve: async () => null }, } satisfies RuntimeDeps); const response = await handlers.fetch(new Request("https://example.test/"), { upgrade: () => false, }); const html = await response!.text(); const encoded = /data-wrn-if="([A-Za-z0-9+/=]+)"/.exec(html)?.[1]; expect(encoded).toBeTruthy(); const branches = JSON.parse(Buffer.from(encoded!, "base64").toString("utf8")); expect(branches[0].body).toContain(''); expect(branches[0].body).not.toContain("data-component="); }); test("SSR carries a parent prop binding onto the rendered child scope", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-component-prop-")); roots.push(root); const app = join(root, "app"); mkdirSync(join(app, "pages"), { recursive: true }); mkdirSync(join(app, "components"), { recursive: true }); writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n"); writeFileSync( join(app, "components/Child.wrn"), "component Child { props { value = 0 } view { {value} } }\n", ); const marker = "["value","{count}"]"; const handlers = createHandlers({ mode: "development", hmr: false, router: buildRouter(app), loadModule: async (file) => basename(file) === "Child.wrn" ? { render: () => '
1
' } : { default: () => `
`, }, getMiddleware: async () => [], assets: { serve: async () => null }, } satisfies RuntimeDeps); const response = await handlers.fetch(new Request("https://example.test/"), { upgrade: () => false, }); const html = await response!.text(); expect(html).toContain(`data-wrn-prop-bind-0="${marker}"`); expect(html).not.toContain('data-component="Child"'); }); test("translated text is present in the raw server response", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-i18n-")); roots.push(root); const app = join(root, "app"); mkdirSync(join(app, "pages"), { recursive: true }); writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n"); const handlers = createHandlers({ mode: "development", hmr: false, router: buildRouter(app), i18n: resolveI18n({ en: { nav: { title: "Navigation" } } }, { default: "en" }), loadModule: async () => ({ default: () => '', }), getMiddleware: async () => [], assets: { serve: async () => null }, } satisfies RuntimeDeps); const response = await handlers.fetch(new Request("https://example.test/"), { upgrade: () => false, }); const html = await response!.text(); expect(html).toContain('Navigation'); }); test("translated layout text survives document-template rendering", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-layout-ssr-i18n-")); roots.push(root); const app = join(root, "app"); mkdirSync(join(app, "pages"), { recursive: true }); mkdirSync(join(app, "layouts"), { recursive: true }); writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n"); writeFileSync(join(app, "layouts/public.wrn"), "layout Public { view { } }\n"); writeFileSync( join(app, "layouts/document.wrn"), "layout Document { view { } }\n", ); const handlers = createHandlers({ mode: "development", hmr: false, router: buildRouter(app), i18n: resolveI18n({ en: { nav: { home: "Home" } } }, { default: "en" }), loadModule: async (file) => { if (basename(file) === "public.wrn") { return { render: () => '' }; } if (basename(file) === "document.wrn") { return { render: () => "" }; } return { default: () => "
Page
", layout: "public" }; }, getMiddleware: async () => [], assets: { serve: async () => null }, } satisfies RuntimeDeps); const response = await handlers.fetch(new Request("https://example.test/"), { upgrade: () => false, }); const html = await response!.text(); expect(html).toContain('Home'); expect(html).not.toContain(''); });