Files
moonlit-engine/tests/save.test.js
T

77 lines
2.5 KiB
JavaScript
Raw Normal View History

2026-10-18 00:29:36 +00:00
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { Engine } from "../src/core/engine.js";
import { SAVE_VERSION } from "../src/core/save.js";
const example = JSON.parse(readFileSync(new URL("../example/game.json", import.meta.url), "utf8"));
const fresh = () => {
let now = 0;
const e = new Engine(structuredClone(example), { clock: () => now });
e.newGame();
return { e, advance: (ms) => { now += ms; e.tick(ms); } };
};
test("save/load round-trip restores the whole world", () => {
const { e, advance } = fresh();
e.selectVerb("use");
e.click({ kind: "object", id: "mug", tags: ["container"] });
e.goTo("tavern");
e.world.setFlag("didSomething", true);
e.world.addVar("score", 42);
const save = e.save();
const e2 = new Engine(structuredClone(example), { clock: () => 0 });
e2.load(save);
assert.equal(e2.world.room, "tavern");
assert.deepEqual(e2.world.inventory, ["mug"]);
assert.equal(e2.world.flags.didSomething, true);
assert.equal(e2.world.vars.score, 42);
assert.equal(e2.phase, "playing");
});
test("save format is versioned and game-tagged", () => {
const { e } = fresh();
const data = JSON.parse(e.save());
assert.equal(data.version, SAVE_VERSION);
assert.equal(data.game, "skeleton-demo");
assert.ok(data.savedAt);
});
test("rejects saves from another game", () => {
const { e } = fresh();
const save = e.save();
const other = structuredClone(example);
other.meta.name = "different-game";
const e2 = new Engine(other, { clock: () => 0 });
assert.throws(() => e2.load(save), /save is for game/);
});
test("rejects future save versions", () => {
const { e } = fresh();
const data = JSON.parse(e.save());
data.version = 999;
assert.throws(() => e.load(JSON.stringify(data)), /newer than engine/);
});
test("rejects garbage saves", () => {
const { e } = fresh();
assert.throws(() => e.load("not json"), /not valid JSON/);
assert.throws(() => e.load("{}"), /no version/);
});
test("mid-dialogue save resumes at the saved node", () => {
const { e } = fresh();
e.goTo("tavern");
e.selectVerb("talk");
e.click({ kind: "character", id: "barkeep", tags: ["talker"] });
assert.equal(e.dialogue.current().node, "greet");
const save = e.save();
const e2 = new Engine(structuredClone(example), { clock: () => 0 });
e2.load(save);
assert.ok(e2.dialogue.active);
assert.equal(e2.dialogue.current().node, "greet");
assert.equal(e2.dialogueChoices().length, 2);
});