151 lines
3.9 KiB
TypeScript
151 lines
3.9 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { generate, renderProps } from "../contracts/typegen/generator.ts";
|
|
import { Container } from "../core/container.ts";
|
|
import { Module } from "../decorators/Module.ts";
|
|
import { Service } from "../decorators/Service.ts";
|
|
import { Controller } from "../decorators/Controller.ts";
|
|
import { Route } from "../decorators/Route.ts";
|
|
import { validate, toFormFields } from "../core/form.ts";
|
|
|
|
/**
|
|
* Tests of the container, of the modules and of the type generator.
|
|
*/
|
|
|
|
describe("container", () => {
|
|
test("resolves a dependency by type", () => {
|
|
@Service()
|
|
class UserService {
|
|
find() {
|
|
return "ada";
|
|
}
|
|
}
|
|
|
|
@Controller("/")
|
|
class UserController {
|
|
constructor(private readonly users: UserService) {}
|
|
|
|
find() {
|
|
return this.users.find();
|
|
}
|
|
}
|
|
|
|
const container = new Container();
|
|
container.registerClass(UserService);
|
|
container.registerClass(UserController);
|
|
|
|
const controller = container.resolve(UserController);
|
|
expect(controller.find()).toBe("ada");
|
|
});
|
|
|
|
test("awaits an asynchronous provider", async () => {
|
|
@Service()
|
|
class AsyncService {
|
|
async load() {
|
|
return "loaded";
|
|
}
|
|
}
|
|
|
|
const container = new Container();
|
|
container.registerFactory(
|
|
AsyncService,
|
|
async () => new AsyncService(),
|
|
);
|
|
|
|
const service = await container.resolveAsync(AsyncService);
|
|
expect(await service.load()).toBe("loaded");
|
|
});
|
|
|
|
test("reports an unknown dependency", () => {
|
|
const container = new Container();
|
|
expect(() => container.resolve("missing")).toThrow("Cannot resolve");
|
|
});
|
|
});
|
|
|
|
describe("modules", () => {
|
|
test("collects the controllers of a module", () => {
|
|
@Controller("/")
|
|
class HomeController {
|
|
@Route("/")
|
|
index() {
|
|
return "home";
|
|
}
|
|
}
|
|
|
|
@Module({ controllers: [HomeController] })
|
|
class AppModule {}
|
|
|
|
const container = new Container();
|
|
container.registerClass(HomeController);
|
|
|
|
expect(AppModule).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("forms", () => {
|
|
test("reads the fields of a form", () => {
|
|
const fields = toFormFields({ title: "Hello", tags: ["a", "b"] });
|
|
expect(fields.get("title")).toBe("Hello");
|
|
expect(fields.has("tags")).toBe(true);
|
|
expect(fields.all().tags).toEqual(["a", "b"]);
|
|
});
|
|
|
|
test("validates the fields", () => {
|
|
const fields = toFormFields({ title: "Hello", age: "42" });
|
|
const result = validate<{ title: string; age: number }>(fields, {
|
|
title: { required: true, maxLength: 10 },
|
|
age: { type: "number", min: 18 },
|
|
});
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.value.age).toBe(42);
|
|
}
|
|
});
|
|
|
|
test("reports the invalid fields", () => {
|
|
const fields = toFormFields({ title: "", age: "abc" });
|
|
const result = validate(fields, {
|
|
title: { required: true },
|
|
age: { type: "number" },
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
expect(result.errors.title).toBe("title is required");
|
|
expect(result.errors.age).toBe("age must be a number");
|
|
});
|
|
});
|
|
|
|
describe("typegen", () => {
|
|
test("renders the props of a view", () => {
|
|
expect(renderProps([])).toBe("Record<string, never>");
|
|
expect(
|
|
renderProps([
|
|
{ name: "title", type: "string", optional: false },
|
|
{ name: "body", type: "string", optional: true },
|
|
]),
|
|
).toBe("{ title: string; body?: string }");
|
|
});
|
|
|
|
test("generates the contracts of the templates", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "muddle-typegen-"));
|
|
await writeFile(
|
|
join(root, "index.spug"),
|
|
"props\n title: string\n count?: number\n\np= title",
|
|
"utf8",
|
|
);
|
|
const result = await generate({
|
|
root,
|
|
out: join(root, "out"),
|
|
viewRoots: ["."],
|
|
});
|
|
expect(result.views).toHaveLength(1);
|
|
expect(result.views[0]?.key).toBe("index");
|
|
expect(result.views[0]?.props.map((prop) => prop.name)).toEqual([
|
|
"title",
|
|
"count",
|
|
]);
|
|
|
|
await rm(root, { recursive: true, force: true });
|
|
});
|
|
});
|