import { describe, expect, test } from "bun:test"; import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { App } from "../core/app.ts"; import { Controller, Module, Root, ViewRoot } from "../decorators/index.ts"; import { View } from "../decorators/View.ts"; import { TemplateEngine } from "../views/engine/runtime.ts"; import { Views } from "../views/api.ts"; /** * Tests of the file based router: the relative views, the static roots of the * controllers and the renderers that are applied inline. */ const dir = await mkdtemp(join(tmpdir(), "muddle-router-")); await mkdir(join(dir, "views"), { recursive: true }); await mkdir(join(dir, "public"), { recursive: true }); await writeFile( join(dir, "views/index.spug"), "props\n title: string\n\nh1= title", "utf8", ); await writeFile(join(dir, "views/about.spug"), "h2 About", "utf8"); await writeFile( join(dir, "views/card.spug"), "props\n title: string\n content?: () => string | Promise\n\narticle.card\n h3= title\n if content\n .card__content!= content()", "utf8", ); await writeFile(join(dir, "public/robots.txt"), "Allow: /", "utf8"); describe("file based router", () => { test("resolves the views of the view root", async () => { const engine = new TemplateEngine({ viewsDir: join(dir, "views"), root: dir, }); const html = await engine.render("index.spug", { title: "Hello" }); expect(html).toBe("

Hello

"); }); test("derives the route from the view", async () => { const html = await engine("about.spug"); expect(html).toBe("

About

"); async function engine(path: string): Promise { return new TemplateEngine({ viewsDir: join(dir, "views"), root: dir, }).render(path); } }); test("renders a renderer that is applied inline", async () => { const engine = new TemplateEngine({ viewsDir: join(dir, "views"), root: dir, }); const html = await engine.render("card.spug", { title: "Card", content: async () => "

inline

", }); expect(html).toContain("

Card

"); expect(html).toContain('

inline

'); }); test("serves the static root of a controller", async () => { @Controller("/assets") @Root(join(dir, "public")) class AssetController {} @Controller("/") @ViewRoot(join(dir, "views")) class PageController { @View("index.spug") index() { return { title: "Hello" }; } } @Module({ controllers: [AssetController, PageController] }) class AppModule {} const app = await App.create({ modules: [AppModule], views: new Views({ viewsDir: join(dir, "views"), root: dir }), }); const page = await app.fetch(new Request("http://localhost/")); expect(page.status).toBe(200); expect(await page.text()).toBe("

Hello

"); const asset = await app.fetch(new Request("http://localhost/assets/robots.txt")); expect(asset.status).toBe(200); expect(await asset.text()).toBe("Allow: /"); const missing = await app.fetch( new Request("http://localhost/assets/nope.txt"), ); expect(missing.status).toBe(404); await rm(dir, { recursive: true, force: true }); }); });