particle-shader.test.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
  2. import { tmpdir } from "node:os";
  3. import { join } from "node:path";
  4. import { describe, expect, it } from "vitest";
  5. import {
  6. ContentLoader,
  7. ContentRegistry,
  8. particleSchema,
  9. shaderSchema,
  10. type ParticleDef,
  11. type ShaderDef,
  12. } from "../src/index.js";
  13. async function setupLoader(): Promise<{ registry: ContentRegistry; loader: ContentLoader; tmp: string }> {
  14. const tmp = await mkdtemp(join(tmpdir(), "farm-particle-shader-"));
  15. await mkdir(join(tmp, "particles"), { recursive: true });
  16. await mkdir(join(tmp, "shaders"), { recursive: true });
  17. await writeFile(join(tmp, "particles", "dust.yaml"), "id: dust\nemitRate: 8\ncolors: ['#d7ccc8']\n");
  18. await writeFile(join(tmp, "shaders", "rain-tint.yaml"), "id: rain-tint\nfilter: grayscale\nparams: { factor: 0.7 }\n");
  19. const registry = new ContentRegistry();
  20. const loader = new ContentLoader(registry);
  21. return { registry, loader, tmp };
  22. }
  23. describe("particle content kind", () => {
  24. it("loads and validates particle YAML from content/particles/", async () => {
  25. const { registry, loader, tmp } = await setupLoader();
  26. try {
  27. const report = await loader.load(tmp);
  28. expect(report.ok).toBe(true);
  29. expect(registry.ids("particle")).toEqual(["dust"]);
  30. expect(registry.get<ParticleDef>("particle", "dust")).toEqual({ id: "dust", emitRate: 8, colors: ["#d7ccc8"] });
  31. } finally {
  32. await rm(tmp, { recursive: true, force: true });
  33. }
  34. });
  35. it("rejects an invalid particle def (bad color, negative speed, strict keys)", () => {
  36. expect(particleSchema.safeParse({ id: "bad", colors: ["green"] }).success).toBe(false);
  37. expect(particleSchema.safeParse({ id: "bad", speed: { min: -1, max: 1 } }).success).toBe(false);
  38. expect(particleSchema.safeParse({ id: "bad", emitRate: 0 }).success).toBe(false);
  39. expect(particleSchema.safeParse({ id: "bad", extra: true }).success).toBe(false);
  40. });
  41. it("accepts an empty (defaults-only) def and a fully-specified def", () => {
  42. expect(particleSchema.safeParse({ id: "plain" }).success).toBe(true);
  43. expect(
  44. particleSchema.safeParse({
  45. id: "rain",
  46. emitRate: 24,
  47. lifetime: { min: 600, max: 1000 },
  48. speed: { min: 240, max: 360 },
  49. angle: 90,
  50. spread: 8,
  51. gravity: 220,
  52. size: { min: 1, max: 2 },
  53. colors: ["#64b5f6"],
  54. fade: true,
  55. }).success,
  56. ).toBe(true);
  57. });
  58. });
  59. describe("shader content kind", () => {
  60. it("loads and validates shader YAML from content/shaders/", async () => {
  61. const { registry, loader, tmp } = await setupLoader();
  62. try {
  63. const report = await loader.load(tmp);
  64. expect(report.ok).toBe(true);
  65. expect(registry.ids("shader")).toEqual(["rain-tint"]);
  66. expect(registry.get<ShaderDef>("shader", "rain-tint")).toEqual({
  67. id: "rain-tint",
  68. filter: "grayscale",
  69. params: { factor: 0.7 },
  70. });
  71. } finally {
  72. await rm(tmp, { recursive: true, force: true });
  73. }
  74. });
  75. it("rejects an invalid shader def (unknown filter, strict keys)", () => {
  76. expect(shaderSchema.safeParse({ id: "bad", filter: "vignette" }).success).toBe(false);
  77. expect(shaderSchema.safeParse({ id: "bad", filter: "blur", extra: true }).success).toBe(false);
  78. });
  79. it("accepts every built-in filter name", () => {
  80. for (const filter of ["blur", "tint", "grayscale", "sepia", "hue", "brightness"]) {
  81. expect(shaderSchema.safeParse({ id: "x", filter }).success).toBe(true);
  82. }
  83. });
  84. });