import { mkdtemp, readFile, stat } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { createEngine } from "@nengine/core"; import { newProject, sanitizeId } from "../src/template.js"; async function tmpDir(): Promise { return mkdtemp(join(tmpdir(), "editor-template-")); } describe("sanitizeId", () => { it("slugs names and falls back for empty input", () => { expect(sanitizeId("My Farm!")).toBe("my-farm"); expect(sanitizeId(" 山谷 农场")).toBe("farm"); expect(sanitizeId("")).toBe("farm"); expect(sanitizeId("Turnip_Vale")).toBe("turnipvale"); }); }); describe("newProject", () => { it("generates the full template tree", async () => { const dir = await tmpDir(); try { const project = await newProject("Test Farm", dir); expect(project.hasContent).toBe(true); expect(project.hasMods).toBe(true); const expected = [ "content/time.yaml", "content/weather.yaml", "content/interact.yaml", "content/maps/test-farm.yaml", "content/items/hoe.yaml", "content/items/test-farm-seed.yaml", "content/items/test-farm.yaml", "content/crops/test-farm.yaml", "content/scenes/test-farm.yaml", ]; for (const rel of expected) { await expect(stat(join(dir, rel))).resolves.toBeDefined(); } await expect(stat(join(dir, "mods"))).resolves.toBeDefined(); } finally { await rmRecursive(dir); } }); it("boots the generated project through createEngine", async () => { const dir = await tmpDir(); try { const project = await newProject("Boot Farm", dir); const engine = createEngine({ contentDir: join(project.root, "content") }); await engine.start(); try { expect(engine.world.get("player")).toMatchObject({ id: "player", type: "player", x: 8, y: 8 }); const mapEntity = engine.world.query({ type: "map" })[0]; expect(mapEntity?.id).toBe("boot-farm"); const scenes = engine.registry.list("scene"); expect(scenes).toHaveLength(1); expect(scenes[0]?.id).toBe("boot-farm"); const crops = engine.registry.list("crop"); expect(crops[0]?.seedItem).toBe("boot-farm-seed"); expect(engine.registry.list("time")).toHaveLength(1); expect(engine.registry.list("weather")).toHaveLength(1); } finally { await engine.stop(); } } finally { await rmRecursive(dir); } }); it("interpolates the sanitized name into file ids", async () => { const dir = await tmpDir(); try { await newProject("Valley View", dir); const crop = await readFile(join(dir, "content/crops/valley-view.yaml"), "utf8"); expect(crop).toContain("id: valley-view"); expect(crop).toContain("seedItem: valley-view-seed"); const scene = await readFile(join(dir, "content/scenes/valley-view.yaml"), "utf8"); expect(scene).toContain("entities: []"); } finally { await rmRecursive(dir); } }); }); async function rmRecursive(dir: string): Promise { const { rm } = await import("node:fs/promises"); await rm(dir, { recursive: true, force: true }); } describe("template map layout", () => { it("lays out grass, water, and tilled tiles correctly", async () => { const dir = await tmpDir(); try { await newProject("Layout", dir); const { parse } = await import("yaml"); const map = parse(await readFile(join(dir, "content/maps/layout.yaml"), "utf8")) as { width: number; height: number; spawn: { x: number; y: number }; layers: { data: string[][] }[]; }; expect(map.width).toBe(12); expect(map.height).toBe(12); const data = map.layers[0]?.data ?? []; expect(data[0]?.[0]).toBe("water"); expect(data[2]?.[2]).toBe("water"); expect(data[4]?.[4]).toBe("tilled"); expect(data[5]?.[7]).toBe("tilled"); expect(data[11]?.[11]).toBe("grass"); expect(data[map.spawn.y]?.[map.spawn.x]).toBe("grass"); } finally { await rmRecursive(dir); } }); });