import assert from "node:assert/strict";
import { access, readdir, readFile, stat } from "node:fs/promises";
import path from "node:path";
import test from "node:test";
import { collectMdxDocuments } from "../scripts/prepare-fumapress.mjs";
import { zhOnlyArticleRoutes } from "./article-routes.mjs";
const root = path.resolve(import.meta.dirname, "..");
const articleManifest = JSON.parse(await readFile(path.join(root, "scripts/blog-articles-manifest.json"), "utf8"));
// The fixed documentation baseline excludes synchronized Blog pages.
const expectedDocuments = 299 + articleManifest.articles.length;
const publicRoot = path.join(root, "dist/public");
test("the documentation keeps a neutral base with custom section and TOC treatments", async () => {
const css = await readFile(path.join(root, "src/app.css"), "utf8");
assert.match(css, /fumadocs-ui\/css\/neutral\.css/);
assert.doesNotMatch(css, /fumadocs-ui\/css\/ocean\.css/);
assert.match(css, /#nd-toc > div > div/);
assert.match(css, /#nd-toc > div > div > a > svg/);
assert.match(css, /#nd-toc > div > div > div\.absolute/);
assert.match(css, /\[data-toc-popover-content\].*> div\.absolute/s);
assert.match(css, /a\[data-active="true"\]::before/);
assert.match(css, /\[data-toc-popover-content\] a\[href\^="#"\]\[data-active="true"\]::before/);
assert.match(css, /inset-inline-start: 0\.5rem/);
assert.match(css, /border-radius: 0/);
});
async function collectHtmlRoutes(directory) {
const routes = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
const target = path.join(directory, entry.name);
if (entry.isDirectory()) routes.push(...await collectHtmlRoutes(target));
else if (entry.name === "index.html") routes.push(target);
}
return routes;
}
function staticHtmlForCanonicalDocument(document) {
const parts = document.replace(/\.mdx$/, "").split("/");
if (parts.at(-1) === "index") parts.pop();
return path.join(publicRoot, ...parts, "index.html");
}
function staticHtmlForUrlPath(urlPath) {
const parts = urlPath.replace(/^\/docs(?:\/|$)/, "").replace(/^\//, "").replace(/\/$/, "").split("/")
.map((segment) => decodeURIComponent(segment));
return path.join(publicRoot, ...parts, "index.html");
}
test("all canonical localized documents have static HTML", async () => {
const documents = await collectMdxDocuments(root);
assert.equal(documents.length, expectedDocuments);
const missing = [];
for (const document of documents) {
try {
await access(staticHtmlForCanonicalDocument(document));
} catch {
missing.push(document);
}
}
assert.deepEqual(missing, []);
});
test("every fixed redirect destination resolves to generated output", async () => {
const redirects = (await readFile(path.join(publicRoot, "_redirects"), "utf8"))
.trim().split("\n").map((line) => line.split(/\s+/));
for (const [source, destination] of redirects) {
if (destination.includes(":splat")) continue;
await access(staticHtmlForUrlPath(destination), undefined, `${source} -> ${destination}`);
}
});
test("static output uses decoded filesystem paths for edge hosting", async () => {
const encodedComponents = [];
async function walk(directory) {
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (/%[0-9A-Fa-f]{2}/.test(entry.name)) encodedComponents.push(path.join(directory, entry.name));
if (entry.isDirectory()) await walk(path.join(directory, entry.name));
}
}
await walk(publicRoot);
assert.deepEqual(encodedComponents, []);
});
test("static routes never repeat their locale prefix", async () => {
for (const locale of ["en", "zh", "ja"]) {
await assert.rejects(access(path.join(publicRoot, locale, locale)), { code: "ENOENT" });
}
});
test("representative localized guides and canonical host are emitted", async () => {
for (const locale of ["en", "zh", "ja"]) {
await access(path.join(publicRoot, locale, "insight/guide/index.html"));
}
const sitemap = await readFile(path.join(publicRoot, "sitemap.xml"), "utf8");
const robots = await readFile(path.join(publicRoot, "robots.txt"), "utf8");
assert.match(sitemap, /https:\/\/langbot\.app\/docs\//);
assert.doesNotMatch(sitemap, /https:\/\/docs\.langbot\.app\//);
assert.match(robots, /https:\/\/langbot\.app\/docs\/sitemap\.xml/);
assert.doesNotMatch(robots, /https:\/\/docs\.langbot\.app\//);
});
test("platform navigation renders logo assets instead of icon path text", async () => {
for (const locale of ["en", "zh", "ja"]) {
const html = await readFile(
path.join(publicRoot, locale, "usage/platforms/discord/index.html"),
"utf8",
);
const asideStart = html.indexOf('
`), `${locale} ${tab.tab}/${group.group} is not a section label`);
}
}
}
});
test("every legacy navigation entry resolves to its exact sidebar URL", async () => {
const docs = JSON.parse(await readFile(path.join(root, "docs.json"), "utf8"));
const localeMap = { en: "en", cn: "zh", jp: "ja" };
const failures = [];
for (const language of docs.navigation.languages) {
const locale = localeMap[language.language];
const pages = collectNavigationPages(language.tabs ?? []);
for (const page of pages) {
if (!page.startsWith(`${locale}/`)) continue;
const route = page.replace(/\/index$/, "");
const htmlPath = path.join(publicRoot, route, "index.html");
const html = await readFile(htmlPath, "utf8");
const asideStart = html.indexOf('