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:
venus
2026-09-13 01:16:20 -05:00
parent fc6c17b541
commit e4601dbbcb
11 changed files with 532 additions and 66 deletions
+71
View File
@@ -0,0 +1,71 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
const postsDir = path.resolve("src/posts");
test("Content Contract: All posts in src/posts/ satisfy frontmatter schema", () => {
assert.ok(fs.existsSync(postsDir), "src/posts directory must exist");
const postFiles = fs.readdirSync(postsDir).filter((f) => f.endsWith(".md"));
assert.ok(postFiles.length > 0, "There must be at least one published post");
for (const file of postFiles) {
const filePath = path.join(postsDir, file);
const content = fs.readFileSync(filePath, "utf-8");
// 1. Filename Convention: YYYY-MM-DD-slug.md
assert.match(
file,
/^\d{4}-\d{2}-\d{2}-[a-z0-9-]+\.md$/,
`Post filename '${file}' must follow 'YYYY-MM-DD-slug.md' naming standard`
);
// 2. YAML Frontmatter Boundary check
assert.match(
content,
/^---\n([\s\S]*?)\n---/,
`Post '${file}' must start with valid YAML frontmatter delimiters`
);
// Extract frontmatter block
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
const fmText = fmMatch[1];
// 3. Title Invariant
assert.match(
fmText,
/title:\s*"[^"]{5,}"/,
`Post '${file}' must contain a valid title of at least 5 characters`
);
// 4. Date Invariant (ISO Format)
assert.match(
fmText,
/date:\s*\d{4}-\d{2}-\d{2}/,
`Post '${file}' must define an ISO date (YYYY-MM-DD)`
);
// 5. Layout Invariant
assert.match(
fmText,
/layout:\s*layouts\/post\.njk/,
`Post '${file}' must specify 'layout: layouts/post.njk'`
);
// 6. Tags Invariant
assert.match(
fmText,
/tags:\s*\n(\s+-\s+[a-z0-9-]+\n)+/,
`Post '${file}' must specify a non-empty YAML tags list`
);
// 7. Excerpt Invariant
assert.match(
fmText,
/excerpt:\s*"[^"]{20,}"/,
`Post '${file}' must provide a substantive excerpt of at least 20 characters`
);
}
});