// A deck re-skinned in the library rendered in the default palette anyway. The // picker saved the choice correctly and the renderer could apply it correctly; // the theme was dropped in between, by the step that puts the drawn figures // back onto the slides. // // attachFigures rebuilds the deck as a fresh object, and a fresh object keeps // only the fields it names. It named title, subtitle, date and slides. Three // different themes rendered byte-identical files. 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'); test('the theme survives the trip to the renderer', () => { const src = read('src/utils/documentExport.js'); const out = src.slice(src.indexOf('var out = {'), src.indexOf('var out = {') + 220); assert.match(out, /theme: deck\.theme/, 'attachFigures drops the theme, so every export renders in the default palette'); }); test('every field the renderer reads off the deck is carried', () => { // The failure was one missing key in an object literal, and it was silent. // Anything the renderer reads at deck level has to be listed there. const src = read('src/utils/documentExport.js'); const out = src.slice(src.indexOf('var out = {'), src.indexOf('var out = {') + 220); for (const field of ['title', 'subtitle', 'date', 'theme', 'slides']) { assert.match(out, new RegExp(field + ':'), field + ' is not carried through attachFigures'); } }); test('the saved theme is what the library writes, and the renderer reads', () => { // Both ends already worked; this pins that they agree on the field name. const route = read('src/routes/myResources.js'); assert.match(route, /deck\.theme = theme/, 'the library writes deck.theme'); const renderer = read('scripts/render_pptx.py'); assert.match(renderer, /apply_theme\(/); assert.match(renderer, /spec\.get\("theme"\)|deck\.get\("theme"\)|data\.get\("theme"\)/, 'the renderer reads the theme off the deck'); }); test('an unknown theme leaves the default rather than failing the render', () => { const renderer = read('scripts/render_pptx.py'); const fn = renderer.slice(renderer.indexOf('def apply_theme')); assert.match(fn.slice(0, 400), /a deck rendering in the wrong colours beats a deck not rendering/); }); test('a presentation that fell back to markdown slides still takes a theme', () => { // 28 of the 41 presentations in production had no structured deck, and for // every one of them "change template" answered that there was no layout to // change. The theme is a column now, and the markdown slide builder carries // it to the same renderer field a designed deck uses. const slideSpec = require('../src/utils/slideSpec'); const spec = slideSpec.build('% Title\n% Author\n% 2026\n\n# One\n\n- a\n- b\n', { theme: 'ward-teal' }); assert.equal(spec.theme, 'ward-teal'); assert.equal(slideSpec.build('# One\n- a').theme, undefined, 'no theme asked for, none invented'); const route = fs.readFileSync(path.join(__dirname, '..', 'src/routes/myResources.js'), 'utf8'); assert.doesNotMatch(route, /has no slide layout, so it has no theme to change/); assert.match(route, /UPDATE user_resources SET theme = \?, deck = COALESCE\(\?::jsonb, deck\)/); assert.match(route, /deck: row\.deck, theme: row\.theme, figureIds/); const exporter = fs.readFileSync(path.join(__dirname, '..', 'src/utils/documentExport.js'), 'utf8'); assert.match(exporter, /slideSpec\.build\(markdown, \{ images: images, theme: options\.theme \}\)/); assert.match(fs.readFileSync(path.join(__dirname, '..', 'migrations/1781200000000_resource-theme.js'), 'utf8'), /ADD COLUMN IF NOT EXISTS theme TEXT/); });