import { test } from "node:test"; import assert from "node:assert/strict"; import { execFileSync, spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { ACCENTS, ICON_PALETTES, MIN_MARK_DIM_CONTRAST, MIN_MARK_LIT_OVER_DIM, contrastRatio, markSvg } from "./brand"; import { MEDIA_BANNER, MEDIA_FIELD_LINE, MEDIA_MARK_PALETTE, MEDIA_PALETTE, MEDIA_RATIO, PyRandom, mediaBannerField, mediaBannerLayout, mediaBannerSvg, mediaLockupGeometry, mediaLockupSvg, mediaMarkSvg, mediaWordAdvanceEm, mediaWordmarkAdvanceEm, } from "./brandMedia"; import { MEDIA_CAP_HEIGHT, MEDIA_GLYPH_SOURCE, MEDIA_RUNS } from "./brandMediaGlyphs"; const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); test("palette: mark M2 is the parent mark's slate, lit in Signal", () => { assert.deepEqual(MEDIA_MARK_PALETTE, { ground: ICON_PALETTES.archilyzer.ground, dim: ICON_PALETTES.archilyzer.dim, lit: ACCENTS.signal.onDark, }); assert.deepEqual(MEDIA_PALETTE, { ground: "#151b20", dim: "#586977", lit: "#5fa8a0", fg: "#e7edf1", muted: "#8496a2", media: "#5fa8a0", amber: "#e3b15c", }); assert.equal(MEDIA_RATIO, 1.035); // The mark is markSvg's, not a second drawing. assert.equal(mediaMarkSvg("maskable"), markSvg(MEDIA_MARK_PALETTE, { variant: "maskable" })); assert.equal(mediaMarkSvg(), markSvg(MEDIA_MARK_PALETTE)); }); // brand.ts's two mark rules, on the one palette brand.test.ts does not hold: // Signal on the slate is the tightest lit-over-dim pair in the family. test("palette: mark M2's dim reaches 3:1 on the slate and Signal stands 2:1 above it", () => { const { ground, dim, lit } = MEDIA_MARK_PALETTE; assert.ok(contrastRatio(dim, ground) >= MIN_MARK_DIM_CONTRAST); assert.ok(contrastRatio(lit, dim) >= MIN_MARK_LIT_OVER_DIM); assert.equal(contrastRatio(lit, dim).toFixed(2), "2.05"); // The banner's field stays texture: above the ground, under the mark's dim. assert.ok(contrastRatio(MEDIA_FIELD_LINE, ground) < contrastRatio(dim, ground)); }); test("glyphs: the measured widths of plans/brand-and-themes.md S4 (5.334 em, 3.953 em, caps 0.686 em)", () => { assert.equal(MEDIA_GLYPH_SOURCE.wdth, 118); assert.deepEqual( [MEDIA_RUNS.archi, MEDIA_RUNS.lyzer, MEDIA_RUNS.media].map((r) => [r.text, r.wght]), [["Archi", 720], ["lyzer", 380], ["MEDIA", 700]], ); assert.equal(Number(mediaWordmarkAdvanceEm().toFixed(3)), 5.334); assert.equal(Number(mediaWordAdvanceEm().toFixed(3)), 3.953); assert.equal(MEDIA_CAP_HEIGHT, 686); }); test("lockup: MEDIA spans the wordmark's advance (within 0.5 %) with 0.30 em between its letters", () => { for (const px of [18, 28, 64, 124]) { const g = mediaLockupGeometry(px); assert.ok( Math.abs(g.mediaAdvance - g.wordmarkAdvance) / g.wordmarkAdvance < 0.005, `px ${px}: MEDIA ${g.mediaAdvance} vs wordmark ${g.wordmarkAdvance}`, ); assert.ok(Math.abs(g.mediaAdvance - g.textWidth) < 1e-6); assert.ok(Math.abs(g.mediaSpacing / g.mediaPx - 0.3) < 0.005, `spacing ${g.mediaSpacing / g.mediaPx} em`); assert.ok(Math.abs(g.mediaPx - px * MEDIA_RATIO) < 1e-9); // The mark is as tall as the block, which is about 1.70 x the wordmark. assert.equal(g.markSize, g.blockHeight); assert.ok(Math.abs(g.blockHeight / px - 1.7) < 0.01, `block ${g.blockHeight / px} x`); // The ink of both lines starts at the text origin and ends within a hair of // the shared advance: flush left and right. const lines = [g.glyphs.filter((p) => p.tone !== "media"), g.glyphs.filter((p) => p.tone === "media")]; for (const line of lines) { const first = line[0]; const last = line[line.length - 1]; assert.ok(first.glyph.left * first.scale < 0.1 * px, "left bearing"); const right = last.x + last.glyph.right * last.scale; assert.ok(Math.abs(g.textWidth - right) < 0.08 * px, `right edge ${right} vs ${g.textWidth}`); } } }); test("lockup on the banner: the approved numbers, inside the safe area", () => { const { geometry: g, block, ink } = mediaBannerLayout(); assert.equal(MEDIA_BANNER.wordmarkPx, 124); assert.equal(Math.round(g.mediaPx), 128); assert.equal(Math.round(g.textWidth), 661); assert.equal(Math.round(g.markSize), 210); const S = MEDIA_BANNER.safe; for (const box of [block, ink]) { assert.ok(box.x >= S.x && box.y >= S.y, `${JSON.stringify(box)} starts inside the safe area`); assert.ok(box.x + box.width <= S.x + S.width, "ends inside horizontally"); assert.ok(box.y + box.height <= S.y + S.height, "ends inside vertically"); } // Centred on it. assert.ok(Math.abs(block.x + block.width / 2 - (S.x + S.width / 2)) < 1e-6); assert.ok(Math.abs(block.y + block.height / 2 - (S.y + S.height / 2)) < 1e-6); // And the field keeps out of the gutter round it. for (const [x, y, w] of mediaBannerField()) { const inBandY = y + MEDIA_BANNER.line.height > S.y - MEDIA_BANNER.gutter.y && y < S.y + S.height + MEDIA_BANNER.gutter.y; if (!inBandY) continue; const clear = x + w <= S.x - MEDIA_BANNER.gutter.x || x >= S.x + S.width + MEDIA_BANNER.gutter.x; assert.ok(clear, `line ${x},${y} +${w} crosses the gutter`); } }); test("PyRandom is CPython's random.Random: the canvas field, line for line", () => { // python3 -c "import random; r = random.Random(11); print([r.getrandbits(32) for _ in range(5)])" const r = new PyRandom(11); assert.deepEqual( Array.from({ length: 5 }, () => r.genrandUint32()), [1942955373, 3718334796, 2404204071, 3680198571, 3969454221], ); // r = random.Random(11); [r.choice([0, 0, 0, 96, 192]) for _ in range(8)] const c = new PyRandom(11); assert.deepEqual( Array.from({ length: 8 }, () => c.choice([0, 0, 0, 96, 192])), [96, 192, 96, 96, 192, 192, 0, 0], ); // The canvas generator's banner() field (gen_media.py), 87 lines. const field = mediaBannerField(); assert.equal(field.length, 87); assert.deepEqual(field.slice(0, 3), [[160, 40, 300], [488, 40, 380], [896, 40, 380]]); assert.deepEqual(field.slice(-3), [[1658, 1384, 190], [1876, 1384, 300], [2204, 1384, 172]]); }); test("SVGs: well-formed roots, colours validated, the safe crop is a viewBox", () => { const lockup = mediaLockupSvg({ px: 28 }); assert.match(lockup, /^/); assert.ok(lockup.includes(`fill="${MEDIA_PALETTE.fg}"`) && lockup.includes(`fill="${MEDIA_PALETTE.muted}"`)); assert.equal((lockup.match(/ mediaLockupSvg({ px: 28, palette: { ...MEDIA_PALETTE, fg: "red" } }), /palette\.fg/); assert.throws(() => mediaLockupGeometry(0), /px must be > 0/); const banner = mediaBannerSvg(); assert.match(banner, /^]*width="2560" height="1440" viewBox="0 0 2560 1440">/); assert.equal((banner.match(//g) ?? []).length, 87); const safe = mediaBannerSvg({ view: MEDIA_BANNER.safe }); assert.match(safe, /^]*width="1546" height="423" viewBox="507 508.5 1546 423">/); assert.equal(safe.slice(safe.indexOf(">")), banner.slice(banner.indexOf(">"))); }); // The glyph file is generated; a hand edit or a font swap without a re-run is a // failure. Needs python3 + fontTools, which a test machine may not have -- and // the SAME fontTools the file records: another version can round the // instancer's outlines differently, which is not a stale file. Both skips say // why in the test output. test("brandMediaGlyphs.ts is exactly what gen-media-glyphs.py writes", (t) => { const probe = spawnSync( "python3", ["-c", "import fontTools, fontTools.varLib.instancer; print(fontTools.version)"], { encoding: "utf8" }, ); if (probe.status !== 0) { t.skip("python3 with fontTools is not available"); return; } const installed = probe.stdout.trim(); if (installed !== MEDIA_GLYPH_SOURCE.fontTools) { t.skip(`fontTools ${installed} is installed; brandMediaGlyphs.ts was generated with ${MEDIA_GLYPH_SOURCE.fontTools}`); return; } const fresh = execFileSync("python3", [path.join(REPO, "common/bin/gen-media-glyphs.py"), "--stdout"], { encoding: "utf8", maxBuffer: 1 << 24, }); const committed = readFileSync(path.join(REPO, "common/lib/brandMediaGlyphs.ts"), "utf8"); assert.equal(committed, fresh); });