import { test } from "node:test"; import assert from "node:assert/strict"; import { HUB_SUMMARY_VERSION, hubSummarySiteFor, officialInstances, parseHubSummary, toHubSummary, type HubSummary, } from "./hubSummary"; import { seriesColor } from "./homepageChart"; import type { HomepageSummary } from "./homepageSummary"; import { resolveAccent } from "./accent"; import { perBaseColor } from "./siteColor"; // A custom hex as a card wears it: fitted to each base (lib/siteColor.ts). const fitted = (hex: string) => perBaseColor(resolveAccent(hex)); // Run with: // pnpm --filter yt-dlp-transcript-common test const stat = (total: number) => ({ total, thisMonth: 0, last12: [] }); function homepageSummary(): HomepageSummary { return { version: 5, generatedAt: "2026-09-25T00:00:00.000Z", totals: { transcripts: 99, downloads: 99, sites: 2, channels: 9, hoursArchived: 999, transcribedThisMonth: 0, downloadedThisMonth: 0, }, channels: [], series: {} as HomepageSummary["series"], sites: [ { siteId: "alpha", siteTitle: "Alpha", siteDescription: "The first archive.", siteUrl: "https://alpha.example", transcribed: stat(30), downloaded: stat(40), channels: 3, recordings: 40, hoursArchived: 12, gone: 1, accent: "#123456", }, { // A pre-v5 site: no per-site figures beyond the transcript count. siteId: "beta", siteTitle: "Beta", siteDescription: "", siteUrl: "https://beta.example", transcribed: stat(5), downloaded: stat(6), }, ], recent: [], official: { sites: 2, channels: 3, recordings: 40, transcripts: 35, hoursArchived: 12, gone: 1, }, }; } test("toHubSummary projects the homepage's official totals and card figures", () => { const hub = toHubSummary(homepageSummary()); assert.equal(hub.version, HUB_SUMMARY_VERSION); assert.equal(hub.generatedAt, "2026-09-25T00:00:00.000Z"); assert.deepEqual(hub.official, homepageSummary().official); assert.deepEqual(hub.sites, [ { siteId: "alpha", siteTitle: "Alpha", siteDescription: "The first archive.", siteUrl: "https://alpha.example", channels: 3, recordings: 40, transcripts: 30, hoursArchived: 12, gone: 1, accent: "#123456", }, { siteId: "beta", siteTitle: "Beta", siteUrl: "https://beta.example", transcripts: 5, }, ]); }); test("a summary without `official` projects to null, not zeros", () => { const s = homepageSummary(); delete s.official; assert.equal(toHubSummary(s).official, null); }); test("parseHubSummary round-trips what toHubSummary writes", () => { const hub = toHubSummary(homepageSummary()); assert.deepEqual(parseHubSummary(JSON.parse(JSON.stringify(hub))), hub); }); test("parseHubSummary degrades instead of throwing", () => { assert.equal(parseHubSummary(null), null); assert.equal(parseHubSummary("nope"), null); assert.equal(parseHubSummary([]), null); // A version this reader does not know may mean something else. assert.equal(parseHubSummary({ version: 99, sites: [] }), null); const parsed = parseHubSummary({ version: HUB_SUMMARY_VERSION, generatedAt: 7, official: { sites: 1 }, // incomplete -> null, never half a total sites: [ { siteId: "ok", siteTitle: "OK", siteUrl: "https://ok.example", channels: -1, recordings: "3", transcripts: 4 }, { siteId: "no-url", siteTitle: "No URL" }, "junk", ], }); assert.deepEqual(parsed, { version: HUB_SUMMARY_VERSION, generatedAt: "", official: null, sites: [ { siteId: "ok", siteTitle: "OK", siteUrl: "https://ok.example", transcripts: 4 }, ], }); }); test("hubSummarySiteFor matches by siteId, then by origin", () => { const hub = toHubSummary(homepageSummary()); assert.equal(hubSummarySiteFor(hub, { siteId: "alpha" })?.siteTitle, "Alpha"); assert.equal( hubSummarySiteFor(hub, { siteId: "renamed", origin: "https://beta.example" })?.siteId, "beta", ); assert.equal(hubSummarySiteFor(hub, { siteId: "gamma", origin: "https://gamma.example" }), null); assert.equal(hubSummarySiteFor(null, { siteId: "alpha" }), null); }); // The hub's official instances, three of them, as hub-sites.json lists them // (alphabetically), none with an accent of its own. function pool() { return [ { siteId: "anilyzer", origin: "https://anilyzer.example" }, { siteId: "hasanalyzer", origin: "https://hasanalyzer.example" }, { siteId: "jeralyzer", origin: "https://jeralyzer.example" }, ]; } // The homepage's order: transcripts, most first. function homepageOrder(): HubSummary { const site = (siteId: string, transcripts: number) => ({ siteId, siteTitle: siteId, siteUrl: `https://${siteId}.example`, transcripts, }); return { version: HUB_SUMMARY_VERSION, generatedAt: "2026-09-26T00:00:00.000Z", official: null, sites: [site("jeralyzer", 300), site("anilyzer", 200), site("hasanalyzer", 100)], }; } test("officialInstances lists the members in the summary's order, each in its homepage colour", () => { const out = officialInstances(pool(), homepageOrder()); assert.deepEqual( out.map((o) => [o.site.siteId, o.figures?.transcripts, o.accent]), [ ["jeralyzer", 300, seriesColor(0)], ["anilyzer", 200, seriesColor(1)], ["hasanalyzer", 100, seriesColor(2)], ], ); }); test("officialInstances: with no summary the given order stands, coloured by place", () => { const out = officialInstances(pool(), null); assert.deepEqual( out.map((o) => [o.site.siteId, o.figures, o.accent]), [ ["anilyzer", null, seriesColor(0)], ["hasanalyzer", null, seriesColor(1)], ["jeralyzer", null, seriesColor(2)], ], ); }); test("officialInstances: an accent the site sets wins; its place still follows the summary", () => { const members = pool().map((m) => m.siteId === "anilyzer" ? { ...m, accent: "#e6a1c0" } : m, ); const summary = homepageOrder(); summary.sites[2] = { ...summary.sites[2], accent: "#b49cf2" }; // hasanalyzer const out = officialInstances(members, summary); assert.deepEqual( out.map((o) => [o.site.siteId, o.accent]), [ ["jeralyzer", seriesColor(0)], ["anilyzer", fitted("#e6a1c0")], // hub-sites.json's own, fitted per base ["hasanalyzer", fitted("#b49cf2")], // the summary's, when hub-sites.json has none ], ); }); test("officialInstances: a member the summary does not name follows, in colours no named one wears", () => { const members = [ { siteId: "zeta", origin: "https://zeta.example" }, ...pool(), { siteId: "alpha", origin: "https://alpha.example" }, ]; // The summary also names a site the hub does not list (index 1): its colour // stays its own, and is not handed to an unnamed member. const summary = homepageOrder(); summary.sites.splice(1, 0, { siteId: "gone", siteTitle: "gone", siteUrl: "https://gone.example", }); const out = officialInstances(members, summary); assert.deepEqual( out.map((o) => [o.site.siteId, o.accent]), [ ["jeralyzer", seriesColor(0)], ["anilyzer", seriesColor(2)], ["hasanalyzer", seriesColor(3)], ["zeta", seriesColor(4)], ["alpha", seriesColor(5)], ], ); // Matched by origin when the id changed between builds. const renamed = officialInstances( [{ siteId: "jer", origin: "https://jeralyzer.example" }], homepageOrder(), ); assert.equal(renamed[0].figures?.siteId, "jeralyzer"); assert.equal(renamed[0].accent, seriesColor(0)); }); test("a named accent's id rides through toHubSummary and parseHubSummary", () => { const s = homepageSummary(); s.sites[1] = { ...s.sites[1], accent: "#e3b15c", accentId: "brass" }; const hub = toHubSummary(s); assert.equal(hub.sites[1].accent, "#e3b15c"); assert.equal(hub.sites[1].accentId, "brass"); assert.ok(!("accentId" in hub.sites[0])); // alpha's is a custom hex assert.deepEqual(parseHubSummary(JSON.parse(JSON.stringify(hub))), hub); // An id the palette does not know is dropped; the hex stays. const odd = parseHubSummary({ version: HUB_SUMMARY_VERSION, sites: [ { siteId: "x", siteTitle: "X", siteUrl: "https://x.example", accent: "#112233", accentId: "gold" }, ], }); assert.deepEqual(odd?.sites[0], { siteId: "x", siteTitle: "X", siteUrl: "https://x.example", accent: "#112233", }); }); test("officialInstances: a named accent is painted per base; a site's own different hex still wins", () => { const summary = homepageOrder(); // jeralyzer: Brass, published as its on-dark hex plus the id. summary.sites[0] = { ...summary.sites[0], accent: "#e3b15c", accentId: "brass" }; // anilyzer: Sakura in the summary, but hub-sites.json says something else. summary.sites[1] = { ...summary.sites[1], accent: "#ee8fb5", accentId: "sakura" }; const members = pool().map((m) => m.siteId === "jeralyzer" ? { ...m, accent: "#E3B15C" } // the same colour, as compose-hub writes it : m.siteId === "anilyzer" ? { ...m, accent: "#cc3366" } : m, ); const out = officialInstances(members, summary); assert.deepEqual( out.map((o) => [o.site.siteId, o.accent]), [ ["jeralyzer", "var(--swatch-brass)"], ["anilyzer", fitted("#cc3366")], // the site's own, which disagrees: no id ["hasanalyzer", seriesColor(2)], ], ); // With no accent in hub-sites.json, the summary's id is used. const bare = officialInstances(pool(), summary); assert.equal(bare[0].accent, "var(--swatch-brass)"); assert.equal(bare[1].accent, "var(--swatch-sakura)"); }); test("officialInstances: a site with no accent wears its homepage chart colour, never a family's slot", () => { const summary = homepageOrder(); // anilyzer is Blue, so its chart layer holds --chart-1 — the slot jeralyzer // (index 0, no accent) would have worn. jeralyzer takes the lowest free one, // as the homepage's chart and card do (siteChartColors). summary.sites[1] = { ...summary.sites[1], accent: "#74a9f2", accentId: "blue" }; const out = officialInstances(pool(), summary); assert.deepEqual( out.map((o) => [o.site.siteId, o.accent]), [ ["jeralyzer", "var(--chart-2)"], ["anilyzer", "var(--swatch-blue)"], ["hasanalyzer", "var(--chart-3)"], ], ); });