108 lines
4.7 KiB
TypeScript
108 lines
4.7 KiB
TypeScript
|
|
import assert from "node:assert/strict";
|
||
|
|
import { test } from "node:test";
|
||
|
|
import { ActorVariables } from "../src/debugVariables";
|
||
|
|
import { actor, REF } from "./fixtures";
|
||
|
|
|
||
|
|
test("actor variables preserve exact values, types, escaped strings and typed references", () => {
|
||
|
|
const variables = new ActorVariables();
|
||
|
|
variables.capture(actor());
|
||
|
|
const scope = variables.scope();
|
||
|
|
assert.match(scope.name, /committed; not frame locals/);
|
||
|
|
const roots = variables.variables(scope.variablesReference);
|
||
|
|
assert.deepEqual(
|
||
|
|
roots.map((variable) => variable.name),
|
||
|
|
["committed_state", "mailbox", "config", "metadata"],
|
||
|
|
);
|
||
|
|
const state = roots[0]!;
|
||
|
|
assert.equal(state.value, "Record(5)");
|
||
|
|
assert.equal(state.namedVariables, 5);
|
||
|
|
const fields = variables.variables(state.variablesReference);
|
||
|
|
assert.equal(fields.find((field) => field.name === "minimum")?.value, "-9223372036854775808");
|
||
|
|
assert.equal(fields.find((field) => field.name === "maximum")?.value, "9223372036854775807");
|
||
|
|
assert.equal(fields.find((field) => field.name === "reference")?.value, `@${REF}`);
|
||
|
|
assert.equal(fields.find((field) => field.name === "reference")?.type, "actor_ref");
|
||
|
|
assert.equal(fields.find((field) => field.name === "text")?.type, "string");
|
||
|
|
assert.equal(
|
||
|
|
fields.find((field) => field.name === "text")?.value,
|
||
|
|
JSON.stringify(`@${REF} "雪"`),
|
||
|
|
);
|
||
|
|
assert.ok(fields.every((field) => field.presentationHint.attributes[0] === "readOnly"));
|
||
|
|
const list = fields.find((field) => field.name === "list")!;
|
||
|
|
assert.equal(list.indexedVariables, 2);
|
||
|
|
assert.deepEqual(
|
||
|
|
variables.variables(list.variablesReference).map((child) => child.value),
|
||
|
|
["null", "true"],
|
||
|
|
);
|
||
|
|
const metadata = variables.variables(roots[3]!.variablesReference);
|
||
|
|
assert.equal(
|
||
|
|
metadata.find((field) => field.name === "mailbox_id")?.value,
|
||
|
|
"18446744073709551615",
|
||
|
|
);
|
||
|
|
const mailbox = variables.variables(roots[1]!.variablesReference);
|
||
|
|
const reserved = mailbox.find((field) => field.name === "reserved")!;
|
||
|
|
assert.equal(
|
||
|
|
reserved.indexedVariables,
|
||
|
|
1,
|
||
|
|
"a reserved null message must not be confused with no reservation",
|
||
|
|
);
|
||
|
|
assert.equal(variables.variables(reserved.variablesReference)[0]?.value, "null");
|
||
|
|
});
|
||
|
|
|
||
|
|
test("variable handles are stable per snapshot, stale after clear, and snapshots never alias inputs", () => {
|
||
|
|
const snapshot = actor();
|
||
|
|
const variables = new ActorVariables();
|
||
|
|
variables.capture(snapshot);
|
||
|
|
const scope = variables.scope();
|
||
|
|
const state = variables.variables(scope.variablesReference)[0]!;
|
||
|
|
assert.deepEqual(variables.variables(scope.variablesReference)[0], state);
|
||
|
|
snapshot.state = { type: "integer", data: 999n };
|
||
|
|
snapshot.queued.push({ type: "null" });
|
||
|
|
assert.equal(variables.variables(scope.variablesReference)[0]?.value, "Record(5)");
|
||
|
|
const mailbox = variables.variables(
|
||
|
|
variables.variables(scope.variablesReference)[1]!.variablesReference,
|
||
|
|
);
|
||
|
|
assert.equal(mailbox.find((field) => field.name === "queued")?.indexedVariables, 1);
|
||
|
|
variables.capture(snapshot);
|
||
|
|
assert.throws(() => variables.variables(scope.variablesReference), /Stale/);
|
||
|
|
const next = variables.scope();
|
||
|
|
assert.notEqual(scope.variablesReference, next.variablesReference);
|
||
|
|
assert.equal(variables.variables(next.variablesReference)[0]?.value, "999");
|
||
|
|
variables.clear();
|
||
|
|
assert.throws(() => variables.scope(), /unavailable/);
|
||
|
|
assert.throws(() => variables.variables(next.variablesReference), /Stale/);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("variables support named/indexed paging and bounded previews without recursive serialization", () => {
|
||
|
|
const snapshot = actor();
|
||
|
|
snapshot.state = {
|
||
|
|
type: "list",
|
||
|
|
data: [
|
||
|
|
{ type: "string", data: "\n".repeat(300) },
|
||
|
|
{ type: "record", data: {} },
|
||
|
|
{ type: "list", data: [] },
|
||
|
|
{ type: "integer", data: 1n },
|
||
|
|
],
|
||
|
|
};
|
||
|
|
snapshot.reserved = null;
|
||
|
|
const variables = new ActorVariables();
|
||
|
|
variables.capture(snapshot);
|
||
|
|
const root = variables.scope().variablesReference;
|
||
|
|
const state = variables.variables(root)[0]!.variablesReference;
|
||
|
|
assert.deepEqual(variables.variables(state, "named"), []);
|
||
|
|
assert.deepEqual(variables.variables(root, "indexed"), []);
|
||
|
|
assert.deepEqual(
|
||
|
|
variables.variables(state, "indexed", 1, 2).map((field) => field.name),
|
||
|
|
["1", "2"],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
variables.variables(state, undefined, 2, 0).map((field) => field.name),
|
||
|
|
["2", "3"],
|
||
|
|
);
|
||
|
|
assert.deepEqual(variables.variables(state, "indexed", 100), []);
|
||
|
|
const fields = variables.variables(state);
|
||
|
|
assert.equal(fields[0]?.value, `${JSON.stringify("\n".repeat(256))}... (300 UTF-16 code units)`);
|
||
|
|
assert.equal(fields[1]?.variablesReference, 0);
|
||
|
|
assert.equal(fields[2]?.variablesReference, 0);
|
||
|
|
assert.throws(() => variables.variables(root, "invalid"), /filter/);
|
||
|
|
});
|