import assert from "node:assert/strict"; import { test } from "node:test"; import { makeEngine } from "./helpers/renpy-engine.mjs"; const runtime = () => ({ game: { resolution: [640, 480], entry: "start", ui: {}, files: {}, defaults: [], init: [], characters: {}, channels: {}, images: { background: { file: "background.webp" } }, transforms: {}, atl: {}, transitions: {}, screens: {}, styles: {}, audio: {} }, modules: [{ labels: [{ name: "start", body: [{ op: "return" }] }], anchors: {} }] }); test("runtime image declarations produce scaled thumbnails and survive saving", () => { const { engine } = makeEngine(runtime()); try { engine.newStore(); engine.fileUrl = (file) => `/mock/${file}`; const image = engine.builtins.im.Scale(engine.builtins.ImageReference("background"), 384, 216); engine.builtins.renpy.image("thumbnail", image); const element = engine.screenImage("thumbnail"); assert.equal(element.style.width, "384px"); assert.equal(element.style.height, "216px"); assert.equal(element.src, "/mock/background.webp"); const saved = structuredClone(engine.snapshotState()); engine.newStore(); assert.equal(engine.resolveImage(["thumbnail"]), null); engine.applySave(saved); assert.deepEqual(engine.resolveImage(["thumbnail"]).def.props.size, [384, 216]); const before = Object.getPrototypeOf(engine.game.images); engine.builtins.renpy.image("__proto__", "safe.webp"); assert.equal(Object.getPrototypeOf(engine.game.images), before); } finally { engine.destroy(); } }); test("a missing original label stops execution instead of advancing into another branch", () => { const { engine } = makeEngine(runtime()); try { engine.cur = { label: "start", path: [0] }; assert.throws(() => engine.jump("missing_original_label"), /не найдена/); assert.deepEqual(engine.cur, { label: "start", path: [0] }); engine.screens.run(engine.builtins.Jump("missing_original_label"), null); assert.match(engine.notifyEl.textContent, /missing_original_label/); assert.equal(engine.running, false); engine.newStore(); assert.equal(engine.screens.layer.style.visibility, ""); } finally { engine.destroy(); } }); test("native Gallery instances keep their methods and registration target after a save", () => { const { engine } = makeEngine(runtime()); try { engine.newStore(); const cls = engine.builtins.Gallery; const gallery = engine.py.instantiate(cls, []); cls.find("button").value(gallery, "art"); cls.find("image").value(gallery, "background"); engine.store.gallery = gallery; const saved = structuredClone(engine.snapshotState()); engine.applySave(saved); const restored = engine.store.gallery; assert.equal(typeof engine.py.attrOf(restored, "make_button"), "function"); cls.find("condition").value(restored, "False"); assert.equal(cls.find("Action").value(restored, "art"), null); } finally { engine.destroy(); } }); test("an animated preview discarded before its first timer does not keep rendering", async () => { const { engine } = makeEngine(runtime()); let rendered = 0; try { engine.fileUrl = (file) => { rendered++; return `/mock/${file}`; }; engine.renderer.displayable({ repeat: true, frames: [ { img: { file: "a.webp" }, d: 0.01 }, { img: { file: "b.webp" }, d: 0.01 }, ] }); await new Promise((resolve) => setTimeout(resolve, 40)); assert.equal(rendered, 1); } finally { engine.destroy(); } });