// Tests for the opt-in brand preset (brand.mjs) and the hooks it has in the // build: the config it resolves, and -- the hard rule -- that a manifest // WITHOUT `render.brand` comes through every hook exactly as it went in. // // The pixels themselves are proven by rendering (a guarded test below, and the // before/after card diff recorded in plans/brand-and-themes.md). // // Run with: pnpm test:scripts import assert from "node:assert/strict"; import test from "node:test"; import { execFileSync, spawnSync } from "node:child_process"; import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { mkdir } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { BRAND_IDS, END_CARD_DEFAULT_SECONDS, END_CARD_DEFAULT_URL, END_CARD_ID, FACES, FONTCONFIG_FILE, FONTS_DIR, MONO_BOLD_FONT_FILE, MONO_FONT_FILE, brandFaces, brandHeaderGeometry, brandKit, brandManifest, brandSvgFace, childOpts, endCardConfig, resolveBrandRender, } from "./brand.mjs"; import { headerFilters, selectVariant } from "./build-video.mjs"; import { renderCard, renderScrollCard } from "./render-cards.mjs"; import { markPng, renderThumbnail, THUMB } from "./brand-cards.mjs"; import { IBM_PLEX_SANS } from "./svg-faces.mjs"; const mkdirP = (d) => mkdir(d, { recursive: true }); const HERE = path.dirname(fileURLToPath(import.meta.url)); const PLAIN_RENDER = { width: 1920, height: 1080, fps: 30, headerHeight: 56, fontRegular: "/usr/share/fonts/TTF/FiraSans-Regular.ttf", palette: { bg: "#12100c", fg: "#f6f1e6", muted: "#a2957f", accent: "#c8752a", amber: "#ffc860" }, }; const manifest = (render, timeline = [{ type: "clip", id: "c01" }]) => ({ slug: "t", render, timeline, provenance: {}, }); test("no brand: every hook hands back the very object it was given", () => { assert.equal(resolveBrandRender(PLAIN_RENDER), PLAIN_RENDER); const m = manifest(PLAIN_RENDER); assert.equal(brandManifest(m), m); assert.equal(brandFaces(PLAIN_RENDER), null); assert.equal(brandSvgFace(PLAIN_RENDER), null); const opts = { maxBuffer: 1 }; assert.equal(childOpts(PLAIN_RENDER, opts), opts); assert.equal(childOpts(PLAIN_RENDER, undefined), undefined); }); test("no brand: selectVariant's result is the pre-preset shape, render untouched, no end card", () => { const m = manifest(PLAIN_RENDER, [ { type: "card", id: "t00", style: "title" }, { type: "clip", id: "c01" }, { type: "card", id: "L1", variant: "full" }, ]); const v = selectVariant(m, "sourced"); assert.equal(v.render, PLAIN_RENDER); assert.deepEqual(v, { ...m, variant: "sourced", timeline: m.timeline.slice(0, 2), ledger: [] }); }); test("no brand: the header is the accent tick and one drawtext line, as it always was", () => { assert.deepEqual(headerFilters(PLAIN_RENDER, "/o/c01.attrib.txt", "/o/c01.channel.txt"), [ "drawbox=x=90:y=16:w=4:h=24:color=#c8752a:t=fill", "drawtext=textfile='/o/c01.attrib.txt':fontfile='/usr/share/fonts/TTF/FiraSans-Regular.ttf'" + ":fontsize=22:fontcolor=#a2957f:x=118:y=15", ]); }); test("the preset owns palette and face, and resolves the end card", () => { const r = resolveBrandRender({ ...PLAIN_RENDER, brand: "archilyzer-media" }); assert.deepEqual(r.palette, { bg: "#151b20", fg: "#e7edf1", muted: "#8496a2", accent: "#5fa8a0", amber: "#e3b15c", dim: "#586977", }); assert.equal(r.fontRegular, MONO_FONT_FILE); // The HyperFrames band's bold: the vendored Plex Mono Bold, whatever the manifest said. assert.equal(r.fontBold, MONO_BOLD_FONT_FILE); assert.equal( resolveBrandRender({ ...PLAIN_RENDER, fontBold: "/usr/share/fonts/TTF/FiraSans-Bold.ttf", brand: "archilyzer-media" }).fontBold, MONO_BOLD_FONT_FILE, ); // The rail / ledger / scroll / chart text: Plex Sans, measured per character. assert.equal(brandSvgFace(r), IBM_PLEX_SANS); assert.deepEqual(r.endCard, { seconds: END_CARD_DEFAULT_SECONDS, url: END_CARD_DEFAULT_URL }); // Everything else is still the manifest's. assert.equal(r.headerHeight, 56); assert.equal(r.fps, 30); assert.throws(() => resolveBrandRender({ ...PLAIN_RENDER, brand: "acme" }), /not a preset/); assert.deepEqual(BRAND_IDS, ["archilyzer-media"]); }); test("endCardConfig: default 20 s, a number, an object, or false", () => { assert.deepEqual(endCardConfig(undefined), { seconds: 20, url: "archilyzer.pages.dev" }); assert.deepEqual(endCardConfig(12), { seconds: 12, url: "archilyzer.pages.dev" }); assert.deepEqual(endCardConfig({ seconds: 8, url: "example.org" }), { seconds: 8, url: "example.org" }); assert.equal(endCardConfig(false), null); assert.throws(() => endCardConfig({ seconds: 0 }), /positive/); }); test("endCard false stays off when a render block is resolved twice", () => { assert.equal(endCardConfig(null), null); const branded = { ...PLAIN_RENDER, brand: "archilyzer-media" }; const once = resolveBrandRender({ ...branded, endCard: false }); assert.equal(once.endCard, null); const twice = resolveBrandRender(once); assert.equal(twice.endCard, null); assert.equal(brandManifest(manifest(twice)).timeline.length, 1); // A configured card resolves to itself. const on = resolveBrandRender({ ...branded, endCard: { seconds: 12 } }); assert.deepEqual(resolveBrandRender(on).endCard, on.endCard); }); test("brandManifest appends one end card, clear of the rail, unless told otherwise", () => { const branded = { ...PLAIN_RENDER, brand: "archilyzer-media" }; const m = brandManifest(manifest(branded)); assert.deepEqual(m.timeline.at(-1), { type: "card", id: END_CARD_ID, style: "end", seconds: 20, hideRail: true, chapter: "End", }); assert.equal(m.timeline.length, 2); // Configured length. assert.equal(brandManifest(manifest({ ...branded, endCard: { seconds: 15 } })).timeline.at(-1).seconds, 15); // Off. assert.equal(brandManifest(manifest({ ...branded, endCard: false })).timeline.length, 1); // The author's own end card is not doubled. const own = [{ type: "clip", id: "c01" }, { type: "card", id: "bye", style: "end", seconds: 10 }]; assert.deepEqual(brandManifest(manifest(branded, own)).timeline, own); // An unrelated entry called "end" is a collision, said rather than overwritten. assert.throws(() => brandManifest(manifest(branded, [{ type: "clip", id: "end" }])), /already has an entry/); // Through selectVariant, which every reader of a cut uses. assert.equal(selectVariant(manifest(branded), "full").timeline.at(-1).style, "end"); }); test("branded header: mono face, no tick, the channel drawn again in the foreground", () => { const r = resolveBrandRender({ ...PLAIN_RENDER, brand: "archilyzer-media" }); const g = brandHeaderGeometry(r); assert.deepEqual(g, { mark: 34, markX: 90, markY: 11, textX: 138, fontSize: 20, textY: 20 }); const f = headerFilters(r, "/o/a.txt", "/o/c.txt"); assert.equal(f.length, 2); assert.ok(f.every((s) => s.startsWith("drawtext="))); assert.match(f[0], /textfile='\/o\/a\.txt'.*fontcolor=#8496a2:x=138:y=20$/); assert.match(f[1], /textfile='\/o\/c\.txt'.*fontcolor=#e7edf1:x=138:y=20$/); assert.ok(f[0].includes(`fontfile='${MONO_FONT_FILE}'`)); assert.equal(headerFilters(r, "/o/a.txt").length, 1); }); test("the vendored faces: every file the preset names is there, under the OFL", () => { for (const f of [ "Archivo[wdth,wght].ttf", "IBMPlexSans[wdth,wght].ttf", "IBMPlexMono-Regular.ttf", "IBMPlexMono-Bold.ttf", "OFL.txt", "fonts.conf", ]) { assert.ok(existsSync(path.join(FONTS_DIR, f)), f); } assert.ok(existsSync(MONO_BOLD_FONT_FILE)); assert.equal(FONTCONFIG_FILE, path.join(FONTS_DIR, "fonts.conf")); const ofl = readFileSync(path.join(FONTS_DIR, "OFL.txt"), "utf8"); assert.match(ofl, /The Archivo Project Authors/); assert.match(ofl, /IBM Corp\. with Reserved Font Name "Plex"/); assert.match(readFileSync(FONTCONFIG_FILE, "utf8"), /\.<\/dir>/); const childEnv = childOpts({ brand: "archilyzer-media" }, { maxBuffer: 1 }).env; assert.equal(childEnv.FONTCONFIG_FILE, FONTCONFIG_FILE); // The faces the cards ask Pango for are the vendored families. assert.match(FACES["archilyzer-media"].display, /^Archivo @wght=720,wdth=118$/); }); test("the kit: the lockup and the mark are outlines, not text", () => { const kit = brandKit("archilyzer-media"); assert.doesNotMatch(kit.lockup.svg, //); assert.ok(kit.lockup.blockTop > 0 && kit.lockup.blockHeight > 0); }); // fontconfig, pointed at fonts.conf, finds the vendored faces -- the bold mono // included -- and without it the system's fonts are what it has. const hasFcMatch = spawnSync("fc-match", ["--version"], { stdio: "ignore" }).status === 0; test("fonts.conf: the preset's children resolve the vendored faces, bold mono included", { skip: !hasFcMatch && "fc-match not available" }, () => { // fc-match escapes its separators in a path (`\,`), so they are unescaped. const match = (pattern) => execFileSync("fc-match", [pattern, "file"], { encoding: "utf8", env: { ...process.env, FONTCONFIG_FILE } }) .trim() .replace(/\\(.)/g, "$1"); assert.equal(match("IBM Plex Mono:bold"), `:file=${MONO_BOLD_FONT_FILE}`); assert.equal(match("IBM Plex Mono"), `:file=${MONO_FONT_FILE}`); assert.equal(match("IBM Plex Sans:bold"), `:file=${path.join(FONTS_DIR, "IBMPlexSans[wdth,wght].ttf")}`); }); // Rendering needs ImageMagick with Pango and rsvg-convert; a machine without // them skips rather than fails. const hasTools = spawnSync("magick", ["-version"], { stdio: "ignore" }).status === 0 && spawnSync(process.env.RSVG_BIN ?? "rsvg-convert", ["--version"], { stdio: "ignore" }).status === 0; test("rendered: title and end cards at the frame size, the thumbnail at 1280 x 720", { skip: !hasTools && "magick / rsvg-convert not available" }, async () => { const dir = mkdtempSync(path.join(tmpdir(), "rtv-brand-")); try { const render = resolveBrandRender({ ...PLAIN_RENDER, brand: "archilyzer-media" }); const size = (f) => execFileSync("magick", ["identify", "-format", "%w %h", f], { encoding: "utf8" }); const title = await renderCard( { type: "card", id: "t00", style: "title", heading: "A title", foot: "Channel · 2025" }, render, dir, [], ); assert.equal(size(title), "1920 1080"); const end = await renderCard({ type: "card", id: "end", style: "end", seconds: 20 }, render, dir, []); assert.equal(size(end), "1920 1080"); // The right half of the end card is bare ground: YouTube's end screen. const right = execFileSync( "magick", [end, "-crop", "960x1080+960+0", "+repage", "-format", "%k", "info:"], { encoding: "utf8" }, ); assert.equal(right, "1"); const still = path.join(dir, "still.png"); execFileSync("magick", ["-size", "640x360", "xc:#446688", still]); const { out } = await renderThumbnail({ render, still, headline: "The county said yes", out: path.join(dir, "thumb.png"), workDir: dir, }); assert.equal(size(out), `${THUMB.width} ${THUMB.height}`); // The duration badge's corner is clear: one colour, the ground. const corner = execFileSync( "magick", [out, "-crop", "110x50+1170+670", "+repage", "-format", "%k", "info:"], { encoding: "utf8" }, ); assert.equal(corner, "1"); // The header mark is cached per DRAWING, not per size: its name carries // the kit SVG's hash, so a regenerated kit cannot be answered by a stale PNG. const mark = await markPng(render, 34, dir); assert.match(path.basename(mark), /^_mark-34-[0-9a-f]{12}\.png$/); assert.equal(size(mark), "34 34"); assert.equal(await markPng(render, 34, dir), mark); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("rendered: the scroll's SVG text takes the preset's face and its fit; unbranded stays Fira", { skip: !hasTools && "magick / rsvg-convert not available" }, async () => { const dir = mkdtempSync(path.join(tmpdir(), "rtv-brand-svg-")); try { const rail = { width: 500, tracks: [{ key: "q", label: "The Quartering · media", shortLabel: "THE QUARTERING · MEDIA", color: "#2bb38a" }], }; const ledger = [{ id: "a", date: "2023-06-30", scope: "q", value: 6, display: "6", label: "six employees now", entryId: "c01" }]; const card = { type: "scroll", id: "z01", hideRail: true }; const svgOf = async (render, sub) => { await mkdirP(path.join(dir, sub, "cards")); const { path: png, width } = await renderScrollCard(card, render, ledger, path.join(dir, sub)); assert.equal(width, 1920); assert.ok(existsSync(png)); return readFileSync(path.join(dir, sub, "cards", "z01.svg"), "utf8"); }; const plain = await svgOf({ ...PLAIN_RENDER, rail }, "plain"); assert.ok(plain.includes('font-family="Fira Sans"')); assert.ok(!plain.includes("IBM Plex")); // Fira's count keeps 21 characters of the head. assert.ok(plain.includes(">THE QUARTERING · MED…<")); const branded = await svgOf(resolveBrandRender({ ...PLAIN_RENDER, rail, brand: "archilyzer-media" }), "brand"); assert.ok(branded.includes('font-family="IBM Plex Sans"')); assert.ok(!branded.includes("Fira Sans")); // Plex Sans Bold, letter-spaced, measured into the 140 px head. assert.ok(branded.includes(">THE QUARTERING…<")); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("rendered: the preset's rsvg-convert children get the vendored fonts; unbranded ones the system's", { skip: !hasTools && "magick / rsvg-convert not available" }, () => { // IBM Plex Sans is not a system font, so a branded SVG rasterized without // FONTCONFIG_FILE would fall back to some other sans -- silently. A stub // RSVG_BIN records the variable each child is given, then runs the real one. const dir = mkdtempSync(path.join(tmpdir(), "rtv-brand-env-")); try { const log = path.join(dir, "env.log"); const stub = path.join(dir, "rsvg-stub.sh"); const real = process.env.RSVG_BIN ?? "rsvg-convert"; writeFileSync(stub, `#!/bin/sh\nprintf '%s\\n' "\${FONTCONFIG_FILE:-system}" >> '${log}'\nexec ${real} "$@"\n`, { mode: 0o755 }); const url = (f) => JSON.stringify(pathToFileURL(path.join(HERE, f)).href); const script = ` import { mkdirSync } from "node:fs"; import { renderScrollCard } from ${url("render-cards.mjs")}; import { resolveBrandRender } from ${url("brand.mjs")}; const render = ${JSON.stringify({ ...PLAIN_RENDER, rail: { tracks: [{ key: "q", label: "Q", color: "#2bb38a" }] } })}; const ledger = [{ id: "a", date: "2023-06-30", scope: "q", value: 6, display: "6", label: "six", entryId: "c" }]; for (const [sub, r] of [["plain", render], ["brand", resolveBrandRender({ ...render, brand: "archilyzer-media" })]]) { mkdirSync(${JSON.stringify(dir)} + "/" + sub + "/cards", { recursive: true }); await renderScrollCard({ type: "scroll", id: "z", hideRail: true }, r, ledger, ${JSON.stringify(dir)} + "/" + sub); } `; const env = { ...process.env, RSVG_BIN: stub }; delete env.FONTCONFIG_FILE; const r = spawnSync(process.execPath, ["--input-type=module", "-e", script], { env, encoding: "utf8" }); assert.equal(r.status, 0, r.stderr); assert.deepEqual(readFileSync(log, "utf8").trim().split("\n"), ["system", FONTCONFIG_FILE]); } finally { rmSync(dir, { recursive: true, force: true }); } });