import { test, expect } from "bun:test"; import { buildRouter, compileRoutePattern, fileToRoute, generateRoutesFile, matchRoute, sortRoutes, type Route, } from "../src/index.ts"; function route(raw: string): Route { return { raw, file: raw, ...compileRoutePattern(raw) }; } test("generateRoutesFile emits a Routes map with param types", () => { const code = generateRoutesFile([ route("/"), route("/about"), route("/users/[id]"), route("/docs/[[...slug]]"), ]); expect(code).toContain('"/": Record;'); expect(code).toContain('"/about": Record;'); expect(code).toContain('"/users/[id]": { "id": string };'); expect(code).toContain('"/docs/[[...slug]]": { "slug"?: string | readonly string[] };'); expect(code).toContain("export function href"); expect(code).toContain('"users.id": "/users/[id]";'); expect(code).toContain("export function route"); }); test("generated href() fills required, optional and catch-all params", async () => { const code = generateRoutesFile([ route("/"), route("/users/[id]"), route("/org/[org]/team/[team]"), route("/blog/[[page]]"), route("/docs/[...slug]"), ]); const { tmpdir } = await import("node:os"); const { writeFileSync, mkdirSync } = await import("node:fs"); const { join } = await import("node:path"); const { pathToFileURL } = await import("node:url"); const dir = join(tmpdir(), "wrnexus-routes-test"); mkdirSync(dir, { recursive: true }); const file = join(dir, `r${Date.now()}.ts`); writeFileSync(file, code); const mod = (await import(pathToFileURL(file).href)) as { href: (p: string, params?: Record) => string; route: ( name: string, params?: Record, query?: Record, ) => string; }; expect(mod.href("/")).toBe("/"); expect(mod.href("/users/[id]", { id: "42" })).toBe("/users/42"); expect(mod.href("/org/[org]/team/[team]", { org: "acme", team: "core" })).toBe( "/org/acme/team/core", ); expect(mod.href("/users/[id]", { id: "a b" })).toBe("/users/a%20b"); expect(mod.href("/blog/[[page]]")).toBe("/blog"); expect(mod.href("/docs/[...slug]", { slug: ["guide", "start here"] })).toBe( "/docs/guide/start%20here", ); expect(mod.route("users.id", { id: "42" }, { tab: "profile", compact: true })).toBe( "/users/42?tab=profile&compact=true", ); }); test("matches optional and catch-all routes", () => { expect(matchRoute([route("/blog/[[page]]")], "/blog")?.params).toEqual({}); expect(matchRoute([route("/blog/[[page]]")], "/blog/2")?.params).toEqual({ page: "2" }); expect(matchRoute([route("/docs/[...slug]")], "/docs/a/b")?.params).toEqual({ slug: "a/b" }); expect(matchRoute([route("/docs/[[...slug]]")], "/docs")?.params).toEqual({}); }); test("static routes sort ahead of dynamic and catch-all routes", () => { const sorted = sortRoutes([route("/docs/[...slug]"), route("/docs/[id]"), route("/docs/new")]); expect(sorted.map((item) => item.raw)).toEqual(["/docs/new", "/docs/[id]", "/docs/[...slug]"]); }); test("route groups and page/index filenames do not affect URLs", () => { expect(fileToRoute("(marketing)/pricing/page.wrn")).toBe("/pricing"); expect(fileToRoute("(dashboard)/index.wrn")).toBe("/"); }); test("malformed encoded route params return no match instead of throwing", () => { expect(matchRoute([route("/users/[id]")], "/users/%E0%A4%A")).toBeNull(); }); test("application routes override package routes with the same path", async () => { const { mkdtempSync, mkdirSync, rmSync, writeFileSync } = await import("node:fs"); const { tmpdir } = await import("node:os"); const { join } = await import("node:path"); const root = mkdtempSync(join(tmpdir(), "wrnexus-router-override-")); try { const appDir = join(root, "app"); const appRoute = join(appDir, "pages", "account.ts"); const packageRoute = join(root, "package-account.ts"); mkdirSync(join(appDir, "pages"), { recursive: true }); writeFileSync(appRoute, "export default function account() {}\n"); writeFileSync(packageRoute, "export default function packageAccount() {}\n"); const router = buildRouter(appDir, { externalRoutes: [{ kind: "page", path: "/account", entry: packageRoute }], }); expect(router.matchPage("/account")?.route.file).toBe(appRoute); expect(router.pages.filter((route) => route.raw === "/account")).toHaveLength(1); } finally { rmSync(root, { recursive: true, force: true }); } }); test("components use declaration names with case-insensitive overrides", async () => { const { mkdtempSync, mkdirSync, rmSync, writeFileSync } = await import("node:fs"); const { tmpdir } = await import("node:os"); const { join } = await import("node:path"); const root = mkdtempSync(join(tmpdir(), "wrnexus-router-components-")); try { const appDir = join(root, "app"); const componentsDir = join(root, "library-components"); mkdirSync(join(appDir, "pages"), { recursive: true }); mkdirSync(componentsDir, { recursive: true }); writeFileSync(join(appDir, "pages", "index.wrn"), "page Home { view {

Home

} }\n"); writeFileSync( join(componentsDir, "button.wrn"), 'component Button { view { } }\n', ); const router = buildRouter(appDir, { componentDirs: [componentsDir] }); expect(router.components).toHaveLength(1); expect(router.components[0]).toEqual({ name: "Button", file: join(componentsDir, "button.wrn"), }); } finally { rmSync(root, { recursive: true, force: true }); } });