feat: adopt strict TDD framework and add adversarial test suite
- Update AGENTS.md and .agents/rules/blog-system.md with strict TDD mandate - Extract Eleventy custom filters into modular src/_filters/index.js for unit testing - Add npm test script using Node.js native test runner (node:test) - Create test/site-config.test.js for environment validation and fatal error assertions - Create test/filters.test.js for adversarial filter edge cases (dates, limits, reading time, headings) - Create test/frontmatter-schema.test.js enforcing post metadata contracts - Create test/design-rules.test.js enforcing strict anti-patterns (no glassmorphism, no shadows, max 4px radius) - Create test/build-integrity.test.js checking static output, zero 404 links, and TOC anchor targets
This commit is contained in:
+5
-52
@@ -1,7 +1,7 @@
|
||||
import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
|
||||
import markdownIt from "markdown-it";
|
||||
import markdownItAnchor from "markdown-it-anchor";
|
||||
import { DateTime } from "luxon";
|
||||
import { filters } from "./src/_filters/index.js";
|
||||
|
||||
export default function (eleventyConfig) {
|
||||
// 1. Static Asset Passthrough Copy
|
||||
@@ -32,57 +32,10 @@ export default function (eleventyConfig) {
|
||||
|
||||
eleventyConfig.setLibrary("md", markdownLib);
|
||||
|
||||
// 4. Custom Filters
|
||||
// Format readable dates: SEP 12, 2026
|
||||
eleventyConfig.addFilter("postDate", (dateObj) => {
|
||||
if (!dateObj) return "";
|
||||
return DateTime.fromJSDate(new Date(dateObj), { zone: "utc" })
|
||||
.toFormat("LLL dd, yyyy")
|
||||
.toUpperCase();
|
||||
});
|
||||
|
||||
// ISO date format for <time datetime="...">
|
||||
eleventyConfig.addFilter("isoDate", (dateObj) => {
|
||||
if (!dateObj) return "";
|
||||
return DateTime.fromJSDate(new Date(dateObj), { zone: "utc" }).toISODate();
|
||||
});
|
||||
|
||||
// Limit array items
|
||||
eleventyConfig.addFilter("limit", (arr, limit) => {
|
||||
if (!Array.isArray(arr)) return [];
|
||||
return arr.slice(0, limit);
|
||||
});
|
||||
|
||||
// Filter out internal system tags
|
||||
eleventyConfig.addFilter("filterTagList", (tags) => {
|
||||
const internalTags = ["all", "nav", "post", "posts"];
|
||||
return (tags || []).filter((tag) => !internalTags.includes(tag.toLowerCase()));
|
||||
});
|
||||
|
||||
// Reading time estimator (defaults to 200 words/min if frontmatter lacks readingTime)
|
||||
eleventyConfig.addFilter("readingTime", (text, frontmatterTime) => {
|
||||
if (frontmatterTime) return frontmatterTime;
|
||||
if (!text) return "1 MIN READ";
|
||||
const wordCount = text.trim().split(/\s+/).length;
|
||||
const minutes = Math.ceil(wordCount / 200);
|
||||
return `${minutes} MIN READ`;
|
||||
});
|
||||
|
||||
// Extract H2 and H3 headings with IDs for dynamic Table of Contents
|
||||
eleventyConfig.addFilter("extractHeadings", (htmlContent) => {
|
||||
if (!htmlContent) return [];
|
||||
const headingRegex = /<h([23])\s+id="([^"]+)"[^>]*>(.*?)<\/h\1>/gis;
|
||||
const headings = [];
|
||||
let match;
|
||||
while ((match = headingRegex.exec(htmlContent)) !== null) {
|
||||
headings.push({
|
||||
level: parseInt(match[1], 10),
|
||||
id: match[2],
|
||||
title: match[3].replace(/<[^>]+>/g, "").trim()
|
||||
});
|
||||
}
|
||||
return headings;
|
||||
});
|
||||
// 4. Custom Filters (imported from modular src/_filters/index.js)
|
||||
for (const [name, fn] of Object.entries(filters)) {
|
||||
eleventyConfig.addFilter(name, fn);
|
||||
}
|
||||
|
||||
// 5. Custom Collections
|
||||
// Chronological blog posts collection
|
||||
|
||||
Reference in New Issue
Block a user