Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
3.9 kB · 115 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116/** * The hero's rotating host name is a contract between three files, and every * way of breaking it is silent — the page still renders a sentence, so nothing * throws and nothing looks wrong until you watch it for ten seconds. * * host-cycle.ts writes class names, styles.css is the only thing that gives * them meaning, and home.ts supplies the sentence around them. The tests here * are the three mistakes that were actually made building it: a full stop left * behind in the paragraph, unshown names hidden in a way that stops them * holding the box open, and a name rolled away with opacity alone so it stayed * in the page for a screen reader. * * Run with `npm test`. */import { test } from "node:test";import assert from "node:assert/strict";import { readFile } from "node:fs/promises";import { join } from "node:path";
const web = join(new URL(".", import.meta.url).pathname, "..");const read = (path) => readFile(join(web, path), "utf8");
const cycle = await read("src/host-cycle.ts");const home = await read("src/screens/home.ts");const css = await read("src/styles.css");
const hosts = [...cycle.matchAll(/name: "([^"]+)"/g)].map((m) => m[1]);assert.ok( hosts.length > 1, "host-cycle.ts no longer declares HOSTS as literals",);
test("every host name carries its own full stop", () => { // The paragraph does not supply one: it cannot, because the names are stacked // in one box and anything after them would sit at the far edge of the widest. for (const host of hosts) { assert.ok( host.endsWith("."), `"${host}" would end the sentence unpunctuated`, ); }});
test("every host brings both of its colours, and both follow the theme", () => { const entries = [ ...cycle.matchAll(/name: "([^"]+)",\s*ink: (.+),\s*glow: (.+),/g), ]; assert.equal( entries.length, hosts.length, "a host is missing an ink or a glow, and would fall back to plain text", ); for (const [, name, ink, glow] of entries) { for (const [what, value] of [ ["ink", ink], ["glow", glow], ]) { assert.match( value, /light-dark\(|var\(--/, `${name} picks one ${what} for both themes; the toggle cannot change it ` + "and one of the two pages gets a colour that does not read", ); } }});
test("the sentence does not end in a full stop of its own", () => { const at = home.indexOf("hero-lead"); const lead = home.slice(at, home.indexOf("form,", at)); assert.ok(lead.includes("hostCycle()"), "the hero lead no longer rotates"); assert.ok( !/hostCycle\(\),\s*"\."/.test(lead), "home.ts puts a full stop after the box; the names bring their own", );});
test("every class host-cycle.ts sets is one styles.css knows", () => { for (const [, name] of cycle.matchAll( /className: "cycle-word ?([a-z-]*)"/g, )) { if (!name) continue; assert.ok(css.includes(`.${name}`), `styles.css has no rule for .${name}`); }});
test("the unshown names are hidden but still hold the box open", () => { const rule = css.slice(css.indexOf(".hero-cycle .cycle-word {")); const body = rule.slice(0, rule.indexOf("}")); assert.match( body, /visibility: hidden/, "the unshown names are not hidden at all", ); assert.doesNotMatch( body, /display: none/, "display: none would stop the unshown names sizing the box, and the " + "paragraph would re-wrap every time a longer name came round", );});
test("the name rolling away leaves the page, not just the eye", () => { const frames = css.slice(css.indexOf("@keyframes cycle-roll-out")); const to = frames.slice( frames.indexOf("to {"), frames.indexOf("}", frames.indexOf("to {")), ); assert.match( to, /visibility: hidden/, "cycle-roll-out ends at opacity 0 alone, so the departed name is still " + "read out and the sentence ends in two hosts", );});