aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/workbench/presentation.test.mjs
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/workbench/presentation.test.mjs
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/workbench/presentation.test.mjs')
-rw-r--r--prototypes/workbench/presentation.test.mjs132
1 files changed, 132 insertions, 0 deletions
diff --git a/prototypes/workbench/presentation.test.mjs b/prototypes/workbench/presentation.test.mjs
new file mode 100644
index 0000000..98bef6e
--- /dev/null
+++ b/prototypes/workbench/presentation.test.mjs
@@ -0,0 +1,132 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { readFile } from 'node:fs/promises';
+import { createRuntime } from './web/runtime.mjs';
+import { createRuntime as before } from './results/2026-09-23-presentation/before/web/runtime.mjs';
+
+const wasm = await readFile(new URL('./web/core.wasm', import.meta.url));
+const source = `state x = 3
+state radius = 5
+input move = KeyM
+input shrink = KeyS
+box tile(x, 10, x * 2, 20, "#123456")
+circle dot(50, 50, radius, "#abcdef")
+text label(0, 0, 10 / x, "Value: ")
+on move.down { x = x + 0.25 }
+on shrink.down { radius = radius - 10 }
+on frame { x = x + dt }
+`;
+
+// The archived adapter predates route diagnostics. These legacy fixtures have
+// no routes; keep all original fields in the equality comparison.
+function archivedSnapshot(runtime) {
+ const value = runtime.snapshot();
+ value.memory.pointerTargetBytes = 3;
+ value.memory.pointerTargets = 0;
+ return value;
+}
+
+// Reconstruct only in tests, for comparison with the archived snapshot path.
+function collect(runtime, hooks = {}) {
+ let states, views, declared;
+ const result = runtime.present({
+ begin(program) {
+ declared = program; states = {}; views = [];
+ hooks.begin?.();
+ },
+ state(i, value) { states[declared.states[i].name] = value; },
+ view(i, valid) {
+ if (!valid) return;
+ const {id, type, color, prefix} = declared.views[i];
+ views.push({id, type, color, prefix});
+ },
+ value(i, name, value) { views.at(-1)[name] = value; hooks.value?.(); },
+ end(count) { assert.equal(views.length, count); },
+ });
+ return {states, views, ...result};
+}
+
+test('direct publication matches archived snapshots and work counts through authored changes', async () => {
+ for (const cacheEntries of [0, 1, 32]) {
+ const left = await createRuntime(wasm, {cacheEntries}), right = await before(wasm, {cacheEntries});
+ const compare = () => {
+ assert.deepEqual(collect(left), archivedSnapshot(right));
+ assert.deepEqual(left.evaluationStats(), right.evaluationStats());
+ };
+ compare();
+ for (const r of [left, right]) r.apply(source);
+ compare();
+ for (let i = 0; i < 120; i++) {
+ for (const r of [left, right]) {
+ r.down('move', 'keyboard'); r.down('move', 'pointer');
+ r.up('move', 'keyboard'); r.up('move', 'pointer');
+ if (i % 11 === 0) r.frame(2);
+ if (i % 17 === 0) r.forgetEvaluation('history');
+ if (i % 23 === 0) r.reset();
+ }
+ compare();
+ }
+ for (const r of [left, right]) { r.down('shrink'); r.up('shrink'); }
+ compare(); // invalid circle only; other views still publish
+ for (const r of [left, right]) assert.throws(() => r.apply('state broken ='));
+ compare();
+ for (const r of [left, right]) r.apply('state radius = 1\nstate x = 0\ntext label(1, 2, x)');
+ compare();
+ for (const r of [left, right]) r.apply('');
+ compare();
+ }
+});
+
+test('whole-scene budget failure never publishes partial bindings and recovers exactly', async () => {
+ const left = await createRuntime(wasm, {cacheEntries:32}), right = await before(wasm, {cacheEntries:32});
+ for (const r of [left, right]) r.apply(source);
+ for (let workLimit = 0; workLimit <= 80; workLimit++) {
+ for (const r of [left, right]) r.configureEvaluation({workLimit});
+ let calls = 0;
+ const result = collect(left, {value: () => calls++});
+ assert.deepEqual(result, archivedSnapshot(right));
+ assert.deepEqual(left.evaluationStats(), right.evaluationStats());
+ if (result.displayError?.code === 101) assert.equal(calls, 0);
+ else assert.equal(calls, 10);
+ }
+});
+
+test('latched event rollback, nonfinite bindings, reset and signed zero keep their semantics', async () => {
+ const left = await createRuntime(wasm), right = await before(wasm);
+ const definition = 'state x = 0\ninput fail = Space\ntext value(0, 0, 1 / x)\non fail.down { x = 2\nx = 1 / 0 }';
+ for (const r of [left, right]) { r.apply(definition); r.down('fail'); r.up('fail'); }
+ assert.deepEqual(collect(left), archivedSnapshot(right));
+ for (const r of [left, right]) r.configureEvaluation({workLimit:0});
+ assert.deepEqual(collect(left), archivedSnapshot(right));
+ for (const r of [left, right]) {
+ r.apply('state x = -0\ntext value(x, 0, x)'); r.reset(); r.configureEvaluation({workLimit:2048});
+ }
+ const result = collect(left);
+ assert.deepEqual(result, archivedSnapshot(right));
+ assert(Object.is(result.states.x, -0));
+});
+
+test('publication rejects reentrant runtime changes and reads before they affect the scene', async () => {
+ const runtime = await createRuntime(wasm);
+ runtime.apply(source);
+ const calls = [() => runtime.apply(''), () => runtime.reset(), () => runtime.frame(),
+ () => runtime.down('move'), () => runtime.up('move'), () => runtime.releaseAll(),
+ () => runtime.configureEvaluation({workLimit:0}), () => runtime.forgetEvaluation(),
+ () => runtime.snapshot(), () => runtime.present({})];
+ const expected = runtime.snapshot();
+ assert.deepEqual(collect(runtime, {begin() {
+ for (const call of calls) assert.throws(call, /출력 전달 중/);
+ }}), expected);
+ assert.deepEqual(runtime.snapshot(), expected);
+});
+
+test('invalid or throwing output adapters do not strand the runtime or alter authored state', async () => {
+ const runtime = await createRuntime(wasm);
+ runtime.apply(source);
+ const expected = runtime.snapshot();
+ assert.throws(() => runtime.present({}), TypeError);
+ assert.throws(() => collect(runtime, {value() { throw new Error('destination failed'); }}), /destination failed/);
+ assert.deepEqual(collect(runtime), expected);
+ runtime.down('move'); runtime.up('move');
+ assert.equal(collect(runtime).states.x, 3.25);
+});