Files
pgames-manager/frontend/tests/engine-behavior.test.mjs
T
mrleo1nidandClaude Sonnet 5.5 4dc9836a8b
CI / frontend (push) Failing after 19s
CI / backend (push) Failing after 40s
feat: mousearea, datetime dates, dir(), renpy.image_size and style.rebuild for Ren'Py games
Screens get an invisible mousearea that reports the pointer entering and leaving without taking
clicks. datetime.date/datetime/timedelta support construction and date arithmetic. dir() lists the
game's variables, renpy.style.rebuild() is a no-op, and renpy.image_size answers only for pictures
the browser already holds (otherwise an IOError the game can catch).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 03:02:13 +03:00

112 lines
5.2 KiB
JavaScript

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(); }
});
test("random generators: choices, sample, seeded Random and frozenset", () => {
const { engine } = makeEngine(runtime());
try {
engine.newStore();
const { renpy, frozenset } = engine.builtins;
assert.deepEqual(frozenset([1, 2, 2, 3]), [1, 2, 3]);
const picked = renpy.random.choices(["a", "b"], { __kw: { weights: [0, 1], k: 5 } });
assert.deepEqual(picked, ["b", "b", "b", "b", "b"]);
const sample = renpy.random.sample([1, 2, 3, 4], 3);
assert.equal(new Set(sample).size, 3);
const first = renpy.random.Random("seed"), second = renpy.random.Random("seed");
assert.deepEqual([first.randint(1, 1000), first.randint(1, 1000)], [second.randint(1, 1000), second.randint(1, 1000)]);
assert.ok(renpy.random.randrange(0, 10, 5) % 5 === 0);
} finally { engine.destroy(); }
});
test("datetime date arithmetic, dir() and style.rebuild", () => {
const { engine } = makeEngine(runtime());
try {
engine.newStore();
const { datetime, renpy, dir } = engine.builtins;
const start = datetime.date(2025, 1, 31);
const next = start.__binop__("+", datetime.timedelta({ __kw: { days: 1 } }));
assert.deepEqual([next.year, next.month, next.day], [2025, 2, 1]);
assert.equal(next.weekday(), 5); // Saturday
assert.throws(() => datetime.date(2025, 2, 30), /ValueError/);
const back = next.__binop__("-", datetime.timedelta({ __kw: { days: 32 } }));
assert.deepEqual([back.year, back.month, back.day], [2024, 12, 31]);
assert.equal(next.__binop__("-", start).days, 1);
engine.store.known_name = 1;
assert.ok(dir().includes("known_name"));
assert.equal(renpy.style.rebuild(), null);
assert.throws(() => renpy.image_size("missing"), /IOError/);
} finally { engine.destroy(); }
});