feat: power reference site with in-process WIP WASM
Add the public-document Worldspace, Rust Client/Host bridge, and WIP-backed tree and document rendering while preserving static fallback and navigation. Pin the Rust source and build tools, verify resource and trap boundaries, and cover dev and preview loading including same-origin generated module URLs.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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 { bookTree } from '../src/lib/landing-tree.ts';
|
||||
import { createWorldspaceData } from '../src/lib/worldspace-data.ts';
|
||||
|
||||
const pages = {
|
||||
'/': { title: 'Worldspace Interface Protocol', href: '/', html: '<h1>Home</h1>' },
|
||||
'/book': { title: 'Book', href: '/book/', html: '<h1>Book</h1>' },
|
||||
};
|
||||
const source = (id) => ({ id, title: id, href: `/book/${id}/`, html: `<h1>${id}</h1>` });
|
||||
|
||||
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 = bookTree(documents);
|
||||
assert.equal(data.nodes.length, documents.length + 2);
|
||||
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).html);
|
||||
assert.match(node.html, /<h1/);
|
||||
}
|
||||
}
|
||||
const section = data.nodes.find((node) => 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('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/' },
|
||||
'/other': { title: 'other/', href: '/reference/book/other/', html: '<h1>other/</h1>' },
|
||||
});
|
||||
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 and book index content is supplied by their existing page renderers', () => {
|
||||
const data = createWorldspaceData([], pages);
|
||||
assert.deepEqual(data.nodes, [
|
||||
{ path: '/', name: '', displayPath: '/', ...pages['/'] },
|
||||
{ path: '/book', name: 'book', displayPath: '/book', parent: '/', ...pages['/book'] },
|
||||
]);
|
||||
});
|
||||
|
||||
// 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 + 2);
|
||||
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'
|
||||
: `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);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user