import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import test from "node:test"; import { validateFrameHtml } from "./lib/frame-contract.mjs"; const scriptDir = dirname(new URL(import.meta.url).pathname); const buildFrameScript = join(scriptDir, "build-frame.mjs"); const assembleScript = join(scriptDir, "assemble-index.mjs"); const transitionsScript = join(scriptDir, "transitions.mjs"); function write(path, contents) { mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, contents); } function validFrame(id = "01-valid", duration = 3) { return ``; } test("valid bare template fragment passes the shared frame contract", () => { assert.doesNotThrow(() => validateFrameHtml(validFrame(), { expectedId: "01-valid", expectedDuration: 3 }), ); }); test("HTML5 unquoted root attributes are accepted", () => { assert.deepEqual( validateFrameHtml( ``, { expectedId: "01-valid", expectedDuration: 3 }, ), { compositionId: "01-valid", duration: 3 }, ); }); test("duration accepts decimal seconds but rejects alternate numeric syntaxes", () => { assert.doesNotThrow(() => validateFrameHtml( ``, ), ); for (const duration of ["0x10", "3e2", "Infinity"]) { assert.throws( () => validateFrameHtml( ``, ), /positive.*duration/i, ); } }); test("full HTML is rejected even when it contains a valid inner template", () => { const html = `${validFrame()}`; assert.throws( () => validateFrameHtml(html, { expectedId: "01-valid", expectedDuration: 3 }), /bare