import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { ContentLoader, ContentRegistry, particleSchema, shaderSchema, type ParticleDef, type ShaderDef, } from "../src/index.js"; async function setupLoader(): Promise<{ registry: ContentRegistry; loader: ContentLoader; tmp: string }> { const tmp = await mkdtemp(join(tmpdir(), "farm-particle-shader-")); await mkdir(join(tmp, "particles"), { recursive: true }); await mkdir(join(tmp, "shaders"), { recursive: true }); await writeFile(join(tmp, "particles", "dust.yaml"), "id: dust\nemitRate: 8\ncolors: ['#d7ccc8']\n"); await writeFile(join(tmp, "shaders", "rain-tint.yaml"), "id: rain-tint\nfilter: grayscale\nparams: { factor: 0.7 }\n"); const registry = new ContentRegistry(); const loader = new ContentLoader(registry); return { registry, loader, tmp }; } describe("particle content kind", () => { it("loads and validates particle YAML from content/particles/", async () => { const { registry, loader, tmp } = await setupLoader(); try { const report = await loader.load(tmp); expect(report.ok).toBe(true); expect(registry.ids("particle")).toEqual(["dust"]); expect(registry.get("particle", "dust")).toEqual({ id: "dust", emitRate: 8, colors: ["#d7ccc8"] }); } finally { await rm(tmp, { recursive: true, force: true }); } }); it("rejects an invalid particle def (bad color, negative speed, strict keys)", () => { expect(particleSchema.safeParse({ id: "bad", colors: ["green"] }).success).toBe(false); expect(particleSchema.safeParse({ id: "bad", speed: { min: -1, max: 1 } }).success).toBe(false); expect(particleSchema.safeParse({ id: "bad", emitRate: 0 }).success).toBe(false); expect(particleSchema.safeParse({ id: "bad", extra: true }).success).toBe(false); }); it("accepts an empty (defaults-only) def and a fully-specified def", () => { expect(particleSchema.safeParse({ id: "plain" }).success).toBe(true); expect( particleSchema.safeParse({ id: "rain", emitRate: 24, lifetime: { min: 600, max: 1000 }, speed: { min: 240, max: 360 }, angle: 90, spread: 8, gravity: 220, size: { min: 1, max: 2 }, colors: ["#64b5f6"], fade: true, }).success, ).toBe(true); }); }); describe("shader content kind", () => { it("loads and validates shader YAML from content/shaders/", async () => { const { registry, loader, tmp } = await setupLoader(); try { const report = await loader.load(tmp); expect(report.ok).toBe(true); expect(registry.ids("shader")).toEqual(["rain-tint"]); expect(registry.get("shader", "rain-tint")).toEqual({ id: "rain-tint", filter: "grayscale", params: { factor: 0.7 }, }); } finally { await rm(tmp, { recursive: true, force: true }); } }); it("rejects an invalid shader def (unknown filter, strict keys)", () => { expect(shaderSchema.safeParse({ id: "bad", filter: "vignette" }).success).toBe(false); expect(shaderSchema.safeParse({ id: "bad", filter: "blur", extra: true }).success).toBe(false); }); it("accepts every built-in filter name", () => { for (const filter of ["blur", "tint", "grayscale", "sepia", "hue", "brightness"]) { expect(shaderSchema.safeParse({ id: "x", filter }).success).toBe(true); } }); });