Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
5.3 kB · 157 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158/** * The blog's feed, read out of the build. * * A feed is the one page on the site nobody looks at. It is fetched by * software, in a reader somewhere else, and every way it goes wrong is silent * from here: an unescaped ampersand in a title ends the document at that byte, * a relative href resolves against the reader rather than against the site, * and a feed stamped with the build time reports news on every deploy. * * Read out of dist/ rather than out of the source, because the source is a * function and the thing subscribers get is its output. `npm test` builds * first. * * Run with `npm test`. */import { test } from "node:test";import assert from "node:assert/strict";import { existsSync, readFileSync, readdirSync } from "node:fs";import { join } from "node:path";import { fileURLToPath } from "node:url";
const web = fileURLToPath(new URL("../", import.meta.url));const dist = join(web, "dist");
const SITE = "https://lance.blue";
const feedPath = join(dist, "atom.xml");const feed = existsSync(feedPath) ? readFileSync(feedPath, "utf8") : null;
/** Every occurrence of one element's text content, in document order. */function all(name) { return [...feed.matchAll(new RegExp(`<${name}>([^<]*)</${name}>`, "g"))].map( (m) => m[1], );}
/** The entries, as their own strings, so a field can be read per entry. */function entries() { return [...feed.matchAll(/<entry>([\s\S]*?)<\/entry>/g)].map((m) => m[1]);}
test("the build writes the feed the layout points at", () => { assert.ok(feed, `no ${feedPath}. The endpoint did not build.`);
const layout = readFileSync(join(web, "src/layouts/Base.astro"), "utf8"); const link = /<link\s+rel="alternate"\s+type="application\/atom\+xml"[\s\S]*?\/>/.exec( layout, ); assert.ok( link, "Base.astro no longer advertises a feed, so nothing finds it", ); assert.match( link[0], /href="\/atom\.xml"/, "the layout points somewhere the build does not write", );});
test("nothing in the document is text that XML would read as markup", () => { // The failure this exists for: a title with an ampersand in it. Every reader // stops at that byte, and the site looks fine. const bare = [ ...feed.matchAll(/&(?!(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-f]+);)/gi), ]; assert.equal( bare.length, 0, `${bare.length} bare & in the feed; the first is at ${bare[0]?.index}`, );
// Angle brackets are harder to catch by eye and the same class of bug, so // pin the shape instead: every < in the document opens a tag this file wrote. const tags = [...feed.matchAll(/<[^>]*>/g)].length; const opens = [...feed.matchAll(/</g)].length; assert.equal(opens, tags, "a < in the feed is not opening a tag");});
test("every address a reader is given is absolute", () => { for (const href of [...feed.matchAll(/href="([^"]*)"/g)].map((m) => m[1])) { assert.ok(href.startsWith(SITE), `${href} is not absolute against ${SITE}`); } for (const id of all("id")) { assert.ok(id.startsWith(SITE), `id ${id} is not absolute against ${SITE}`); }});
test("the feed says where it is, and where its human page is", () => { assert.match( feed, new RegExp(`rel="self"[^>]*href="${SITE}/atom\\.xml"`), "no rel=self, so a reader handed this document cannot refetch it", ); assert.match( feed, new RegExp(`rel="alternate" type="text/html" href="${SITE}/blog"`), "no alternate, so there is nowhere to send a human", );});
test("there is one entry per post the build published", () => { const built = readdirSync(join(dist, "blog"), { withFileTypes: true }) .filter((e) => e.isDirectory()) .map((e) => e.name); assert.ok(built.length, "the build wrote no posts at all");
const linked = entries().map((entry) => /<id>([^<]*)<\/id>/.exec(entry)?.[1]); assert.deepEqual( [...linked].sort(), built.map((slug) => `${SITE}/blog/${slug}`).sort(), "the feed and the built pages disagree about what has been posted", );});
test("the feed is dated by its newest post, not by the build", () => { // A feed stamped at build time changes on every deploy, and a reader that // trusts the field reports news on a day nothing was written. const feedUpdated = all("updated")[0]; const perEntry = entries().map( (entry) => /<updated>([^<]*)<\/updated>/.exec(entry)?.[1], ); assert.ok(perEntry.length, "no entries to date the feed by");
const newest = perEntry.slice().sort().at(-1); assert.equal( feedUpdated, newest, "the feed's <updated> is not its newest entry's", );
// The entries are newest first, which is the order a reader shows them in // when it has nothing else to go on. assert.deepEqual( perEntry, perEntry.slice().sort().reverse(), "the entries are not newest first", );});
test("a draft is not in the feed", () => { const posts = join(web, "src/content/blog"); const drafts = readdirSync(posts) .filter((name) => name.endsWith(".md")) .filter((name) => /^draft:\s*true$/m.test(readFileSync(join(posts, name), "utf8")), ) .map((name) => name.replace(/\.md$/, ""));
for (const slug of drafts) { assert.doesNotMatch( feed, new RegExp(`${SITE}/blog/${slug}\\b`), `${slug} is a draft and is in the feed`, ); }});