import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { resolveLinkedArticles, validateLinkedArticles } from "../src/_utils/linked-articles.js"; import { filters } from "../src/_filters/index.js"; import configureEleventy from "../eleventy.config.js"; test("Linked articles: accepts an article's list when every name identifies another post", () => { assert.doesNotThrow(() => { validateLinkedArticles([ { inputPath: "src/posts/2026-10-06-terminal-preview-demo.md", data: { title: "Terminal Preview Demo", linkedArticles: ["Markdown Preview Demo"] } }, { inputPath: "src/posts/2026-10-05-markdown-preview-demo.md", data: { title: "Markdown Preview Demo" } } ]); }); }); test("Linked articles: rejects a non-list frontmatter value", () => { assert.throws( () => { validateLinkedArticles([ { inputPath: "src/posts/2026-10-06-terminal-preview-demo.md", data: { title: "Terminal Preview Demo", linkedArticles: "Markdown Preview Demo" } }, { inputPath: "src/posts/2026-10-05-markdown-preview-demo.md", data: { title: "Markdown Preview Demo" } } ]); }, /must define 'linkedArticles' as a YAML list/ ); }); test("Linked articles: rejects empty and unknown article names", () => { const posts = [ { inputPath: "src/posts/2026-10-06-terminal-preview-demo.md", data: { title: "Terminal Preview Demo", linkedArticles: [""] } }, { inputPath: "src/posts/2026-10-05-markdown-preview-demo.md", data: { title: "Markdown Preview Demo" } } ]; assert.throws(() => validateLinkedArticles(posts), /non-empty article titles/); posts[0].data.linkedArticles = ["Missing Dispatch"]; assert.throws(() => validateLinkedArticles(posts), /links to unknown article 'Missing Dispatch'/); }); test("Linked articles: rejects self-references and duplicate navigation targets", () => { const posts = [ { inputPath: "src/posts/2026-10-06-terminal-preview-demo.md", data: { title: "Terminal Preview Demo", linkedArticles: ["Terminal Preview Demo"] } }, { inputPath: "src/posts/2026-10-05-markdown-preview-demo.md", data: { title: "Markdown Preview Demo" } } ]; assert.throws(() => validateLinkedArticles(posts), /cannot link to itself/); posts[0].data.linkedArticles = ["Markdown Preview Demo", "Markdown Preview Demo"]; assert.throws(() => validateLinkedArticles(posts), /cannot contain duplicate article titles/); }); test("Linked articles: the Eleventy posts collection rejects invalid frontmatter", () => { const collections = new Map(); configureEleventy({ setUseGitIgnore() {}, addPassthroughCopy() {}, addPlugin() {}, setLibrary() {}, addFilter() {}, addCollection(name, callback) { collections.set(name, callback); }, setServerOptions() {} }); assert.throws( () => { collections.get("posts")({ getFilteredByGlob() { return [ { inputPath: "src/posts/2026-10-06-terminal-preview-demo.md", data: { title: "Terminal Preview Demo", linkedArticles: ["Missing Dispatch"] } } ]; } }); }, /links to unknown article 'Missing Dispatch'/ ); }); test("Linked articles: resolves named posts in the frontmatter order for navigation", () => { const posts = [ { url: "/dispatches/terminal-preview-demo/", data: { title: "Terminal Preview Demo" } }, { url: "/dispatches/markdown-preview-demo/", data: { title: "Markdown Preview Demo" } } ]; assert.deepEqual( resolveLinkedArticles(posts, ["Markdown Preview Demo", "Terminal Preview Demo"]), [posts[1], posts[0]] ); }); test("Linked articles: exposes navigation resolution to Nunjucks", () => { assert.strictEqual(filters.resolveLinkedArticles, resolveLinkedArticles); }); test("Linked articles: post layout renders Other Articles at the top of dispatch telemetry", () => { const postLayout = fs.readFileSync( path.resolve("src/_includes/layouts/post.njk"), "utf-8" ); assert.match( postLayout, /{% set linkedPosts = collections\.posts \| resolveLinkedArticles\(linkedArticles\) %}[\s\S]*?