import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
import { readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { mediaBannerSvg, mediaLockupGeometry, mediaMarkSvg, MEDIA_BANNER, MEDIA_VIDEO_KIT_PX } from "../lib/brandMedia";
import { BANNER_MAX_BYTES, MEDIA_ASSETS, VIDEO_KIT_PATH, videoKitJson, writeMediaAssets } from "./brand-media";
const PNG_MAGIC = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
function ihdr(png: Buffer): { width: number; height: number; colourType: number } {
assert.deepEqual([...png.subarray(0, 8)], PNG_MAGIC, "PNG magic");
assert.equal(png.toString("latin1", 12, 16), "IHDR");
return { width: png.readUInt32BE(16), height: png.readUInt32BE(20), colourType: png[25] };
}
test("the asset list: YouTube's four sizes, each from its own SVG", () => {
assert.deepEqual(
MEDIA_ASSETS.map((a) => [a.png, a.svg, a.width, a.height]),
[
["avatar-800.png", "avatar.svg", 800, 800],
["watermark-150.png", "watermark.svg", 150, 150],
["banner-2560x1440.png", "banner.svg", 2560, 1440],
["banner-safe-1546x423.png", "banner-safe.svg", 1546, 423],
],
);
const by = Object.fromEntries(MEDIA_ASSETS.map((a) => [a.png, a.render()]));
assert.equal(by["avatar-800.png"], mediaMarkSvg("maskable"));
assert.equal(by["watermark-150.png"], mediaMarkSvg("any"));
assert.equal(by["banner-2560x1440.png"], mediaBannerSvg());
assert.equal(by["banner-safe-1546x423.png"], mediaBannerSvg({ view: MEDIA_BANNER.safe }));
});
test("writeMediaAssets: every PNG at its IHDR size, the banner under 6 MB, sources and INDEX.html beside them", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "brand-media-"));
try {
const rows = await writeMediaAssets(dir);
const names = (await readdir(dir)).sort();
assert.deepEqual(names, [
"INDEX.html",
"avatar-800.png",
"avatar.svg",
"banner-2560x1440.png",
"banner-safe-1546x423.png",
"banner-safe.svg",
"banner.svg",
"lockup.svg",
"watermark-150.png",
"watermark.svg",
]);
for (const r of rows) {
const png = await readFile(path.join(dir, r.png));
const h = ihdr(png);
assert.deepEqual([h.width, h.height], [r.width, r.height], r.png);
assert.equal(png.byteLength, r.bytes);
assert.equal(await readFile(path.join(dir, r.svg), "utf8"), r.render());
}
const banner = await readFile(path.join(dir, "banner-2560x1440.png"));
assert.ok(banner.byteLength <= BANNER_MAX_BYTES, `${banner.byteLength} B`);
const html = await readFile(path.join(dir, "INDEX.html"), "utf8");
for (const r of rows) assert.ok(html.includes(`src="${r.png}"`), r.png);
// Studio's current page: Customization -> Profile (Branding was folded into it).
for (const step of ["Customization", "Profile", "Picture", "Banner image", "Video watermark", "Change"]) {
assert.ok(html.includes(step), step);
}
} finally {
await rm(dir, { recursive: true, force: true });
}
});
// umtool's report-to-video preset cannot import TypeScript, so it reads this
// JSON. Stale means the preset draws a lockup the channel no longer uses.
test("umtool/report-to-video/brands/archilyzer-media.json is what --video-kit writes", () => {
assert.equal(readFileSync(VIDEO_KIT_PATH, "utf8"), videoKitJson());
const kit = JSON.parse(videoKitJson());
const g = mediaLockupGeometry(MEDIA_VIDEO_KIT_PX);
assert.equal(kit.lockup.px, MEDIA_VIDEO_KIT_PX);
assert.ok(Math.abs(kit.lockup.blockHeight - g.blockHeight) < 1e-3);
assert.deepEqual(kit.palette, {
bg: "#151b20",
fg: "#e7edf1",
muted: "#8496a2",
accent: "#5fa8a0",
amber: "#e3b15c",
dim: "#586977",
});
});