| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- import { describe, expect, it } from "vitest";
- import { createPlaytestSession, keydownInput, saveAllFlushes } from "../src/renderer/playtest-session.js";
- describe("saveAllFlushes", () => {
- it("writes clean flushes and reports them as flushed", async () => {
- const written: string[] = [];
- const result = await saveAllFlushes(
- [
- { path: "a.yaml", content: "x" },
- { path: "b.yaml", content: "y" },
- ],
- async (path, content) => {
- written.push(`${path}:${content}`);
- },
- );
- expect(result.flushed).toEqual(["a.yaml", "b.yaml"]);
- expect(result.failures).toEqual([]);
- expect(written).toEqual(["a.yaml:x", "b.yaml:y"]);
- });
- it("records error flushes and rejecting writes as failures without throwing", async () => {
- const result = await saveAllFlushes(
- [
- { path: "bad.yaml", error: "map invalid" },
- { path: "throws.yaml", content: "z" },
- { path: "ok.yaml", content: "ok" },
- ],
- async (path) => {
- if (path === "throws.yaml") throw new Error("disk full");
- },
- );
- expect(result.flushed).toEqual(["ok.yaml"]);
- expect(result.failures).toEqual([
- { path: "bad.yaml", error: "map invalid" },
- { path: "throws.yaml", error: "disk full" },
- ]);
- });
- it("writes nothing when there are no flushes", async () => {
- const result = await saveAllFlushes([], async () => {
- throw new Error("unreachable");
- });
- expect(result.flushed).toEqual([]);
- expect(result.failures).toEqual([]);
- });
- });
- describe("keydownInput", () => {
- it("maps arrow keys to raw inputs", () => {
- expect(keydownInput("ArrowUp")).toBe("key:ArrowUp");
- expect(keydownInput("ArrowDown")).toBe("key:ArrowDown");
- expect(keydownInput("ArrowLeft")).toBe("key:ArrowLeft");
- expect(keydownInput("ArrowRight")).toBe("key:ArrowRight");
- });
- it("returns undefined for non-arrow keys", () => {
- expect(keydownInput("a")).toBeUndefined();
- expect(keydownInput("Enter")).toBeUndefined();
- expect(keydownInput(" ")).toBeUndefined();
- });
- });
- describe("createPlaytestSession", () => {
- it("runs save-all → compile → boot → playing, then stop tears down", async () => {
- const phases: string[] = [];
- let booted: unknown = null;
- let tornDown = false;
- const session = createPlaytestSession({
- collectFlushes: () => [{ path: "m.yaml", content: "m" }],
- write: async () => {},
- compile: async () => ({ format: "nengine-bundle", version: 1, content: {} }),
- boot: async (bundle) => {
- booted = bundle;
- },
- teardown: async () => {
- tornDown = true;
- },
- onPhase: (phase) => phases.push(phase),
- });
- const result = await session.start();
- expect(result.ok).toBe(true);
- expect(result.saved).toEqual(["m.yaml"]);
- expect(booted).toEqual({ format: "nengine-bundle", version: 1, content: {} });
- expect(session.phase).toBe("playing");
- expect(phases).toEqual(["saving", "compiling", "booting", "playing"]);
- await session.stop();
- expect(tornDown).toBe(true);
- expect(session.phase).toBe("idle");
- expect(phases[phases.length - 1]).toBe("idle");
- });
- it("fails on an un-serializable model and never compiles", async () => {
- let compiled = false;
- const session = createPlaytestSession({
- collectFlushes: () => [{ path: "bad.yaml", error: "map invalid" }],
- write: async () => {},
- compile: async () => {
- compiled = true;
- throw new Error("nope");
- },
- boot: async () => {},
- teardown: async () => {},
- });
- const result = await session.start();
- expect(result.ok).toBe(false);
- expect(result.saved).toEqual([]);
- expect(result.error).toContain("bad.yaml");
- expect(result.error).toContain("map invalid");
- expect(compiled).toBe(false);
- expect(session.phase).toBe("idle");
- });
- it("fails on a write rejection", async () => {
- const session = createPlaytestSession({
- collectFlushes: () => [{ path: "a.yaml", content: "a" }],
- write: async () => {
- throw new Error("disk full");
- },
- compile: async () => {
- throw new Error("unreachable");
- },
- boot: async () => {},
- teardown: async () => {},
- });
- const result = await session.start();
- expect(result.ok).toBe(false);
- expect(result.saved).toEqual([]);
- expect(result.error).toContain("disk full");
- });
- it("fails on a compile error with the compile message", async () => {
- let booted = false;
- const session = createPlaytestSession({
- collectFlushes: () => [],
- write: async () => {},
- compile: async () => {
- throw new Error("content validation failed");
- },
- boot: async () => {
- booted = true;
- },
- teardown: async () => {},
- });
- const result = await session.start();
- expect(result.ok).toBe(false);
- expect(result.error).toContain("content validation failed");
- expect(booted).toBe(false);
- expect(session.phase).toBe("idle");
- });
- it("fails on a boot error with the boot message", async () => {
- const session = createPlaytestSession({
- collectFlushes: () => [],
- write: async () => {},
- compile: async () => ({ format: "nengine-bundle", version: 1, content: {} }),
- boot: async () => {
- throw new Error("no map content loaded");
- },
- teardown: async () => {},
- });
- const result = await session.start();
- expect(result.ok).toBe(false);
- expect(result.error).toContain("no map content loaded");
- expect(session.phase).toBe("idle");
- });
- it("rejects start while already running", async () => {
- let resolveBoot: (() => void) | undefined;
- const session = createPlaytestSession({
- collectFlushes: () => [],
- write: async () => {},
- compile: async () => ({ format: "nengine-bundle", version: 1, content: {} }),
- boot: async () => {
- await new Promise<void>((resolve) => {
- resolveBoot = resolve;
- });
- },
- teardown: async () => {},
- });
- const first = session.start();
- await Promise.resolve();
- const second = await session.start();
- expect(second.ok).toBe(false);
- expect(second.error).toBe("playtest already running");
- resolveBoot?.();
- await first;
- expect(session.phase).toBe("playing");
- });
- it("stop is a no-op when idle", async () => {
- let tornDown = false;
- const session = createPlaytestSession({
- collectFlushes: () => [],
- write: async () => {},
- compile: async () => ({ format: "nengine-bundle", version: 1, content: {} }),
- boot: async () => {},
- teardown: async () => {
- tornDown = true;
- },
- });
- await session.stop();
- expect(tornDown).toBe(false);
- expect(session.phase).toBe("idle");
- });
- });
|