import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { syncContent, pairCodeTabs, mdxSafeProse, slugify, BLOB_BASE } from "./sync-content.mjs"; function fixture(lessons, extra = {}) { const root = mkdtempSync(join(tmpdir(), "sync-")); for (const [dir, md] of Object.entries(lessons)) { mkdirSync(join(root, "course", dir), { recursive: true }); writeFileSync(join(root, "course", dir, "lesson.md"), md); } for (const [f, md] of Object.entries(extra)) { mkdirSync(join(root, f, ".."), { recursive: true }); writeFileSync(join(root, f), md); } return root; } const run = (root) => { const out = join(root, "out"); syncContent({ repoRoot: root, outDir: out }); return (f) => readFileSync(join(out, f), "utf8"); }; test("sync_rewrites_and_checks_links", () => { const root = fixture( { "01-a": "# Module 1: A\n\nSee [two](../02-x/lesson.md) and [anchor](../02-x/lesson.md#expansion-drop), [tool](../../tools/t.mjs), [sc](./scenarios.md), [ext](https://example.com/a).\n", "02-x": "# Module 2: X\n\n## Expansion Drop\n\nback to [one](../01-a/lesson.md).\n", }, { "tools/t.mjs": "//", "course/01-a/scenarios.md": "# s\n" }, ); const page = run(root); const one = page("01-a.mdx"); assert.match(one, /title: "Module 1: A"/); assert.match(one, /order: 1/); assert.doesNotMatch(one, /^# Module/m); assert.match(one, /\[two\]\(\/course\/02-x\/\)/); assert.match(one, /\[anchor\]\(\/course\/02-x\/#expansion-drop\)/); assert.match(one, new RegExp(`\\[tool\\]\\(${BLOB_BASE}tools/t\\.mjs\\)`)); assert.match(one, new RegExp(`\\[sc\\]\\(${BLOB_BASE}course/01-a/scenarios\\.md\\)`)); assert.match(one, /\(https:\/\/example\.com\/a\)/); const dead = fixture({ "01-a": "# A\n\n[gone](../09-none/lesson.md)\n" }); assert.throws(() => run(dead), /dead link/); const deadFile = fixture({ "01-a": "# A\n\n[gone](./missing.md)\n" }); assert.throws(() => run(deadFile), /dead link/); }); test("sync_rejects_dead_anchors", () => { const cross = fixture({ "01-a": "# A\n\n[x](../02-x/lesson.md#nope)\n", "02-x": "# X\n\n## Real\n" }); assert.throws(() => run(cross), /dead anchor/); const own = fixture({ "01-a": "# A\n\n## Play it\n\n[ok](#play-it) [bad](#play-it-now)\n" }); assert.throws(() => run(own), /dead anchor #play-it-now/); }); test("slugify_matches_heading_ids", () => { assert.equal(slugify("Hint 1 — nudge"), "hint-1--nudge"); assert.equal(slugify("The `trap` in the wild"), "the-trap-in-the-wild"); }); const FENCE = "```"; test("sync_pairs_code_tabs", () => { const md = `intro\n\n${FENCE}rust\nfn a() {}\n${FENCE}\n${FENCE}ts\nfunction a() {}\n${FENCE}\n\nlone:\n\n${FENCE}rust\nfn b() {}\n${FENCE}\n\ntext\n\n${FENCE}sh\nx\n${FENCE}\n${FENCE}sh\ny\n${FENCE}\n`; const { md: out, used } = pairCodeTabs(md); assert.ok(used); assert.equal(out.match(//g).length, 1); assert.match(out, /\n\n```rust\nfn a\(\) \{\}\n```\n\n<\/TabItem>\n\n\n```ts\nfunction a/); assert.match(out, /lone:\n\n```rust\nfn b/); // lone block untouched assert.match(out, /```sh\nx\n```\n```sh\ny\n```/); // adjacent non-rust/ts blocks untouched const root = fixture({ "01-a": `# A\n\n${FENCE}rust\nr\n${FENCE}\n${FENCE}ts\nt\n${FENCE}\n` }); assert.match(run(root)("01-a.mdx"), /^import \{ Tabs, TabItem \} from "@astrojs\/starlight\/components";$/m); const none = fixture({ "01-a": `# A\n\n${FENCE}rust\nr\n${FENCE}\n` }); assert.doesNotMatch(run(none)("01-a.mdx"), /import/); }); test("sync_strips_comments_and_escapes_mdx", () => { const out = mdxSafeProse("a b {x} and `{kept}`
S 1 < 2"); assert.doesNotMatch(out, /\n\n${FENCE}rust\nif a < b { }\n${FENCE}\n` }); const page = run(root)("01-a.mdx"); assert.doesNotMatch(page, /hidden/); assert.match(page, /if a < b \{ \}/); // code untouched }); test("sync_writes_harness_and_ignores_missing_dirs", () => { const root = fixture({ "01-a": "# A\n\n[h](../harness.md)\n" }, { "course/harness.md": "# Harness contract\n" }); const page = run(root); assert.match(page("01-a.mdx"), /\(\/course\/harness\/\)/); assert.match(page("harness.mdx"), /title: "Harness contract"/); assert.deepEqual(readdirSync(join(root, "out")).sort(), ["01-a.mdx", "harness.mdx"]); });