/** * The daily card's ground belongs to the right of its text, and the card has * to leave room for it there. * * `.daily-text` holds the readable column and the terrain tile side by side, * and the tile is a fixed 8rem. The readable column got whatever the card had * left - about 200px on the front page, narrower than the launch control, * which does not wrap - so the blurb and the battle values ran on under the * tile and were read through it. * * A media query was meant to catch that, but it asked about the window: the * window was 1440px wide while the column that had run out was 344px. The fix * is not to move the tile. It is for the card's own split to have a floor * under the side that holds both of them, and for the board - the one thing * here that scales - to give up the difference. * * The rule this file keeps: that floor is at least the readable column, the * gap and the tile added up. The arithmetic is here rather than a remembered * number, because it moves the moment anyone touches the tile's width, the * gap, or what the launch control needs. * * Run with `npm test`. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; const src = fileURLToPath(new URL("../src/", import.meta.url)); const css = await readFile(`${src}styles.css`, "utf8"); const ROOT_FONT = 16; /** * What PLAY and Download need side by side, measured in a browser. Neither * half wraps, so this is a floor rather than a preference: below it the * control overflows its column, which is the bug this file is about. Node * cannot measure text, so a change to either half's type or padding means * re-measuring this. */ const LAUNCH_CONTROL = 229; /** The body of a rule, by its selector. */ function rule(selector) { const start = css.indexOf(`\n${selector} {`); assert.notEqual(start, -1, `${selector} moved or was renamed`); const end = css.indexOf("\n}", start); return css.slice(start, end); } function declaration(body, property, what) { const found = new RegExp(`\\n\\s*${property}:\\s*([^;]+);`).exec(body); assert.ok(found, `${what} moved or was renamed`); return found[1].trim(); } /** A CSS length in px. Only px and rem appear in these rules. */ function length(raw, what) { const found = /^([\d.]+)(px|rem)$/.exec(raw.trim()); assert.ok(found, `${what} is ${raw}, which this test cannot measure`); return Number(found[1]) * (found[2] === "rem" ? ROOT_FONT : 1); } /** The readable column, the gap and the terrain tile, added up. */ function needed() { const main = rule(".daily-text-main"); const basis = declaration(main, "flex", ".daily-text-main flex").split(/\s+/); assert.equal( basis.length, 3, ".daily-text-main flex is not grow/shrink/basis", ); const column = length(basis[2], ".daily-text-main flex-basis"); const gap = length( declaration(rule(".daily-text"), "gap", ".daily-text gap"), ".daily-text gap", ); const tile = length( declaration( rule(".daily-terrain-tile"), "width", ".daily-terrain-tile width", ), ".daily-terrain-tile width", ); return { column, gap, tile, total: column + gap + tile }; } test("the readable column can hold the launch control", () => { const { column } = needed(); assert.ok( column >= LAUNCH_CONTROL, `the readable column may be as narrow as ${column}px, and PLAY beside ` + `Download needs about ${LAUNCH_CONTROL}px - the text will run on under ` + `the terrain tile`, ); }); test("the card leaves room for the ground beside the text", () => { const { column, gap, tile, total } = needed(); const columns = declaration( rule(".daily"), "grid-template-columns", ".daily grid-template-columns", ); const found = /minmax\((\d+(?:\.\d+)?(?:px|rem)),\s*1fr\)\s*$/.exec(columns); assert.ok( found, `.daily's text column is "${columns}" and has no floor under it: a share ` + `of the card is not enough, which is how the text came to be read ` + `through the terrain tile`, ); const floor = length(found[1], ".daily text column floor"); assert.ok( floor >= total, `.daily gives the text side a floor of ${floor}px, and it holds a ` + `${column}px column, a ${gap}px gap and a ${tile}px tile - ${total}px`, ); }); test("nothing readable can outgrow the column it is in", () => { const track = declaration( rule(".daily-text-main"), "grid-template-columns", ".daily-text-main grid-template-columns", ); assert.match( track, /^minmax\(0,/, "an auto track sizes to the launch control, which does not wrap, and the " + "column overflows whatever is beside it", ); }); test("the ground still has somewhere to go on a phone", () => { const row = rule(".daily-text"); assert.equal( declaration(row, "display", ".daily-text display"), "flex", ".daily-text is a grid again: its tracks cannot give way, so on a screen " + "too narrow for two columns the text will run on under the tile", ); assert.equal( declaration(row, "flex-wrap", ".daily-text flex-wrap"), "wrap", "without wrapping there is nowhere for the terrain tile to go", ); });