muddle/tests/router.test.ts
2026-10-23 09:12:49 -04:00

108 lines
3.2 KiB
TypeScript

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<string>\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("<h1>Hello</h1>");
});
test("derives the route from the view", async () => {
const html = await engine("about.spug");
expect(html).toBe("<h2>About</h2>");
async function engine(path: string): Promise<string> {
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 () => "<p>inline</p>",
});
expect(html).toContain("<h3>Card</h3>");
expect(html).toContain('<div class="card__content"><p>inline</p></div>');
});
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("<h1>Hello</h1>");
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 });
});
});