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/); });