A resource was private with no way out but the author's own Nextcloud. Share opens reading — open, preview, download — to one person at a time by exact email (no account is ever listed) or to everyone signed in with one switch; what others share appears in your library marked "Shared by …". Writing never travels: modify, re-skin, delete and the share list stay the author's, every write still filtered on user_id, and the read routes go through one reader rule. Rows follow the resource and the person. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
48 lines
3.3 KiB
JavaScript
48 lines
3.3 KiB
JavaScript
// 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 <img src> 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-pages \{[^}]*overflow:auto/);
|
|
assert.match(css, /\.mr-preview-page \{[^}]*width:100%/);
|
|
});
|