| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- 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,
- spriteSchema,
- soundSchema,
- type SoundDef,
- type SpriteDef,
- } from "../src/index.js";
- const SPRITE_YAML = [
- "id: parsnip-mature",
- "frames:",
- " - name: idle",
- " durationTicks: 1",
- " layers:",
- " - { shape: rect, x: 8, y: 8, w: 16, h: 16, fill: '#2e7d32' }",
- " - { shape: circle, x: 16, y: 8, r: 6, fill: '#66bb6a' }",
- "",
- ].join("\n");
- const SOUND_YAML = [
- "id: coin",
- "synth:",
- " wave: sine",
- " freq: 880",
- " durationMs: 120",
- " gain: 0.5",
- " slideTo: 1320",
- "",
- ].join("\n");
- async function setupLoader(): Promise<{ registry: ContentRegistry; loader: ContentLoader; tmp: string }> {
- const tmp = await mkdtemp(join(tmpdir(), "farm-sprite-sound-"));
- await mkdir(join(tmp, "sprites"), { recursive: true });
- await mkdir(join(tmp, "sounds"), { recursive: true });
- await writeFile(join(tmp, "sprites", "parsnip-mature.yaml"), SPRITE_YAML);
- await writeFile(join(tmp, "sounds", "coin.yaml"), SOUND_YAML);
- const registry = new ContentRegistry();
- const loader = new ContentLoader(registry);
- return { registry, loader, tmp };
- }
- describe("sprite content kind", () => {
- it("loads and validates sprite YAML from content/sprites/", async () => {
- const { registry, loader, tmp } = await setupLoader();
- try {
- const report = await loader.load(tmp);
- expect(report.ok).toBe(true);
- expect(registry.ids("sprite")).toEqual(["parsnip-mature"]);
- const sprite = registry.get<SpriteDef>("sprite", "parsnip-mature");
- expect(sprite).toEqual({
- id: "parsnip-mature",
- frames: [
- {
- name: "idle",
- durationTicks: 1,
- layers: [
- { shape: "rect", x: 8, y: 8, w: 16, h: 16, fill: "#2e7d32" },
- { shape: "circle", x: 16, y: 8, r: 6, fill: "#66bb6a" },
- ],
- },
- ],
- });
- } finally {
- await rm(tmp, { recursive: true, force: true });
- }
- });
- it("rejects an invalid sprite def (bad fill, unknown shape, strict unknown keys)", () => {
- expect(
- spriteSchema.safeParse({
- id: "bad",
- frames: [{ layers: [{ shape: "rect", x: 0, y: 0, w: 4, h: 4, fill: "green" }] }],
- }).success,
- ).toBe(false);
- expect(
- spriteSchema.safeParse({
- id: "bad",
- frames: [{ layers: [{ shape: "triangle", x: 0, y: 0, fill: "#ffffff" }] }],
- }).success,
- ).toBe(false);
- expect(
- spriteSchema.safeParse({
- id: "bad",
- frames: [{ layers: [{ shape: "rect", x: 0, y: 0, w: 4, h: 4, fill: "#ffffff", extra: true }] }],
- }).success,
- ).toBe(false);
- expect(spriteSchema.safeParse({ id: "bad", frames: [] }).success).toBe(false);
- });
- it("reports a bad sprite file as a loader error without crashing siblings", async () => {
- const { registry, loader, tmp } = await setupLoader();
- try {
- await writeFile(join(tmp, "sprites", "broken.yaml"), "id: broken\nframes: []\n");
- const report = await loader.load(tmp);
- expect(report.ok).toBe(false);
- const broken = report.items.find((i) => i.id === "broken");
- expect(broken?.status).toBe("error");
- expect(broken?.errors?.join(" ")).toMatch(/frames/);
- expect(registry.get("sprite", "parsnip-mature")).toBeDefined();
- expect(registry.get("sprite", "broken")).toBeUndefined();
- } finally {
- await rm(tmp, { recursive: true, force: true });
- }
- });
- });
- describe("sound content kind", () => {
- it("loads and validates sound YAML from content/sounds/", async () => {
- const { registry, loader, tmp } = await setupLoader();
- try {
- const report = await loader.load(tmp);
- expect(report.ok).toBe(true);
- expect(registry.ids("sound")).toEqual(["coin"]);
- const sound = registry.get<SoundDef>("sound", "coin");
- expect(sound).toEqual({
- id: "coin",
- mode: "sfx",
- synth: { wave: "sine", freq: 880, durationMs: 120, gain: 0.5, slideTo: 1320 },
- });
- } finally {
- await rm(tmp, { recursive: true, force: true });
- }
- });
- it("rejects an invalid sound def (neither synth nor src, bad wave, strict keys)", () => {
- expect(soundSchema.safeParse({ id: "silent" }).success).toBe(false);
- expect(soundSchema.safeParse({ id: "bad", synth: { wave: "noise", freq: 1, durationMs: 1 } }).success).toBe(false);
- expect(
- soundSchema.safeParse({ id: "bad", synth: { wave: "sine", freq: 1, durationMs: 1, extra: true } }).success,
- ).toBe(false);
- });
- it("accepts a src-only def and a synth+src def", () => {
- expect(soundSchema.safeParse({ id: "file-only", src: "sfx/coin.ogg" }).success).toBe(true);
- expect(
- soundSchema.safeParse({
- id: "both",
- synth: { wave: "square", freq: 440, durationMs: 50 },
- src: "sfx/coin.ogg",
- }).success,
- ).toBe(true);
- });
- });
|