pediatric-ai-scribe-v3/test/deck-image-placeholders.test.js
Daniel 6ca278c0e6
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 52s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 7s
feat: deck cards fit their content, callouts use the theme's ink, questions can carry a film, formats end with references
Compare cards were as tall as the body whatever they held, so two columns of
four short bullets sat in cards 60% empty; a one-sentence callout sat in a
card two thirds of the slide. Both now size to the text. The callout card's
fixed brown ink was the worst contrast in the catalogue on Board Review's
mint card; it takes the theme's ink. A question slide may carry image_prompt
— a film beside the stem, drawn or left as a labelled frame like a figure.
The evidence-based formats (board review, journal club, QI, abstract, debrief)
gain a References part and the brief says the deck ends with it: DeepSeek
skipped it when it was only suggested.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-14 15:35:10 +02:00

86 lines
4.5 KiB
JavaScript

// A picture the deck wanted and did not get keeps its place: an empty frame
// labelled with what belongs there, for the author to fill after downloading
// — a real radiograph, a run chart, a photograph no model should draw. It
// used to become a slide of text, which forgot that a picture was wanted.
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const deckBuild = require('../src/utils/deckBuild');
const deckSchema = require('../src/utils/deckSchema');
const docSpec = require('../src/utils/docSpec');
const read = f => fs.readFileSync(path.join(__dirname, '..', f), 'utf8');
function deck() {
return deckSchema.normalise({ title: 'T', slides: [
{ type: 'figure', heading: 'The film', bullets: ['a'], image_prompt: 'the admission chest radiograph' },
{ type: 'image', heading: 'Run chart', image_prompt: 'the run chart of hand-hygiene compliance', caption: 'Weekly' },
{ type: 'custom', heading: 'Layout', shapes: [
{ kind: 'text', x: 5, y: 20, w: 40, h: 20, text: 'beside it' },
{ kind: 'image', x: 50, y: 20, w: 45, h: 50, image_prompt: 'a fishbone diagram' }
] },
{ type: 'bullets', heading: 'Plain', bullets: ['b'] }
] });
}
test('with illustration off every requested figure stays a figure, marked as a frame to fill', async () => {
const d = deck();
const result = await deckBuild.drawFigures(d, { imageModel: '' });
assert.deepEqual(result.jobs, []);
assert.equal(d.slides[0].type, 'figure');
assert.equal(d.slides[0].placeholder, true);
assert.equal(d.slides[0].image_prompt, 'the admission chest radiograph', 'the prompt is the label');
assert.equal(d.slides[1].type, 'image');
assert.equal(d.slides[1].placeholder, true);
assert.equal(d.slides[2].shapes.length, 2, 'the empty image shape is kept, not dropped');
assert.equal(d.slides[2].shapes[1].placeholder, true);
assert.equal(d.slides[3].placeholder, undefined);
});
test('the frame survives normalisation on both slides and shapes, and only as a boolean', () => {
const d = deck();
d.slides[0].placeholder = true; d.slides[2].shapes[1].placeholder = true;
const again = deckSchema.normalise(d);
assert.equal(again.slides[0].placeholder, true);
assert.equal(again.slides[2].shapes[1].placeholder, true);
const forged = deckSchema.normalise({ slides: [{ type: 'image', image_prompt: 'x', placeholder: 'yes' }] });
assert.equal(forged.slides[0].placeholder, undefined);
});
test('the Word export says what belongs in the frame', () => {
const d = deck();
d.slides[0].placeholder = true; d.slides[2].shapes[1].placeholder = true;
const blocks = docSpec.fromDeck ? docSpec.fromDeck(d, {}) : docSpec.build(d, {});
const text = JSON.stringify(blocks);
assert.match(text, /\[Add an image here: the admission chest radiograph\]/);
assert.match(text, /\[Add an image here: a fishbone diagram\]/);
assert.doesNotMatch(text, /hand-hygiene/, 'a slide not marked as a frame gets no placeholder line');
});
test('the renderer draws the frame on figure, image and custom image shapes', () => {
const py = read('scripts/render_pptx.py');
assert.match(py, /def _placeholder\(slide, left, top, width, height, label\)/);
assert.match(py, /MSO_LINE_DASH_STYLE\.DASH/);
assert.equal((py.match(/\n\s+_placeholder\(slide, /g) || []).length, 4, 'figure, image, question and draw_image each draw it');
assert.match(py, /"Add an image here"/);
});
test('the model is told a picture it names is drawn or left as a frame, never described in prose', () => {
const brief = deckSchema.instructions(8, 0);
assert.match(brief, /keeps an empty labelled/);
assert.match(brief, /Never write an image placeholder into any text/);
});
test('a question may carry a picture beside its stem, drawn or left as a frame', async () => {
const d = deckSchema.normalise({ slides: [
{ type: 'question', heading: 'Case 2', question: 'What does the film show?', options: ['a', 'b'], answer: 'a', image_prompt: 'the admission chest radiograph' }
] });
assert.equal(d.slides[0].type, 'question', 'not converted into a figure slide');
assert.equal(d.slides[0].image_prompt, 'the admission chest radiograph');
await deckBuild.drawFigures(d, { imageModel: '' });
assert.equal(d.slides[0].placeholder, true);
const blocks = docSpec.fromDeck(d, {});
assert.match(JSON.stringify(blocks), /\[Add an image here: the admission chest radiograph\]/);
const py = read('scripts/render_pptx.py');
assert.match(py, /has_picture = bool\(path and os\.path\.exists\(path\)\) or bool\(spec\.get\("placeholder"\)\)/);
});