import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { existsSync, readFileSync, statSync } from "node:fs"; import path from "node:path"; import test from "node:test"; import { zhOnlyArticleRoutes } from "./article-routes.mjs"; const root = path.resolve(import.meta.dirname, ".."); const docs = JSON.parse(readFileSync(path.join(root, "docs.json"), "utf8")); const manifest = JSON.parse(readFileSync(path.join(root, "scripts/blog-articles-manifest.json"), "utf8")); const localeConfig = { en: { language: "en", tab: "Articles" }, zh: { language: "cn", tab: "文章" }, ja: { language: "jp", tab: "記事" }, }; const extraArticlePages = { en: [], zh: zhOnlyArticleRoutes.map((route) => `zh/${route}`), ja: [], }; test("all canonical Blog slugs are published in every Wiki locale", () => { const slugsByLocale = new Map(); for (const locale of Object.keys(localeConfig)) { const items = manifest.articles.filter((article) => article.locale === locale); assert.ok(items.length > 0, `${locale} has no articles`); slugsByLocale.set(locale, items.map((article) => article.slug).sort()); } assert.deepEqual(slugsByLocale.get("zh"), slugsByLocale.get("en")); assert.deepEqual(slugsByLocale.get("ja"), slugsByLocale.get("en")); }); test("article tabs group every generated Blog page exactly once", () => { for (const [locale, config] of Object.entries(localeConfig)) { const language = docs.navigation.languages.find((item) => item.language === config.language); const matchingTabs = language.tabs.filter((item) => item.tab === config.tab); assert.equal(matchingTabs.length, 1, `${locale} must have exactly one ${config.tab} tab`); const tab = matchingTabs[0]; const pages = tab.groups.flatMap((group) => group.pages).filter((page) => page !== `${locale}/articles/index`); const manifestPages = manifest.articles .filter((article) => article.locale === locale) .map((article) => article.page); const extras = extraArticlePages[locale]; assert.deepEqual( extras.filter((page) => manifestPages.includes(page)), [], `${locale} extra article pages must not overlap with the synchronized manifest`, ); const expected = [...new Set(manifestPages.concat(extras))]; assert.deepEqual([...pages].sort(), [...expected].sort()); assert.equal(new Set(pages).size, pages.length, `${locale} has duplicate article navigation entries`); } }); test("generated articles use only non-empty Wiki-local images", () => { const expectedImages = new Map(manifest.images.map((image) => [image.path, image])); for (const article of manifest.articles) { const file = path.join(root, `${article.page}.mdx`); assert.ok(existsSync(file), `missing ${article.page}.mdx`); const content = readFileSync(file, "utf8"); assert.doesNotMatch(content, /\]\(\/(?:en|zh|ja)\/blog\//, `${article.page} has a Blog link relative to the docs mount`); const imageUrls = [...content.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)].map((match) => match[1]); for (const imageUrl of imageUrls) { assert.match(imageUrl, /^\/images\/articles\//, `${article.page} hotlinks ${imageUrl}`); const image = path.join(root, imageUrl.slice(1)); assert.ok(existsSync(image), `${article.page} references missing ${imageUrl}`); assert.ok(statSync(image).size > 0, `${imageUrl} is empty`); const bytes = readFileSync(image); const expected = expectedImages.get(imageUrl); assert.ok(expected, `${imageUrl} is missing from the synchronization manifest`); assert.equal(bytes.length, expected.bytes, `${imageUrl} size differs from the manifest`); assert.equal(createHash("sha256").update(bytes).digest("hex"), expected.sha256, `${imageUrl} digest differs from the manifest`); } } }); test("documentation-only articles and their images survive Blog synchronization", () => { for (const pages of Object.values(extraArticlePages)) { for (const page of pages) { const content = readFileSync(path.join(root, `${page}.mdx`), "utf8"); assert.ok(content.length > 100, `${page} is empty`); for (const [, url] of content.matchAll(/!\[[^\]]*\]\((\/images\/articles\/[^)]+)\)/g)) { const bytes = readFileSync(path.join(root, url.slice(1))); const image = manifest.images.find((item) => item.path === url); assert.ok(image, `${page}: ${url} missing from image manifest`); assert.equal(createHash("sha256").update(bytes).digest("hex"), image.sha256); } } } });