Add a DOM schema builder and optional response formatting before image Autogrow. Return thoughts separately from final text and retain all outputs in schema-aware caches.
100 lines
4.5 KiB
JavaScript
100 lines
4.5 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createState, addField, removeField, selectType, updateSetting, serializeState, restoreState, visibleChildren } from "../web/response_schema_model.js";
|
|
|
|
test("default has no stored placeholder and nodes never share state", () => {
|
|
const a = createState(), b = createState();
|
|
addField(a, a.root, "string");
|
|
assert.deepEqual(b.entries.n0.fields, []);
|
|
assert.equal(Object.keys(a.entries).length, 2);
|
|
});
|
|
|
|
test("20 fields keep creation order and independent IDs through rename", () => {
|
|
const state = createState();
|
|
const ids = Array.from({ length: 20 }, () => addField(state, state.root, "string"));
|
|
updateSetting(state, ids[0], "name", "猫.[]");
|
|
assert.deepEqual(ids, Array.from({ length: 20 }, (_, i) => `n${i + 1}`));
|
|
assert.deepEqual(visibleChildren(state, state.root), ids);
|
|
assert.deepEqual(restoreState(serializeState(state)), state);
|
|
});
|
|
|
|
test("type changes retain object children, items, choices and bounds", () => {
|
|
const state = createState();
|
|
const id = addField(state, state.root, "object");
|
|
const child = addField(state, id, "string");
|
|
updateSetting(state, id, "choices", "a\nb");
|
|
updateSetting(state, id, "minimum", -1);
|
|
selectType(state, id, "array", "field");
|
|
const item = state.entries[id].item;
|
|
selectType(state, id, "disabled", "field");
|
|
assert.deepEqual(visibleChildren(state, id), []);
|
|
selectType(state, id, "object", "field");
|
|
assert.deepEqual(visibleChildren(state, id), [child]);
|
|
selectType(state, id, "array", "field");
|
|
assert.equal(state.entries[id].item, item);
|
|
assert.equal(state.entries[id].choices, "a\nb");
|
|
assert.equal(state.entries[id].minimum, -1);
|
|
const clone = restoreState(serializeState(state));
|
|
updateSetting(clone, id, "name", "independent");
|
|
assert.equal(state.entries[id].name, "");
|
|
});
|
|
|
|
test("arrays can nest beyond three containers without preallocating future children", () => {
|
|
const state = createState();
|
|
selectType(state, state.root, "array", "root");
|
|
let id = state.entries[state.root].item;
|
|
for (let i = 0; i < 8; i++) {
|
|
selectType(state, id, "array", "item");
|
|
id = state.entries[id].item;
|
|
}
|
|
assert.equal(Object.keys(state.entries).length, 10);
|
|
assert.equal(state.entries[id].type, "string");
|
|
assert.deepEqual(restoreState(serializeState(state)), state);
|
|
});
|
|
|
|
for (const damage of ["version", "cycle", "shared", "orphan", "missing", "allocator", "type", "mode", "disabled_root", "disabled_item"]) {
|
|
test(`restore rejects ${damage}`, () => {
|
|
const state = createState();
|
|
const field = addField(state, state.root, "string");
|
|
if (damage === "version") state.version = 2;
|
|
if (damage === "cycle") state.entries[field].fields.push(state.root);
|
|
if (damage === "shared") state.entries.n0.fields.push(field);
|
|
if (damage === "orphan") state.entries.n0.fields = [];
|
|
if (damage === "missing") state.entries.n0.fields.push("n99");
|
|
if (damage === "allocator") state.next_id = 1;
|
|
if (damage === "type") state.entries[field].type = "invalid";
|
|
if (damage === "mode") state.entries[field].allowed_values = "invalid";
|
|
if (damage === "disabled_root") state.entries.n0.type = "disabled";
|
|
if (damage === "disabled_item") {
|
|
selectType(state, state.root, "array", "root");
|
|
state.entries[state.entries.n0.item].type = "disabled";
|
|
}
|
|
assert.throws(() => restoreState(JSON.stringify(state)));
|
|
});
|
|
}
|
|
|
|
test("old prototype data and nonfinite serialized numbers fail explicitly", () => {
|
|
for (const value of ["object", "{", "[]", '{"type":"object"}']) assert.throws(() => restoreState(value));
|
|
const state = createState();
|
|
state.entries.n0.minimum = Infinity;
|
|
assert.throws(() => serializeState(state), /finite/);
|
|
assert.throws(() => selectType(createState(), "n0", "disabled", "root"));
|
|
});
|
|
|
|
test("removal deletes disabled fields and retained subtrees without touching siblings or reusing IDs", () => {
|
|
const state = createState();
|
|
const first = addField(state, "n0", "string");
|
|
const group = addField(state, "n0", "object");
|
|
addField(state, group, "array");
|
|
selectType(state, group, "array", "field");
|
|
selectType(state, group, "disabled", "field");
|
|
const last = addField(state, "n0", "number");
|
|
const next = state.next_id;
|
|
assert.equal(removeField(state, group), "n0");
|
|
assert.deepEqual(Object.keys(state.entries), ["n0", first, last]);
|
|
assert.deepEqual(state.entries.n0.fields, [first, last]);
|
|
assert.deepEqual(restoreState(serializeState(state)), state);
|
|
assert.equal(addField(state, "n0", "boolean"), `n${next}`);
|
|
assert.throws(() => removeField(state, "n0"));
|
|
});
|