feat: archive page

This commit is contained in:
venus
2026-10-27 17:33:34 -05:00
parent 59f807c45b
commit 0967d540cf
5 changed files with 327 additions and 4 deletions
+250
View File
@@ -0,0 +1,250 @@
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(
/<section[^>]*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(
/<section[^>]*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"
);
});