Files
silveroxides 8dbc870407 feat: add Gemini structured response support
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.
2026-09-11 19:06:22 +02:00

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