// Tests for the build's half of the deck: the framing filters, the regions the // overlay is placed by, the footer a card reserves, and the chapter name. The // geometry itself is deck.mjs's and is tested there; these check the build // reads it rather than restating it. // // Run with: pnpm test:scripts import assert from "node:assert/strict"; import test from "node:test"; import path from "node:path"; import { chapterTitle, chromeRegions, concatListText, deckFraming, deckFramingFilter, sameConcatList } from "./build-video.mjs"; import { deckGeometry } from "./deck.mjs"; import { reservedFooterHeight } from "./render-cards.mjs"; const PALETTE = { bg: "#15121c", fg: "#ece8f4", muted: "#9a93ad", accent: "#7c5cff", amber: "#f2b84b" }; const BASE = { width: 1920, height: 1080, fps: 30, palette: PALETTE }; const deck = (d = {}) => ({ ...BASE, chrome: { engine: "hyperframes", layout: "deck", deck: d } }); test("deckFraming: the default box is 1574x886 at (173,2), the rest is ground", () => { const f = deckFraming(deck()); assert.deepEqual(f.box, { x: 173, y: 2, width: 1574, height: 886 }); assert.deepEqual(f.fit, [ "scale=1574:886:force_original_aspect_ratio=decrease", `pad=1574:886:(ow-iw)/2:(oh-ih)/2:color=${PALETTE.bg}`, ]); assert.deepEqual(f.place, [`pad=1920:1080:173:2:color=${PALETTE.bg}`, "setsar=1", "fps=30"]); }); test("deckFraming: follows deckGeometry for other settings, never its own numbers", () => { const r = deck({ height: 240, footageScale: 0.7 }); const g = deckGeometry(r); const f = deckFraming(r); assert.deepEqual(f.box, g.footage); assert.equal(f.place[0], `pad=1920:1080:${g.footage.x}:${g.footage.y}:color=${PALETTE.bg}`); // The bottom of the box sits at or above the deck's top edge. assert.ok(g.footage.y + g.footage.height <= g.deck.y); }); test("deckFramingFilter: fit then place, one chain", () => { assert.equal( deckFramingFilter(deck()), [ "scale=1574:886:force_original_aspect_ratio=decrease", `pad=1574:886:(ow-iw)/2:(oh-ih)/2:color=${PALETTE.bg}`, `pad=1920:1080:173:2:color=${PALETTE.bg}`, "setsar=1", "fps=30", ].join(","), ); }); test("reservedFooterHeight: the deck reserves nothing over hidden cards, its height over shown ones", () => { assert.equal(reservedFooterHeight(deck()), 0); assert.equal(reservedFooterHeight(deck({ overCards: "hide" })), 0); assert.equal(reservedFooterHeight(deck({ overCards: "show" })), 190); assert.equal(reservedFooterHeight(deck({ overCards: "show", height: 240 })), 240); }); test("reservedFooterHeight: without a deck, unchanged", () => { assert.equal(reservedFooterHeight(BASE), 100); assert.equal(reservedFooterHeight({ ...BASE, footerHeight: 92 }), 92); assert.equal(reservedFooterHeight({ ...BASE, chromeEngine: "hyperframes" }), 200); assert.equal(reservedFooterHeight({ ...BASE, chromeEngine: "hyperframes", chart: { height: 240 } }), 240); }); test("chromeRegions: the deck is one full-width region at the bottom", () => { assert.deepEqual(chromeRegions(deck(), "/o/sourced"), [ { name: "deck", frames: "/o/sourced/chrome/deck-frames", x: 0, y: 890, width: 1920, height: 190 }, ]); assert.deepEqual(chromeRegions(deck({ height: 240 }), "/o")[0], { name: "deck", frames: "/o/chrome/deck-frames", x: 0, y: 840, width: 1920, height: 240, }); }); test("chromeRegions: the chart band's branch is untouched", () => { assert.deepEqual(chromeRegions({ ...BASE, chromeEngine: "hyperframes" }, "/o"), [ { name: "chart", frames: "/o/chrome/chart-frames", x: 0, y: 880, width: 1920, height: 200 }, ]); }); test("chapterTitle: chapter, then (under the deck) the on-screen title, then the derived line", async () => { const PROV = { siteOrigin: "https://example.pages.dev", channelSlug: "chan" }; const DECK = { deck: true }; // A clip with an on-screen title under the deck never reaches the metadata // lookup, so this needs no cue source and no network. const clip = { type: "clip", id: "c01", video: "abc", start: 1, end: 9, onscreen: { title: "County says yes" } }; assert.equal(await chapterTitle(clip, 0, PROV, DECK), "County says yes"); assert.equal(await chapterTitle({ ...clip, chapter: "Authored" }, 0, PROV, DECK), "Authored"); const card = { type: "card", id: "t00", heading: "The heading", title: "The title" }; assert.equal(await chapterTitle(card, 0, PROV, DECK), "The title"); assert.equal(await chapterTitle({ ...card, onscreen: { title: "On screen" } }, 0, PROV, DECK), "On screen"); assert.equal(await chapterTitle({ type: "card", id: "x" }, 4, PROV, DECK), "Card 5"); // A subtitle alone is not a title. assert.equal(await chapterTitle({ ...card, onscreen: { subtitle: "only" } }, 0, PROV, DECK), "The title"); // Without the deck an on-screen title is not drawn, so it names no chapter: // a manifest without render.chrome builds exactly as it did. assert.equal(await chapterTitle({ ...card, onscreen: { title: "On screen" } }, 0, PROV), "The title"); assert.equal(await chapterTitle({ ...card, onscreen: { title: "On screen" } }, 0, PROV, { deck: false }), "The title"); }); test("sameConcatList: a cached hard-cut concat is reused only for the same segments in the same order", () => { const segs = ["out/sourced/segments/c01.mp4", "out/sourced/segments/c02.mp4", "out/sourced/segments/c03.mp4"]; const recorded = concatListText(segs); assert.equal(sameConcatList(recorded, segs), true); // Reordered: same files, same total length -- the case length and age miss. assert.equal(sameConcatList(recorded, [segs[1], segs[0], segs[2]]), false); assert.equal(sameConcatList(recorded, segs.slice(0, 2)), false); assert.equal(sameConcatList(recorded, [...segs, "out/sourced/segments/c04.mp4"]), false); // No record (a concat made before this check existed) is never reused. assert.equal(sameConcatList(null, segs), false); // Absolute and relative spellings of the same files are the same list. assert.equal(sameConcatList(recorded, segs.map((x) => path.resolve(x))), true); });