test/fieldlength.test.js
Rama main · réplica de lectura del repositorio original.
import { test } from "node:test";
import assert from "node:assert/strict";
import { fieldLengthPass, fieldInventory, looksCutAtCap } from "../src/ai/fieldlength.js";
import { slideCatalog, catalogForType } from "../src/ai/catalog.js";
import { readFile } from "node:fs/promises";
/** The schema's own cap for a field, so a corrected cap does not fail a test
* that is about the inventory rather than about the number. */
async function schemaCap(type, path) {
const schema = JSON.parse(await readFile(new URL("../schema/deck.schema.json", import.meta.url), "utf8"));
const rule = schema.definitions.slide.allOf.find((r) => r.if?.properties?.type?.const === type);
let node = rule.then.properties;
for (const seg of path) node = seg === "[]" ? node.items : (node.properties ?? node)[seg];
return node.maxLength;
}
const DECK = {
title: "Field length",
theme: "warm-humanist",
slides: [
{
type: "framework",
headline: "One Node, Four Parallel Levels",
concept: { title: "Hybrid Node Layout", body: "MPI ranks own NUMA domains, OpenMP…" },
elements: [
{ title: "MPI Ranks", body: "One rank per NUMA domain, not per core…" },
{ title: "OpenMP Threads", body: "Threads inside a rank share its memory…" },
{ title: "CUDA", body: "GPUs attached per rank" },
],
},
{
type: "flow",
headline: "Decompose",
direction: "ltr",
steps: [
{ title: "Decompose", body: "Split the application into fine-grained tasks that carry their own data use." },
{ title: "Express", body: "Task inputs and outputs form a directed acyclic graph (DAG) the…" },
],
},
],
};
/** A fake chat returning a full-slide update_slide op (the shape this local
* model actually drifts toward) for both flagged slides. */
function fakeChat() {
return async ({ schema }) => {
const type = schema.properties.ops.items.properties.slide.properties.type.enum[0];
if (type === "framework") {
return {
data: {
ops: [{
op: "update_slide",
slide: {
type: "framework",
// A whole-slide replacement must carry the headline too: it is
// required on every content type, so an update that drops it is
// rejected and the repair discarded — which is the behaviour, not
// a fixture detail.
headline: "One Node, Four Parallel Levels",
concept: { title: "Hybrid Node Layout", body: "MPI ranks own NUMA domains, OpenMP threads share rank memory." },
elements: [
{ title: "MPI Ranks", body: "One rank per NUMA domain, not per core." },
{ title: "OpenMP Threads", body: "Threads inside a rank share its memory." },
{ title: "CUDA", body: "GPUs attached per rank" },
],
},
}],
},
};
}
return {
data: {
ops: [{
op: "update_slide",
slide: {
type: "flow",
headline: "Decompose",
direction: "ltr",
steps: [
{ title: "Decompose", body: "Split the application into fine-grained tasks that carry their own data use." },
{ title: "Express", body: "Task inputs and outputs form a directed acyclic graph the scheduler orders." },
],
},
}],
},
};
};
}
test("fieldInventory states the schema cap per field — the writer's budget", async () => {
const slide = DECK.slides[0];
const inv = await fieldInventory(slide);
const concept = inv.find((f) => f.path === "concept.body");
assert.ok(concept, "concept.body is inventoried");
assert.equal(concept.cap, await schemaCap("framework", ["concept", "body"]));
const elem = inv.find((f) => f.path === "elements[].body");
const elemCap = await schemaCap("framework", ["elements", "[]", "body"]);
assert.equal(elem.cap, elemCap, "nested element bodies carry their own cap");
assert.notEqual(elem.cap, concept.cap, "and not the parent's");
});
test("the catalog spells out the per-field caps, not just per-type", async () => {
const catalog = await slideCatalog();
assert.ok(catalog.includes("concept"), "framework concept field named");
assert.ok(catalog.includes("≤120"), "a char cap is in the catalog");
const flow = await catalogForType("flow");
assert.ok(flow.includes("body ≤120") || flow.includes("≤120"), "flow step body cap reaches the writer");
});
test("fieldLengthPass rewrites mid-sentence ellipses into complete sentences", async () => {
const r = await fieldLengthPass({ deck: DECK, model: "mock", chat: fakeChat() });
const s6 = r.deck.slides[0];
assert.ok(!s6.concept.body.includes("…"), "no ellipsis left in the concept body");
assert.ok(s6.concept.body.length <= 120, "within the cap");
assert.equal(s6.concept.body, "MPI ranks own NUMA domains, OpenMP threads share rank memory.");
const rep = r.repaired.filter((x) => x.index === 0);
assert.ok(rep.length >= 1, "the repair is recorded");
assert.ok(r.problems.length === 0);
// The flow slide's mid-sentence ellipsis is repaired too.
assert.ok(!r.deck.slides[1].steps[1].body.includes("…"), "no ellipsis left in the flow step");
assert.ok(r.deck.slides[1].steps[1].body.length <= 120);
});
test("fieldLengthPass leaves a deck with nothing overlong alone", async () => {
const clean = {
title: "t",
theme: "warm-humanist",
slides: [{ type: "bullets", headline: "Points", bullets: ["one", "two", "three", "four"] }],
};
const r = await fieldLengthPass({ deck: clean, model: "mock", chat: () => { throw new Error("no model call expected"); } });
assert.deepEqual(r.repaired, []);
assert.deepEqual(r.deck, clean);
});
/* ------------------------------------------------- what a grammar cut looks like */
/**
* `looksCutAtCap` used to be "at cap and no terminal punctuation", which is
* true of every headline ever written — a headline is a noun phrase and ends
* without a full stop by design. Each false positive is a rewrite call that was
* not needed, and since tokens stopped being free that is money.
*/
test("a headline at its cap is not a cut", () => {
for (const h of [
"Weighing V2G Economics Against Grid Impact",
"Bidirectional Charging Cuts Depot Costs",
]) {
assert.equal(looksCutAtCap(h, h.length), false, h);
}
});
test("a single-token value at its cap is not a cut", () => {
// Its cap is small so it sits at cap constantly, and it is complete.
for (const v of ["$10.9M", "27.6%", "RTX-4090", "1000h"]) {
assert.equal(looksCutAtCap(v, v.length), false, v);
}
});
test("a phrase ending where no writer would end is a cut", () => {
// Both observed on a real feature-grid.
for (const c of [
"Depot charging improves stability and P",
"cheaper to run than spiro-OM",
"Grid revenue averaged EUR 41 per bus per",
"Costs fall as fleets scale, and utilisation",
]) {
assert.equal(looksCutAtCap(c, c.length), true, c);
}
});
test("a field under its cap is never a cut, however it ends", () => {
assert.equal(looksCutAtCap("Ends on and", 200), false);
});
test("a finished sentence at its cap is not a cut", () => {
const s = "Depot charging improves operational stability.";
assert.equal(looksCutAtCap(s, s.length), false);
});