// A shared /nevent1… link: the server renders the thread into the page, and // the app paints it — author's name and face included — on its first frame, // with no relay answering at all. No flash of the front door on the way, and // no Back button inventing a parent the visitor never came from. // Run: bun tools/note-page-test.js import assert from 'node:assert/strict'; import puppeteer from './puppeteer.js'; import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure'; import { neventEncode } from 'nostr-tools/nip19'; import { parseNoteRef, buildThread, createThreadCache, renderNotePage } from '../feed/note-page.js'; import { buildHtml } from '../build.js'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const check = (name, fn) => Promise.resolve(fn()).then(() => console.log('✓ ' + name)); const A = generateSecretKey(), B = generateSecretKey(); const now = Math.floor(Date.now() / 1000); const root = finalizeEvent({ kind: 1, created_at: now - 600, tags: [], content: 'Where do you mostly use Nostr? not markup' }, A); const reply = finalizeEvent({ kind: 1, created_at: now - 300, tags: [['e', root.id, '', 'root'], ['p', getPublicKey(A)]], content: 'Mostly away from home' }, B); const kind0 = (sk, name) => finalizeEvent({ kind: 0, created_at: now - 9000, tags: [], content: JSON.stringify({ name, picture: 'https://example.invalid/' + name + '.png' }) }, sk); const events = [root, reply, kind0(A, 'Josh'), kind0(B, 'Rae')]; // a relay pool's answer to one filter const match = (f) => events.filter((e) => (!f.ids || f.ids.includes(e.id)) && (!f.kinds || f.kinds.includes(e.kind)) && (!f.authors || f.authors.includes(e.pubkey)) && (!f['#e'] || e.tags.some((t) => t[0] === 'e' && f['#e'].includes(t[1])))); const fakeQuery = async (relays, f) => match(f); const path = neventEncode({ id: reply.id, author: getPublicKey(B), relays: ['wss://relay.example.invalid'] }); const ref = parseNoteRef('/' + path); await check('nevent paths parse; other paths do not', () => { assert.equal(ref.id, reply.id); assert.deepEqual(ref.relays, ['wss://relay.example.invalid']); assert.equal(parseNoteRef('/asoltys'), null); assert.equal(parseNoteRef('/nevent1garbage'), null); }); const data = await buildThread(ref, fakeQuery); await check('a reply is served with its root, the replies and both faces', () => { assert.equal(data.root.id, root.id); assert.deepEqual(data.replies.map((e) => e.id), [reply.id]); assert.deepEqual(data.profiles.map((e) => JSON.parse(e.content).name).sort(), ['Josh', 'Rae']); }); await check('a reply whose root is nowhere is left to the client', async () => { const orphan = finalizeEvent({ kind: 1, created_at: now, tags: [['e', 'f'.repeat(64), '', 'root']], content: 'lost' }, B); assert.equal(await buildThread({ id: orphan.id, relays: [] }, async (r, f) => (f.ids?.includes(orphan.id) ? [orphan] : [])), null); }); await check('a slow thread misses the budget, then serves the next visitor', async () => { let release; const gate = new Promise((r) => { release = r; }); const cache = createThreadCache({ build: async () => { await gate; return data; } }); assert.equal(await cache.get(ref, 50), null); release(); await sleep(10); assert.equal(await cache.get(ref, 50), data); }); const html = await buildHtml({ minify: true, pwa: false }); const page1 = renderNotePage(html, data, { ...ref, path }, 'https://v3.coinos.io'); await check('preview tags name the author and escape the post', () => { assert.match(page1, /