// A theme picker owes you a look at the theme. A server-rendered PNG of one // slide answered a narrower question than the one being asked, needed a // Gotenberg round trip and a pdftoppm to do it, and could fail in ways a // missing picture cannot explain. A sample deck answers it properly: every // layout, filler text, opened in PowerPoint at the size it will be shown. const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const read = f => fs.readFileSync(path.join(__dirname, '..', f), 'utf8'); const deckSample = require('../src/utils/deckSample'); const deckSchema = require('../src/utils/deckSchema'); // The vocabulary the renderer can draw, from the schema itself, so a layout // added later fails this until the sample shows it too. const SCHEMA_TYPES = ['title', 'section', 'bullets', 'two', 'compare', 'table', 'callout', 'figure', 'image', 'custom']; test('the sample shows every layout the renderer can draw', () => { const deck = deckSample.build({ id: 'clinical-blue', name: 'Clinical Blue' }); const present = new Set(deck.slides.map(s => s.type)); for (const type of SCHEMA_TYPES) { assert.ok(present.has(type), 'the sample never shows a "' + type + '" slide'); } }); test('that list is the schema\'s own, not a copy that can drift', () => { const schema = read('src/utils/deckSchema.js'); const declared = schema.match(/var VALID = \[([^\]]*)\]/)[1] .split(',').map(s => s.trim().replace(/'/g, '')).filter(Boolean); assert.deepEqual(declared.slice().sort(), SCHEMA_TYPES.slice().sort(), 'deckSchema.VALID changed — the sample must show the new layout too'); }); test('the custom slide exercises the shape vocabulary, not just one box', () => { const deck = deckSample.build({ id: 'slate', name: 'Slate' }); const custom = deck.slides.find(s => s.type === 'custom'); const kinds = new Set(custom.shapes.map(s => s.kind)); for (const kind of ['roundRect', 'arrow', 'line', 'text', 'chart']) { assert.ok(kinds.has(kind), 'no ' + kind + ' shape in the custom sample'); } }); test('the figure layouts carry a placeholder, or they render as plain text', () => { // attachFigures downgrades a figure slide with no picture to bullets and an // image slide to a section, so without this the sample would silently stop // showing those two layouts at all. const deck = deckSample.build({ id: 'ward-teal', name: 'Ward Teal' }); for (const type of ['figure', 'image']) { const slide = deck.slides.find(s => s.type === type); assert.equal(slide.image_job, deckSample.FIGURE_JOB, type + ' has no figure attached'); } assert.ok(fs.existsSync(deckSample.FIGURE), 'the placeholder image is missing from the repo'); }); test('the sample carries no clinical content', () => { // Real content invites you to read it, and then you are judging the teaching // rather than the type — which is what the previous sample slide got wrong. const text = JSON.stringify(deckSample.build({ id: 'slate', name: 'Slate' })).toLowerCase(); for (const word of ['stridor', 'croup', 'cough', 'dose', 'mg/kg', 'patient', 'fever']) { assert.ok(!text.includes(word), 'the sample mentions "' + word + '"'); } }); test('every theme builds, and the deck is stamped with the one asked for', () => { for (const theme of deckSchema.themes()) { const deck = deckSample.build(theme); assert.ok(deck.slides.length >= SCHEMA_TYPES.length); assert.equal(deck.title, theme.name); } }); // ---- how it is served and offered ------------------------------------------ test('the route renders with python-pptx alone — no Gotenberg, no pdftoppm', () => { const route = read('src/routes/myResources.js'); const fn = route.slice(route.indexOf("router.get('/my-resources/theme-sample/:id'")); const body = fn.slice(0, fn.indexOf('\n});')); assert.match(body, /documentExport\.renderDeck\(deck, \[deckSample\.FIGURE\], \[deckSample\.FIGURE_JOB\]\)/); assert.doesNotMatch(body, /slideImages|GOTENBERG|pdftoppm/); assert.doesNotMatch(route, /theme-preview/, 'the rendered-PNG preview should be gone'); }); test('it downloads as a named file rather than rendering in the tab', () => { const route = read('src/routes/myResources.js'); const fn = route.slice(route.indexOf("router.get('/my-resources/theme-sample/:id'")); assert.match(fn.slice(0, 1400), /attachment; filename="' \+ id \+ '-sample\.pptx"/); assert.match(fn.slice(0, 1400), /documentExport\.FORMATS\.pptx\.mime/); }); test('an unknown theme is refused rather than rendered as a default', () => { const route = read('src/routes/myResources.js'); const fn = route.slice(route.indexOf("router.get('/my-resources/theme-sample/:id'")); assert.match(fn.slice(0, 600), /if \(!id\) return res\.status\(404\)/); }); test('the route is registered before the :id catch-all', () => { const route = read('src/routes/myResources.js'); assert.ok(route.indexOf("'/my-resources/theme-sample/:id'") < route.indexOf("router.get('/my-resources/:id'")); }); test('the link is a plain anchor, present whatever the server does', () => { // The previous preview hid itself when it failed, so a broken preview and an // absent one looked the same. A link cannot fail to appear. const ui = read('public/js/myResources.js'); const fn = ui.slice(ui.indexOf('function showThemeSample'), ui.indexOf('function describeTheme')); assert.match(fn, /createElement\('a'\)/); assert.match(fn, /link\.href = '\/api\/my-resources\/theme-sample\/'/); assert.match(fn, /box\.hidden = false/); assert.doesNotMatch(fn, /onerror/); assert.doesNotMatch(ui, /showThemePreview/); }); test('the link names the theme it will download', () => { const ui = read('public/js/myResources.js'); const fn = ui.slice(ui.indexOf('function showThemeSample'), ui.indexOf('function describeTheme')); assert.match(fn, /'Download a sample deck in ' \+ name/); });