mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
feat(cli): caption-zone and frame-check gates on check
Ports the EF bridge's captionZone and frameCheck semantics as opt-in flags so the bespoke bridge can be retired: --caption-zone takes fractional band geometry (x0;y0;x1;y1) with optional severity routing and seek points, defaults matching the bridge (caption seek [1], frame seek [0.5], 2px tolerance, 0.05 opacity floor, 4px minimum size, 0.95 full-frame exclusion, center-in-band comparison, tag|text dedup). --frame-check adds media bounds detection (img/svg/video/canvas) the always-on text canvas_overflow never covered, reusing overflowFor. Breach floor: max(120px, 6% of min canvas dimension). Band math derives from the composition's own canvas, portrait included. Both gates off by default; plain check output unchanged.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import {
|
||||
openSettledCompositionPage,
|
||||
type OpenSettledCompositionPageOptions,
|
||||
} from "../capture/captureCompositionFrame.js";
|
||||
import { DEFAULT_CHECK_OPTIONS, runAuditGrid } from "./checkPipeline.js";
|
||||
import { runBrowserCheck } from "./checkBrowser.js";
|
||||
import type { ProjectDir } from "./project.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
serverClose: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@hyperframes/core/compiler", () => ({
|
||||
bundleToSingleHtml: vi.fn(async () => "<html></html>"),
|
||||
}));
|
||||
|
||||
vi.mock("../capture/captureCompositionFrame.js", () => ({
|
||||
openSettledCompositionPage: vi.fn(),
|
||||
resolveCliChromeGpuMode: vi.fn(() => "hardware"),
|
||||
seekCompositionTimeline: vi.fn(async () => undefined),
|
||||
waitForPreferredSeekTarget: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("./staticProjectServer.js", () => ({
|
||||
serveStaticProjectHtml: vi.fn(async () => ({
|
||||
url: "http://127.0.0.1:3000",
|
||||
close: mocks.serverClose,
|
||||
})),
|
||||
}));
|
||||
|
||||
const PROJECT: ProjectDir = {
|
||||
dir: "/project",
|
||||
name: "project",
|
||||
indexPath: "/project/index.html",
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
document.body.innerHTML = "";
|
||||
Reflect.deleteProperty(window, "__hyperframesGeometryCandidates");
|
||||
Reflect.deleteProperty(window, "__hyperframesLayoutAudit");
|
||||
});
|
||||
|
||||
it("carries raw browser geometry through the page driver and pipeline", async () => {
|
||||
document.body.innerHTML = `
|
||||
<div data-composition-id="main" data-duration="10" data-width="640" data-height="360">
|
||||
<section data-composition-file="scenes/hero.html">
|
||||
<img id="hero-image" data-layout-name="hero" src="data:image/png;base64,AA==" />
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
Object.defineProperty(window, "innerWidth", { configurable: true, value: 640 });
|
||||
Object.defineProperty(window, "innerHeight", { configurable: true, value: 360 });
|
||||
installRects();
|
||||
const page = fakePage();
|
||||
const browser = Object.assign(Object.create(null), {
|
||||
close: vi.fn(async () => undefined),
|
||||
});
|
||||
vi.mocked(openSettledCompositionPage).mockImplementation(
|
||||
async (_html: string, _url: string, options: OpenSettledCompositionPageOptions) => {
|
||||
await options.beforeNavigate?.(page);
|
||||
return { page, browser, renderReadyTimedOut: false };
|
||||
},
|
||||
);
|
||||
|
||||
const result = await runBrowserCheck(
|
||||
PROJECT,
|
||||
{ ...DEFAULT_CHECK_OPTIONS, samples: 1, contrast: false, frameCheck: {} },
|
||||
{ kind: "none" },
|
||||
runAuditGrid,
|
||||
);
|
||||
|
||||
expect(result.layoutIssues).toEqual([
|
||||
expect.objectContaining({
|
||||
code: "frame_out_of_frame",
|
||||
severity: "warning",
|
||||
selector: "#hero-image",
|
||||
sourceFile: "scenes/hero.html",
|
||||
dataAttributes: { "data-layout-name": "hero" },
|
||||
bbox: { x: 600, y: 80, width: 200, height: 100 },
|
||||
rect: { left: 600, top: 80, right: 800, bottom: 180, width: 200, height: 100 },
|
||||
overflow: { right: 160 },
|
||||
time: 5,
|
||||
}),
|
||||
]);
|
||||
expect(mocks.serverClose).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
function installRects(): void {
|
||||
const root = document.querySelector("[data-composition-id]");
|
||||
const image = document.querySelector("#hero-image");
|
||||
if (!root || !image) throw new Error("Geometry fixture failed to mount");
|
||||
vi.spyOn(root, "getBoundingClientRect").mockReturnValue(new DOMRect(0, 0, 640, 360));
|
||||
vi.spyOn(image, "getBoundingClientRect").mockReturnValue(new DOMRect(600, 80, 200, 100));
|
||||
}
|
||||
|
||||
function fakePage() {
|
||||
return Object.assign(Object.create(null), {
|
||||
on: vi.fn(),
|
||||
addScriptTag: vi.fn(async ({ content }: { content: string }) => {
|
||||
window.eval(content);
|
||||
}),
|
||||
evaluate: vi.fn(async (callback: unknown, ...args: unknown[]) => {
|
||||
if (typeof callback !== "function") throw new Error("Expected an evaluate callback");
|
||||
return Reflect.apply(callback, window, args);
|
||||
}),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user