fix: reduce landing to existing documents and their tree
Reference site / build (push) Canceled after 0s

This commit is contained in:
2026-10-13 19:06:31 +09:00
parent 822272cad3
commit 6e9e675c70
16 changed files with 479 additions and 1348 deletions
+75
View File
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict';
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { contentsRoot } from '../plugins/document-links.mjs';
import { documentTree, childrenOf, visibleNodes, treePrefix, serializeTree } from '../src/lib/landing-tree.ts';
async function sourceFiles(directory, prefix = '') {
const entries = await readdir(directory, { withFileTypes: true });
return (await Promise.all(entries.map(async (entry) => {
const relative = prefix + entry.name;
if (entry.isDirectory()) return sourceFiles(path.join(directory, entry.name), relative + '/');
return entry.name.endsWith('.md') ? [relative] : [];
}))).flat();
}
const files = await sourceFiles(contentsRoot);
const documents = await Promise.all(files.map(async (file) => {
const body = await readFile(path.join(contentsRoot, file), 'utf8');
return { id: file.slice(0, -3), title: body.match(/^#\s+(.+)$/m)?.[1], href: '/book/' + file.slice(0, -3) + '/', html: body };
}));
const nodes = documentTree(documents);
test('tree mirrors only real content files and their directory parents', () => {
assert.deepEqual(nodes.filter((node) => node.href).map((node) => node.path).sort(), files.map((file) => '/' + file).sort());
const directories = new Set(['/']);
for (const file of files) {
const segments = file.split('/');
for (let length = 1; length < segments.length; length++) directories.add('/' + segments.slice(0, length).join('/'));
}
assert.deepEqual(new Set(nodes.filter((node) => !node.href).map((node) => node.path)), directories);
assert.equal(new Set(nodes.map((node) => node.path)).size, nodes.length);
for (const node of nodes.slice(1)) {
assert.equal(node.name, node.path.split('/').at(-1));
assert.equal(node.depth, node.path.split('/').length);
assert.ok(childrenOf(nodes, node.parent).includes(node));
assert.ok(nodes.findIndex((parent) => parent.path === node.parent) < nodes.indexOf(node));
}
});
test('existing titles, hrefs and content pass through without added descriptions or operations', () => {
for (const document of documents) {
const node = nodes.find((node) => node.path === '/' + document.id + '.md');
assert.equal(node.html, document.html);
assert.equal(node.title, document.title);
assert.equal(node.href, document.href);
assert.equal('description' in node, false);
assert.equal('interface' in node, false);
}
for (const folder of nodes.filter((node) => !node.href)) {
assert.equal(folder.html, undefined);
assert.equal(folder.title, undefined);
}
});
test('collapse hides descendants only and joins follow the visible tree', () => {
assert.deepEqual(visibleNodes(nodes, new Set()), nodes);
assert.deepEqual(visibleNodes(nodes, new Set(['/'])), [nodes[0]]);
const collapsed = visibleNodes(nodes, new Set(['/draft']));
assert.ok(collapsed.some((node) => node.path === '/draft'));
assert.ok(!collapsed.some((node) => node.path.startsWith('/draft/')));
for (const shown of [nodes, collapsed]) {
for (const node of shown) {
const prefix = treePrefix(node, nodes, shown);
assert.equal(prefix.length, (node.depth - 1) * 4);
if (node.parent) assert.ok(prefix.endsWith(childrenOf(shown, node.parent).at(-1) === node ? '└── ' : '├── '));
}
}
});
test('embedded data cannot close its JSON script and roundtrips exact content', () => {
const input = documentTree([{ id: 'source', title: '<title>', href: '/book/source/', html: '</script><script>alert(1)</script>\u2028&' }]);
const serialized = serializeTree(input);
assert.equal(serialized.includes('<'), false);
assert.deepEqual(JSON.parse(serialized), input);
});