import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { execSync } from "node:child_process"; import { isPostArchived, isPostDraft, isPostHidden, isPostPublic, postComparator } from "../src/_utils/sort-posts.js"; import { normalizeObsidianMarkdown } from "../server.js"; import configureEleventy from "../eleventy.config.js"; test("Archived Posts: isPostArchived accurately identifies archive flags across data shapes", () => { // 1. Explicit boolean true assert.equal(isPostArchived({ data: { archive: true } }), true, "Explicit boolean true in data"); assert.equal(isPostArchived({ archive: true }), true, "Root level archive true"); // 2. Truthy string and numeric representations (case-insensitive & trimmed) assert.equal(isPostArchived({ data: { archive: "true" } }), true, "String 'true'"); assert.equal(isPostArchived({ data: { archive: "TRUE" } }), true, "Uppercase string 'TRUE'"); assert.equal(isPostArchived({ data: { archive: " true " } }), true, "Whitespace-padded 'true'"); assert.equal(isPostArchived({ data: { archive: "yes" } }), true, "String 'yes'"); assert.equal(isPostArchived({ data: { archive: "YES" } }), true, "String 'YES'"); assert.equal(isPostArchived({ data: { archive: "1" } }), true, "String '1'"); assert.equal(isPostArchived({ data: { archive: 1 } }), true, "Numeric 1"); // 3. Falsy representations assert.equal(isPostArchived({ data: { archive: false } }), false, "Explicit boolean false"); assert.equal(isPostArchived({ data: { archive: "false" } }), false, "String 'false'"); assert.equal(isPostArchived({ data: { archive: "no" } }), false, "String 'no'"); assert.equal(isPostArchived({ data: { archive: "0" } }), false, "String '0'"); assert.equal(isPostArchived({ data: { archive: 0 } }), false, "Numeric 0"); assert.equal(isPostArchived({ data: { archive: "" } }), false, "Empty string"); assert.equal(isPostArchived({ data: { archive: null } }), false, "Null property"); assert.equal(isPostArchived({ data: {} }), false, "Missing archive property"); assert.equal(isPostArchived({}), false, "Empty post object"); // 4. Adversarial edge cases assert.equal(isPostArchived(null), false, "Null input"); assert.equal(isPostArchived(undefined), false, "Undefined input"); assert.equal(isPostArchived("unexpected-string"), false, "Primitive string input"); assert.equal(isPostArchived(12345), false, "Primitive number input"); assert.equal(isPostArchived([]), false, "Array input"); assert.equal(isPostArchived({ data: null }), false, "Null data property"); }); test("Archived Posts: isPostPublic excludes archived posts from active public dispatches", () => { assert.equal( isPostPublic({ data: { title: "Active Post", draft: false, hidden: false, archive: false } }), true, "Non-archived public post is public" ); assert.equal( isPostPublic({ data: { title: "Archived Post", archive: true } }), false, "Archived post is excluded from active public dispatches" ); assert.equal( isPostPublic({ data: { title: "Archived String Post", archive: "true" } }), false, "Archived string post is excluded from active public dispatches" ); }); test("Archived Posts: Eleventy collections separate active posts and archivedPosts while excluding drafts and hidden notes", () => { const collections = new Map(); configureEleventy({ setUseGitIgnore() {}, addPassthroughCopy() {}, addPlugin() {}, setLibrary() {}, addFilter() {}, addCollection(name, callback) { collections.set(name, callback); }, addGlobalData() {}, on() {}, setServerOptions() {} }); assert.ok(collections.has("posts"), "posts collection must be registered"); assert.ok(collections.has("archivedPosts"), "archivedPosts collection must be registered"); const samplePosts = [ { inputPath: "src/posts/2026-10-06-active-new.md", date: new Date("2026-10-06"), data: { title: "Active New", tags: ["systems"] } }, { inputPath: "src/posts/2026-10-05-active-old.md", date: new Date("2026-10-05"), data: { title: "Active Old", archive: false, tags: ["notes"] } }, { inputPath: "src/posts/2026-10-03-archived-new.md", date: new Date("2026-10-03"), data: { title: "Archived New", archive: true, tags: ["legacy-only"] } }, { inputPath: "src/posts/2026-10-01-archived-old.md", date: new Date("2026-10-01"), data: { title: "Archived Old", archive: "yes", tags: ["legacy-only"] } }, { inputPath: "src/posts/2026-10-04-archived-draft.md", date: new Date("2026-10-04"), data: { title: "Archived Draft", archive: true, draft: true, tags: ["draft-tag"] } }, { inputPath: "src/posts/2026-10-04-archived-hidden.md", date: new Date("2026-10-04"), data: { title: "Archived Hidden", archive: true, hidden: true, tags: ["hidden-tag"] } } ]; const mockCollectionApi = { getFilteredByGlob() { return samplePosts; } }; const activePosts = collections.get("posts")(mockCollectionApi); const archivedPosts = collections.get("archivedPosts")(mockCollectionApi); const tagList = collections.get("tagList")(mockCollectionApi); assert.deepEqual( activePosts.map((p) => p.data.title), ["Active New", "Active Old"], "collections.posts must only contain non-archived, non-draft, non-hidden posts" ); assert.deepEqual( archivedPosts.map((p) => p.data.title), ["Archived New", "Archived Old"], "collections.archivedPosts must only contain non-draft, non-hidden archived posts sorted chronologically" ); assert.ok(!tagList.includes("legacy-only"), "Archived-only tags must not leak into active Dispatches tagList"); assert.ok(!tagList.includes("draft-tag"), "Draft tags must not leak into tagList"); assert.ok(!tagList.includes("hidden-tag"), "Hidden tags must not leak into tagList"); }); test("Archived Posts: Archive Telemetry sidebar contains link to /archive/ and archive.njk template omits featured section", () => { const sidebarPath = path.resolve("src/_includes/partials/stats-sidebar.njk"); assert.ok(fs.existsSync(sidebarPath), "stats-sidebar.njk must exist"); const sidebarContent = fs.readFileSync(sidebarPath, "utf-8"); // Extract Archive Telemetry section and verify link to /archive const telemetryMatch = sidebarContent.match( /]*aria-label="Archive Telemetry"[^>]*>([\s\S]*?)<\/section>/i ); assert.ok(telemetryMatch, "Archive Telemetry section must exist in stats-sidebar.njk"); assert.match( telemetryMatch[1], /href="\/archive\/?"/i, "Archive Telemetry section must contain a link to /archive/ (riverrooks.dev/archive)" ); // Verify src/archive.njk exists and mirrors dispatches.njk minus featured section const archivePagePath = path.resolve("src/archive.njk"); assert.ok(fs.existsSync(archivePagePath), "src/archive.njk must exist"); const archivePageContent = fs.readFileSync(archivePagePath, "utf-8"); assert.match(archivePageContent, /layout:\s*layouts\/base\.njk/, "archive.njk must use base layout"); assert.match(archivePageContent, /collections\.archivedPosts/, "archive.njk must iterate over collections.archivedPosts"); assert.match(archivePageContent, /partials\/post-card\.njk/, "archive.njk must render post cards"); assert.doesNotMatch(archivePageContent, /featured-card\.njk/, "archive.njk must NOT include the featured section"); }); test("Archived Posts: Obsidian normalization preserves archive frontmatter property", () => { const obsidianNote = `--- title: "Legacy Architecture Notes" created: 2026-10-03 tags: ["#archive", "#systems"] archive: true --- # Legacy Notes Archived reference documentation. `; const normalized = normalizeObsidianMarkdown(obsidianNote); assert.match(normalized, /archive:\s*true/, "Normalized markdown must preserve 'archive: true'"); }); test("Archived Posts: Example archived markdown post exists in src/posts/ and builds to _site/archive/index.html", () => { const postsDir = path.resolve("src/posts"); const postFiles = fs.readdirSync(postsDir).filter((f) => f.endsWith(".md")); const archivedExamples = postFiles.filter((file) => { const content = fs.readFileSync(path.join(postsDir, file), "utf-8"); const fmMatch = content.match(/^---\n([\s\S]*?)\n---/); return fmMatch && /^archive:\s*true\s*$/m.test(fmMatch[1]); }); assert.ok( archivedExamples.length >= 1, "At least one example markdown post in src/posts/ must have 'archive: true'" ); const dispatchesHtmlPath = path.resolve("_site/dispatches/index.html"); const archiveHtmlPath = path.resolve("_site/archive/index.html"); if (!fs.existsSync(archiveHtmlPath)) { execSync("npm run build", { stdio: "pipe", env: { ...process.env, SITE_DOMAIN: process.env.SITE_DOMAIN || "riverrooks.dev" } }); } assert.ok(fs.existsSync(dispatchesHtmlPath), "_site/dispatches/index.html must exist"); assert.ok(fs.existsSync(archiveHtmlPath), "_site/archive/index.html must exist"); const dispatchesHtml = fs.readFileSync(dispatchesHtmlPath, "utf-8"); const archiveHtml = fs.readFileSync(archiveHtmlPath, "utf-8"); // 1. Dispatches page has link to /archive/ in Archive Telemetry const dispatchesTelemetry = dispatchesHtml.match( /]*aria-label="Archive Telemetry"[^>]*>([\s\S]*?)<\/section>/i ); assert.ok(dispatchesTelemetry, "Compiled dispatches page must have Archive Telemetry section"); assert.match( dispatchesTelemetry[1], /href="\/archive\/?"/i, "Compiled Archive Telemetry section must link to /archive/" ); // 2. Archived example post does NOT appear on Dispatches page, but DOES appear on Archive page assert.doesNotMatch( dispatchesHtml, /Archived Dispatch Demo/, "Archived post must NOT appear in the Dispatches page list or featured card" ); assert.match( archiveHtml, /Archived Dispatch Demo/, "Archived post MUST appear on the /archive/ page" ); // 3. Archive page does NOT have the Featured Dispatch section assert.doesNotMatch( archiveHtml, /aria-label="Featured Technical Dispatch"/i, "Archive page must not contain the Featured Dispatch section" ); });