// A resource, or a theme's sample deck, shown as pages in the page itself — // on a phone as much as anywhere — rendered once per version. 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 cache key follows the version, so a modification or a re-skin renders afresh', () => { const previews = require('../src/utils/previewPages'); const a = previews.cacheKey(['resource', 7, '2026-09-13T10:00:00Z', 'ward-teal', 'presentation']); const same = previews.cacheKey(['resource', 7, '2026-09-13T10:00:00Z', 'ward-teal', 'presentation']); const edited = previews.cacheKey(['resource', 7, '2026-09-13T11:00:00Z', 'ward-teal', 'presentation']); const reskinned = previews.cacheKey(['resource', 7, '2026-09-13T10:00:00Z', 'slate', 'presentation']); assert.equal(a, same); assert.notEqual(a, edited); assert.notEqual(a, reskinned); assert.match(a, /^[0-9a-f]{40}$/, 'a directory name, nothing a caller typed'); }); test('previews are served for a resource and for a theme sample, and only to the owner', () => { const route = read('src/routes/myResources.js'); assert.match(route, /router\.get\('\/my-resources\/:id\/preview', async/); assert.match(route, /router\.get\('\/my-resources\/:id\/preview\/:page', async/); assert.match(route, /router\.get\('\/my-resources\/theme-sample\/:id\/preview', async/); assert.match(route, /router\.get\('\/my-resources\/theme-sample\/:id\/preview\/:page', async/); // The resource routes read through the reader rule: the owner, or someone it is shared with. const preview = route.slice(route.indexOf("router.get('/my-resources/:id/preview'"), route.indexOf("// The sample deck of a theme")); assert.equal((preview.match(/await readableResource\(req\.params\.id, req\.user\.id/g) || []).length, 2); // Rendered the way the download is: the same exporter, the same theme. assert.match(route, /documentExport\.render\(row\.markdown, row\.kind, format,\s*\{ images: figures, deck: row\.deck, theme: row\.theme, figureIds: figureIdList\(row\.image_ids\) \}\)/); assert.match(route, /previewPages\.cacheKey\(\['resource', row\.id, row\.updated_at, row\.theme \|\| '', row\.kind\]\)/); assert.match(route, /previewPages\.cacheKey\(\['theme-sample', id, JSON\.stringify\(theme \|\| \{\}\)\]\)/); }); test('the page offers Preview beside the downloads and beside the theme sample, and the gallery scrolls', () => { const js = read('public/js/myResources.js'); assert.match(js, /look\.dataset\.preview = String\(row\.id\)/); assert.match(js, /openPreview\('\/api\/my-resources\/theme-sample\/' \+ encodeURIComponent\(select\.value\) \+ '\/preview'/); // Fetched with the auth header — an cannot carry one. assert.match(js, /fetch\(base \+ '\/' \+ n, \{ headers: getAuthHeaders\(\) \}\)/); assert.match(js, /URL\.revokeObjectURL\(u\)/, 'blob URLs are released when the preview closes'); // Every presentation takes a theme now, markdown slides included. assert.doesNotMatch(js, /row\.has_deck !== false && themeCatalogue\.length > 1/); const css = read('public/css/styles.css'); assert.match(css, /\.mr-preview-page \{[^}]*max-height:100%/); assert.match(css, /\.mr-preview-pdf \{/); const mr = read('public/js/myResources.js'); assert.match(mr, /data-view="pdf"/); assert.match(mr, /e\.key === 'ArrowRight'/); assert.match(mr, /touchend/); assert.doesNotMatch(mr, /theme\.dataset\.theme = String\(row\.id\)/, 'the list row no longer carries a theme dropdown'); });