test/section-carry.test.js
Rama main · réplica de lectura del repositorio original.
import { test } from "node:test";
import assert from "node:assert/strict";
import { generateDeck } from "../src/ai/generate.js";
/**
* The plan owns each slide's section index; the writer does not.
*
* The prompt states `section: N` and the model is free to leave it out of its
* append_slide op — and when it does, nothing downstream can recover it. The
* chrome eyebrow reads `slide.section` to print the part's name, so the slide
* renders its rule with no label beside it. Four of fourteen slides on a real
* generated deck lost their section exactly this way, and the deck rendered
* four unlabelled eyebrows without a single check firing: the field is
* optional, so validation passes and the fit sweeps have nothing to measure.
*
* The coherence sweep already re-asserts type, presenter and section for this
* reason. The first write had no such guard.
*/
const PLAN = {
title: "Sectioned deck",
sections: ["Intro", "Middle", "End"],
slides: [
{ type: "title", section: 0, purpose: "Open the deck." },
{ type: "bullets", section: 0, purpose: "The first point." },
{ type: "cards", section: 1, purpose: "Three ideas." },
{ type: "bullets", section: 2, purpose: "The last point." },
{ type: "closing", section: 2, purpose: "Close." },
],
};
/** Slides with NO `section` — what a model actually returns. */
const SLIDES = {
title: { type: "title", headline: "Sectioned deck", subtitle: "s" },
bullets: { type: "bullets", headline: "A point", bullets: ["a", "b", "c", "d"] },
cards: {
type: "cards",
headline: "Ideas",
cards: [{ title: "A", body: "b" }, { title: "B", body: "b" }, { title: "C", body: "b" }],
},
closing: { type: "closing", headline: "Thanks", quote: "q" },
};
const fakeChat = () => async ({ schema }) => {
const type = schema.properties.ops.items.properties.slide.properties.type.enum[0];
return { data: { ops: [{ op: "append_slide", slide: SLIDES[type] }] } };
};
test("every written slide carries the section its plan spec named", async () => {
const res = await generateDeck({ brief: "b", plan: PLAN, model: "mock", chat: fakeChat() });
const written = res.deck.slides;
assert.equal(written.length, PLAN.slides.length, "one slide per spec");
const missing = written
.map((s, i) => ({ i, type: s.type, section: s.section }))
.filter((s) => s.section == null);
assert.deepEqual(missing, [], `slides lost their section: ${JSON.stringify(missing)}`);
assert.deepEqual(
written.map((s) => s.section),
PLAN.slides.map((s) => s.section),
"the written sections must match the plan's, in order",
);
});