// Tests for attribution.mjs — the one definition of the burned-in header line. // // Three programs draw or promise this string (the renderer, the clip bench's // preview, the manifest writer's validation), so it is worth pinning to the // character. // // Run with: pnpm test:scripts import assert from "node:assert/strict"; import test from "node:test"; import { attributionLine, channelName, cleanTitle, hms, imageAttributionLine, isCalendarDate, uploadDateToIso, } from "./attribution.mjs"; test("hms: hours only when there are any", () => { assert.equal(hms(0), "0:00"); assert.equal(hms(61), "1:01"); assert.equal(hms(3599), "59:59"); assert.equal(hms(3600), "1:00:00"); assert.equal(hms(32989.9), "9:09:49"); }); test("attributionLine: a clip carries its channel and a clock", () => { assert.equal( attributionLine( { title: "Chillin'", date: "2022-08-31", cite: 7088 }, { channel: "OmniBased" }, ), "OmniBased · Chillin' · 2022-08-31 @ 1:58:08", ); assert.equal( attributionLine( { start: 10 }, { title: "Stream 🙂 !socials", uploadDate: "20250101", channel: "Destiny" }, ), "Destiny · Stream · 2025-01-01 @ 0:10", ); }); test("attributionLine: channelTitle overrides the record", () => { assert.equal( attributionLine( { channelTitle: "Destiny", channel: "omnivods3", title: "Meet the Snarkers", date: "2026-03-13", cite: 43 }, { channel: "OMNI VODS 3" }, ), "Destiny · Meet the Snarkers · 2026-03-13 @ 0:43", ); }); test("attributionLine: with nothing naming the channel, the slug is on screen", () => { // Visibly a thing to fix. A silently empty head would just look like a design. assert.equal( attributionLine( { channel: "omnivods3", title: "Chillin'", date: "2022-08-31", cite: 0 }, {}, { channelSlug: "omnibased", channel: "OmniBased" }, ), "omnivods3 · Chillin' · 2022-08-31 @ 0:00", ); }); test("channelName: the four answers, in order", () => { const prov = { channelSlug: "omnibased", channel: "OmniBased" }; // 1. the per-clip override assert.equal(channelName({ channelTitle: "Destiny", channel: "x" }, { channel: "Y" }, prov), "Destiny"); // 2. the cue record's own uploader name assert.equal(channelName({ channel: "destiny" }, { channel: "Destiny" }, prov), "Destiny"); // 3. the sweep's display name, for the sweep's own channel assert.equal(channelName({ channel: "omnibased" }, {}, prov), "OmniBased"); assert.equal(channelName({}, {}, prov), "OmniBased"); // 4. the raw slug assert.equal(channelName({ channel: "destiny" }, {}, prov), "destiny"); assert.equal(channelName({}, {}, {}), ""); }); test("attributionLine: a cut that can name no channel reads as it always did", () => { assert.equal( attributionLine({ title: "Monday Mail #11", date: "2016-09-12", cite: 32989 }, {}), "Monday Mail #11 · 2016-09-12 @ 9:09:49", ); }); test("imageAttributionLine: title and date", () => { assert.equal( imageAttributionLine({ title: "Bx (@bx_on_x) on X, September 2026 — 1/4", date: "2026-08-19", }), "Bx (@bx_on_x) on X, September 2026 — 1/4 · 2026-08-19", ); }); test("imageAttributionLine: no date, no separator", () => { assert.equal( imageAttributionLine({ title: "1st pic — Substack profile" }), "1st pic — Substack profile", ); // A still has no moment to cite, so there is never a clock on it. assert.doesNotMatch(imageAttributionLine({ title: "x", date: "2026-08-19" }), /@/); }); test("imageAttributionLine: a date alone still reads", () => { assert.equal(imageAttributionLine({ date: "2026-08-19" }), "2026-08-19"); }); test("imageAttributionLine: nothing to say is the empty string", () => { // The renderer reads "" as "draw no header": an empty textfile is the one // thing drawtext refuses outright. assert.equal(imageAttributionLine({}), ""); assert.equal(imageAttributionLine({ title: " ", date: null }), ""); assert.equal(imageAttributionLine(undefined), ""); }); test("imageAttributionLine: the author's title is not cleaned", () => { // An @handle is the caption's subject here, not a chat !command to strip. const line = imageAttributionLine({ title: "Bx (@bx_on_x) on X" }); assert.equal(line, "Bx (@bx_on_x) on X"); assert.notEqual(line, cleanTitle("Bx (@bx_on_x) on X")); }); test("isCalendarDate / uploadDateToIso", () => { assert.equal(isCalendarDate("2026-08-19"), true); assert.equal(isCalendarDate("2025-02-31"), false); assert.equal(isCalendarDate("20250101"), false); assert.equal(uploadDateToIso("20250101"), "2025-01-01"); assert.equal(uploadDateToIso("2025-01-01"), "2025-01-01"); });