import assert from 'node:assert/strict'; import { readdir, readFile } from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; import { createMarkdownProcessor } from '@astrojs/markdown-remark'; import { load } from 'cheerio'; import documentLinks, { contentsRoot } from '../plugins/document-links.mjs'; import { publicTree } from '../src/lib/landing-tree.ts'; import { createWorldspaceData } from '../src/lib/worldspace-data.ts'; const pages = { '/': { title: 'Worldspace Interface Protocol', href: '/', html: '

Home

' }, '/book': { title: 'Book', href: '/book/', html: '

Book

' }, '/sources': { title: 'Sources', href: '/sources/', html: '

Sources

' }, '/packages': { title: 'Packages', href: '/packages/', html: '

Packages

' }, '/playground': { title: 'Playground', href: '/playground/', html: '

Playground

' }, }; const source = (id) => ({ id, title: id, href: `/book/${id}/`, html: `

${id}

` }); async function sourceFiles(directory, prefix = '') { return (await Promise.all((await readdir(directory, { withFileTypes: true })).map(async (entry) => { const relative = prefix + entry.name; return entry.isDirectory() ? sourceFiles(path.join(directory, entry.name), relative + '/') : entry.name.endsWith('.md') ? [relative] : []; }))).flat(); } const files = await sourceFiles(contentsRoot); const publicFiles = files.filter((file) => !file.startsWith('draft/')); const processor = await createMarkdownProcessor({ remarkPlugins: [[documentLinks, { base: '/' }]], smartypants: false }); const documents = await Promise.all(publicFiles.map(async (file) => { const body = await readFile(path.join(contentsRoot, file), 'utf8'); const result = await processor.render(body, { fileURL: new URL(`file://${path.join(contentsRoot, file)}`) }); return { ...source(file.slice(0, -3)), title: body.match(/^#\s+(.+)$/m)?.[1] ?? file, html: result.code }; })); function checkIdentity(data) { for (const field of ['path', 'displayPath', 'href']) { assert.equal(new Set(data.nodes.map((node) => node[field])).size, data.nodes.length); } const root = data.nodes.find((node) => node.path === '/'); assert.equal(root.name, ''); assert.equal(root.parent, undefined); for (const node of data.nodes.filter((node) => node.path !== '/')) { assert.ok(data.nodes.some((parent) => parent.path === node.parent)); assert.equal(node.path, `${node.parent === '/' ? '' : node.parent}/${node.name}`); assert.ok(node.name.length > 0 && !node.name.includes('/')); assert.ok(!node.path.split('/').includes('draft')); assert.ok(!node.displayPath.split('/').includes('draft')); } } test('actual public documents keep Web and display identities but acquire real parent paths', () => { const data = createWorldspaceData(documents, pages); checkIdentity(data); const display = publicTree(documents); assert.equal(data.nodes.length, documents.length + 5); assert.deepEqual(data.nodes.map((node) => node.displayPath), display.map((node) => node.path)); for (const node of data.nodes) { const legacy = display.find((item) => item.path === node.displayPath); assert.equal(node.parent, legacy.parent === undefined ? undefined : data.nodes.find((item) => item.displayPath === legacy.parent).path); if (legacy.href) { assert.equal(node.href, legacy.href); assert.equal(node.html, (documents.find((entry) => `/${entry.id}.md` === node.displayPath) ?? pages[node.displayPath]).html); assert.match(node.html, /

node.displayPath === '/1.1-interaction-abstraction.md'); assert.equal(section.path, '/book/1-concept.md/1.1-interaction-abstraction.md'); assert.equal(section.parent, '/book/1-concept.md'); assert.equal(section.href, '/book/1.1-interaction-abstraction/'); assert.equal(data.nodes[1].name, 'book'); assert.equal(display[1].name, 'book'); }); test('catalog and playground objects retain root identity, standalone URLs and no static descendants', () => { const data = createWorldspaceData(documents, pages); assert.deepEqual(data.nodes.filter((node) => node.parent === '/').map((node) => node.path), ['/book', '/sources', '/packages', '/playground']); for (const path of ['/sources', '/packages', '/playground']) { const leaf = data.nodes.find((node) => node.path === path); assert.equal(leaf.displayPath, path); assert.equal(leaf.href, `${path}/`); assert.equal(leaf.html, pages[path].html); assert.equal(data.nodes.filter((node) => node.parent === path).length, 0); assert.ok(!data.nodes.some((node) => node.path.startsWith(`${path}/`))); } }); test('drafts are removed before hierarchy, body and URL generation', () => { const marker = 'PRIVATE-DRAFT-CONTENT-MUST-NOT-SERIALIZE'; const drafts = files.filter((file) => file.startsWith('draft/')).map((file) => ({ ...source(file.slice(0, -3)), html: marker })); assert.ok(drafts.length > 0); const publicOnly = createWorldspaceData(documents, pages); const mixed = createWorldspaceData([...documents, ...drafts], pages); assert.deepEqual(mixed, publicOnly); assert.ok(!JSON.stringify(mixed).includes(marker)); assert.ok(!mixed.nodes.some((node) => node.href.includes('/draft/'))); }); test('nested numbering, real directories and site base prefixes remain unambiguous', () => { const docs = ['1-parent', '1.2-child', '1.2.3-leaf', 'other/1-parent', 'other/1.1-child'] .map((id) => ({ ...source(id), href: `/reference/book/${id}/` })); const data = createWorldspaceData(docs, { '/': { ...pages['/'], href: '/reference/' }, '/book': { ...pages['/book'], href: '/reference/book/' }, '/sources': { ...pages['/sources'], href: '/reference/sources/' }, '/packages': { ...pages['/packages'], href: '/reference/packages/' }, '/playground': { ...pages['/playground'], href: '/reference/playground/' }, '/other': { title: 'other/', href: '/reference/book/other/', html: '

other/

' }, }); checkIdentity(data); assert.equal(data.nodes.find((node) => node.displayPath === '/1.2.3-leaf.md').path, '/book/1-parent.md/1.2-child.md/1.2.3-leaf.md'); assert.equal(data.nodes.find((node) => node.displayPath === '/other/1.1-child.md').path, '/book/other/1-parent.md/1.1-child.md'); assert.ok(data.nodes.every((node) => node.href.startsWith('/reference/'))); }); test('invalid, duplicate and incomplete generated inputs fail rather than lose documents', () => { assert.throws(() => createWorldspaceData([source('a'), source('a')], pages), /Duplicate document/); for (const id of ['', '/absolute', 'a//b', 'a/../b', 'a/./b', 'a/draft/b']) { assert.throws(() => createWorldspaceData([source(id)], pages), /Invalid public document id/); } assert.throws(() => createWorldspaceData([source('a')], {}), /Missing rendered page/); assert.throws(() => createWorldspaceData([{ ...source('a'), href: '/book/' }], pages), /Duplicate Web URL/); assert.throws(() => createWorldspaceData([{ ...source('a'), href: '//external.example/' }], pages), /Invalid page metadata/); assert.throws(() => createWorldspaceData([{ ...source('a'), title: '' }], pages), /Invalid page metadata/); assert.throws(() => createWorldspaceData([source('dir/a')], pages), /Missing rendered page: \/dir/); }); test('root, book, catalog and playground content is supplied by their shared page renderers', () => { const data = createWorldspaceData([], pages); assert.deepEqual(data.nodes, [ { path: '/', name: '', displayPath: '/', ...pages['/'] }, { path: '/book', name: 'book', displayPath: '/book', parent: '/', ...pages['/book'] }, { path: '/sources', name: 'sources', displayPath: '/sources', parent: '/', ...pages['/sources'] }, { path: '/packages', name: 'packages', displayPath: '/packages', parent: '/', ...pages['/packages'] }, { path: '/playground', name: 'playground', displayPath: '/playground', parent: '/', ...pages['/playground'] }, ]); }); // Explicit post-build validation, usable for either root or prefixed deployments. // WIP_TEST_GENERATED=1 node --test tests/worldspace-data.test.mjs test('built artifact contains exactly public routes and the same rendered article bodies', { skip: process.env.WIP_TEST_GENERATED !== '1' }, async () => { const data = JSON.parse(await readFile(new URL('../dist/worldspace.json', import.meta.url), 'utf8')); checkIdentity(data); assert.equal(data.nodes.length, documents.length + 5); assert.deepEqual(new Set(data.nodes.filter((node) => node.displayPath.endsWith('.md')).map((node) => node.displayPath.slice(1))), new Set(publicFiles)); for (const node of data.nodes) { const relative = node.displayPath === '/' ? 'index.html' : node.displayPath === '/book' ? 'book/index.html' : ['/sources', '/packages', '/playground'].includes(node.displayPath) ? `${node.displayPath.slice(1)}/index.html` : `book/${node.displayPath.slice(1, -3)}/index.html`; const page = load(await readFile(new URL(`../dist/${relative}`, import.meta.url), 'utf8')); // Astro adds hydration/render markers outside the actual article fragment. // Compare normalized markup, not generated comment/whitespace placement. const fragment = load(node.html, {}, false); const article = page('[data-selected-content]'); assert.equal(article.length, 1, `${relative} has a document body`); assert.equal(fragment.text().replace(/\s+/g, ' ').trim(), article.text().replace(/\s+/g, ' ').trim(), node.path); assert.deepEqual(fragment('a[href]').map((_, item) => fragment(item).attr('href')).get(), article.find('a[href]').map((_, item) => page(item).attr('href')).get(), node.path); } });