import test from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { initCoreSync } from "../web/core.ts"; import { startEngine } from "../web/app/engine.ts"; import { fresh, parseSave, VERSION } from "../web/app/save-format.ts"; import { store } from "../web/app/store.ts"; import { KEY, load, save, cancelSave } from "../web/app/storage.ts"; initCoreSync(readFileSync(new URL("../core/wasm/pkg/tokenling_core_bg.wasm", import.meta.url))); const raw = readFileSync(new URL("fixtures/save-v1.json", import.meta.url), "utf8"); const original = JSON.parse(raw); test("the previous browser save migrates to local mode without losing pet data", () => { assert.equal(VERSION(), 2); const loaded = parseSave(raw); assert.deepEqual(loaded, { ...original, v: 2, hosting: fresh().hosting }); const { hosting, ...preserved } = loaded; assert.deepEqual({ ...preserved, v: 1 }, original); assert.equal(hosting.mode, "local"); }); test("missing optional top-level fields are supplied without losing older settings", () => { const older = structuredClone(original); delete older.history; delete older.wokeAt; const loaded = parseSave(JSON.stringify(older)); assert.deepEqual(loaded.history, {}); assert.equal(loaded.wokeAt, 0); assert.deepEqual(loaded.settings, { chatter: true }); assert.equal(loaded.pet.name, "Mochi"); }); test("corrupt, incomplete and newer saves are rejected", () => { assert.throws(() => parseSave("not json")); assert.throws(() => parseSave('{"v":1}'), /missing/); assert.throws( () => parseSave(JSON.stringify({ ...original, v: 99 })), (error) => error.newer === true, ); }); test("bad saves, reset and loss of ownership block debounced writes", async () => { const entries = new Map([[KEY, JSON.stringify({ ...original, v: 99 })]]); const storage = { getItem: (k) => entries.get(k) ?? null, setItem: (k, v) => entries.set(k, v) }; Object.defineProperty(globalThis, "localStorage", { value: storage, configurable: true }); const untouched = entries.get(KEY); store.tabOwned = true; store.resetting = false; startEngine(load(), ""); save(); await new Promise((resolve) => setTimeout(resolve, 240)); assert.equal(store.badEgg.newer, true); assert.equal(entries.get(KEY), untouched); store.badEgg = null; startEngine(parseSave(raw), ""); save(); store.tabOwned = false; await new Promise((resolve) => setTimeout(resolve, 240)); assert.equal(entries.get(KEY), untouched); store.tabOwned = true; store.resetting = true; save(); await new Promise((resolve) => setTimeout(resolve, 240)); assert.equal(entries.get(KEY), untouched); store.resetting = false; save(); await new Promise((resolve) => setTimeout(resolve, 240)); assert.deepEqual(JSON.parse(entries.get(KEY)), { ...original, v: 2, hosting: fresh().hosting }); cancelSave(); delete globalThis.localStorage; });