Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
4.0 kB · 104 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105/** * The theme setting is one string that four files have to agree about, and * three of them cannot import the one that defines it. * * theme.ts writes "light" or "dark" under a key in localStorage. Base.astro's * inline script reads that key before the first paint — inline, so it cannot * import anything, so the key is spelled out twice. styles.css is what turns * the resulting attribute into colours. Change any one of the three and * nothing errors: the toggle keeps working for the rest of the session and * has simply stopped surviving a reload, or stopped changing anything at all. * * 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 theme = await read("src/theme.ts");const layout = await read("src/layouts/Base.astro");const css = await read("src/styles.css");const account = await read("src/account.ts");
const key = theme.match(/STORE_KEY = "([^"]+)"/)?.[1];assert.ok(key, "theme.ts no longer declares STORE_KEY as a literal");
test("the inline script reads the key theme.ts writes", () => { const inline = layout.slice(layout.indexOf("<script is:inline>")); assert.ok( inline.includes(`localStorage.getItem("${key}")`), `Base.astro does not read ${key}; the choice would not survive a reload`, );});
test("both settings the inline script accepts are settings theme.ts writes", () => { const written = [...theme.matchAll(/theme: Theme; label: string/g)]; assert.equal(written.length, 1, "the choice list moved");
for (const value of ["light", "dark"]) { assert.ok( theme.includes(`theme: "${value}"`), `theme.ts offers no ${value}`, ); assert.ok( layout.includes(`saved === "${value}"`), `Base.astro drops ${value} before the first paint`, ); assert.match( css, new RegExp(`:root\\[data-theme="${value}"\\]`), `styles.css has no rule for data-theme="${value}"`, ); }});
test("System is the absence of the attribute, not a third value", () => { // theme.ts removes the attribute and the key for System. A CSS rule naming // it would mean someone had made it a value, and the two would disagree. assert.doesNotMatch(css, /\[data-theme="system"\]/); assert.ok( theme.includes('root.removeAttribute("data-theme")'), "System has to clear the attribute or the browser never decides again", );});
test("the layout leaves the box theme.ts fills", () => { const slot = theme.match(/const SLOT = "#([^"]+)"/)?.[1]; assert.ok(slot, "theme.ts no longer declares SLOT as a literal"); assert.ok( layout.includes(`id="${slot}"`), `Base.astro has no #${slot} for the control to mount into`, ); assert.match( layout, /import \{ mountTheme \}[\s\S]*mountTheme\(\)/, "the layout does not mount the theme control", );});
test("the control does not belong to the account menu", () => { // It used to, which meant the only people who could change the colour // scheme were the ones who had signed in. Nothing errors if it moves back: // the control keeps working for whoever built it, and quietly stops // existing for everyone else — including every reader of the blog. assert.doesNotMatch( account, /from "\.\/theme"/, "the theme control is behind the session again", );});
test("no colour is chosen by a media query the toggle cannot override", () => { // A media query sees the browser's preference and not the player's choice, // so any token still set from one keeps its value on a page the player // asked to be the other scheme. light-dark() is what follows color-scheme. assert.doesNotMatch( css, /prefers-color-scheme/, "use light-dark() so an explicit theme wins", );});