/** * The sitemap and robots.txt, read out of the build. * * Both are fetched by software somewhere else, and every way they go wrong is * silent from here: a relative loc resolves against the crawler, a page the * sitemap names but the build did not write is a soft error a search console * reports weeks later, and a robots.txt that names a sitemap that is not * there is just ignored. * * Read out of dist/ rather than out of the source, because the source is a * function and the thing crawlers 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 } 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 sitemapPath = join(dist, "sitemap.xml"); const sitemap = existsSync(sitemapPath) ? readFileSync(sitemapPath, "utf8") : null; /** Every , in document order. */ function locs() { return [...sitemap.matchAll(/([^<]*)<\/loc>/g)].map((m) => m[1]); } test("the build writes the sitemap robots.txt points at", () => { assert.ok(sitemap, `no ${sitemapPath}. The endpoint did not build.`); const robotsPath = join(dist, "robots.txt"); assert.ok(existsSync(robotsPath), `no ${robotsPath}`); const robots = readFileSync(robotsPath, "utf8"); const named = /^Sitemap:\s*(\S+)$/m.exec(robots); assert.ok(named, "robots.txt names no sitemap"); assert.equal(named[1], `${SITE}/sitemap.xml`); }); test("every loc is absolute and behind a file the build wrote", () => { for (const loc of locs()) { assert.ok(loc.startsWith(`${SITE}/`) || loc === `${SITE}/`, loc); // The key a loc is served from: "/" is index.html at the root, and every // other page is a directory holding one - trailingSlash "never" means the // extensionless copy push.sh writes comes from the same file. const path = loc.slice(SITE.length); const file = path === "/" ? "index.html" : join(path.slice(1), "index.html"); assert.ok(existsSync(join(dist, file)), `${loc} names no ${file}`); } }); test("the pages are there and 404.html is not", () => { const all = locs(); assert.ok(all.includes(`${SITE}/`), "no home page"); assert.ok(all.includes(`${SITE}/blog`), "no blog index"); assert.ok(!all.some((loc) => loc.includes("404")), "404 is not a page"); }); test("posts carry a lastmod and the indexes do not", () => { const urls = [...sitemap.matchAll(/([\s\S]*?)<\/url>/g)].map( (m) => m[1], ); for (const url of urls) { const loc = /([^<]*)<\/loc>/.exec(url)[1]; const hasLastmod = /\d{4}-\d{2}-\d{2}<\/lastmod>/.test(url); const isPost = loc.startsWith(`${SITE}/blog/`); assert.equal(hasLastmod, isPost, loc); } });