mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 09:28:08 +00:00
* feat(engine): drawElementImage capture service * chore(ci): ignore drawElementService exports pending upstack consumers Fallow's per-PR audit diffs against the merge base, so the bottom of the fast-capture stack (#1917) sees drawElementService's exports as unused — their consumers (frameCapture) land in #1919, two PRs upstack. ignoreExports entry documents this and can be dropped once #1919 merges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(engine): 3D projection + compositor-effect risk gate * fix(engine): gate filter drop-shadow wherever blur gates (review) detectCssEffectRisk documented drop-shadow as a ~29dB damage case but only detected blur( in its three scan paths — a drop-shadow comp stayed on the fast path despite the gate's own correctness contract. Detect drop-shadow( in computed styles, stylesheet rules, and GSAP tween vars, pinned by a focused test that runs the real page-side closure against a DOM shim (computed / stylesheet / tween coverage + blur regression + effect-free null). Addresses miguel-heygen's blocker on #1918. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
/**
|
|
* Focused coverage for `detectCssEffectRisk`'s effect detection — specifically
|
|
* that `filter: drop-shadow(...)` gates the fast path everywhere `blur(` does
|
|
* (computed styles, stylesheet rules, GSAP tween vars). drop-shadow is a
|
|
* documented ~29 dB damage case (drop-shadow-on-SVG especially); a gate that
|
|
* detects blur but not drop-shadow silently ships that damage.
|
|
*
|
|
* The detector's page-side closure runs verbatim inside a mock `page.evaluate`
|
|
* with a minimal DOM shim, so these tests pin the real scan logic, not a copy.
|
|
*/
|
|
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import type { Page } from "puppeteer-core";
|
|
import { detectCssEffectRisk } from "./threeDProjection.js";
|
|
|
|
interface ShimEnv {
|
|
/** Computed style returned for every element. */
|
|
computed?: Partial<{
|
|
filter: string;
|
|
backdropFilter: string;
|
|
mixBlendMode: string;
|
|
animationName: string;
|
|
animationDuration: string;
|
|
}>;
|
|
/** Raw cssText of stylesheet rules visible to the scan. */
|
|
styleRules?: string[];
|
|
/** GSAP-shaped timelines exposed as window.__timelines. */
|
|
timelines?: Record<string, unknown>;
|
|
}
|
|
|
|
const SHIMMED_GLOBALS = ["document", "window", "getComputedStyle"] as const;
|
|
const saved = new Map<string, unknown>();
|
|
|
|
function installShim(env: ShimEnv): void {
|
|
const el = { tagName: "DIV", querySelectorAll: () => [] };
|
|
const doc = {
|
|
querySelector: (sel: string) => (sel === "[data-composition-id]" ? el : null),
|
|
querySelectorAll: () => [],
|
|
styleSheets: (env.styleRules ?? []).map((cssText) => ({
|
|
cssRules: [{ cssText }],
|
|
})),
|
|
};
|
|
const win = { __timelines: env.timelines ?? {} };
|
|
const computed = {
|
|
filter: "none",
|
|
backdropFilter: "",
|
|
webkitBackdropFilter: "",
|
|
mixBlendMode: "normal",
|
|
animationName: "none",
|
|
animationDuration: "0s",
|
|
...env.computed,
|
|
};
|
|
const g = globalThis as Record<string, unknown>;
|
|
for (const k of SHIMMED_GLOBALS) {
|
|
if (!saved.has(k)) saved.set(k, g[k]);
|
|
}
|
|
g.document = doc;
|
|
g.window = win;
|
|
g.getComputedStyle = () => computed;
|
|
}
|
|
|
|
afterEach(() => {
|
|
const g = globalThis as Record<string, unknown>;
|
|
for (const [k, v] of saved) {
|
|
if (v === undefined) delete g[k];
|
|
else g[k] = v;
|
|
}
|
|
saved.clear();
|
|
});
|
|
|
|
function makeMockPage(env: ShimEnv): Page {
|
|
return {
|
|
evaluate: async (fn: () => unknown) => {
|
|
installShim(env);
|
|
return fn();
|
|
},
|
|
} as unknown as Page;
|
|
}
|
|
|
|
describe("detectCssEffectRisk drop-shadow gating", () => {
|
|
it("detects drop-shadow in computed styles", async () => {
|
|
const risk = await detectCssEffectRisk(
|
|
makeMockPage({ computed: { filter: "drop-shadow(0 2px 4px black)" } }),
|
|
);
|
|
expect(risk).toBe("filter:drop-shadow");
|
|
});
|
|
|
|
it("detects drop-shadow declared in a stylesheet rule", async () => {
|
|
const risk = await detectCssEffectRisk(
|
|
makeMockPage({ styleRules: [".card { filter: drop-shadow(0 0 8px red); }"] }),
|
|
);
|
|
expect(risk).toBe("filter:drop-shadow");
|
|
});
|
|
|
|
it("detects drop-shadow animated via GSAP tween vars", async () => {
|
|
const tween = { vars: { filter: "drop-shadow(0 4px 12px rgba(0,0,0,0.5))" } };
|
|
const timeline = { getChildren: () => [tween] };
|
|
const risk = await detectCssEffectRisk(makeMockPage({ timelines: { main: timeline } }));
|
|
expect(risk).toBe("filter:drop-shadow");
|
|
});
|
|
|
|
it("still detects blur in all three paths", async () => {
|
|
expect(await detectCssEffectRisk(makeMockPage({ computed: { filter: "blur(4px)" } }))).toBe(
|
|
"filter:blur",
|
|
);
|
|
expect(
|
|
await detectCssEffectRisk(makeMockPage({ styleRules: [".x { filter: blur(2px); }"] })),
|
|
).toBe("filter:blur");
|
|
const tween = { vars: { filter: "blur(6px)" } };
|
|
expect(
|
|
await detectCssEffectRisk(
|
|
makeMockPage({ timelines: { main: { getChildren: () => [tween] } } }),
|
|
),
|
|
).toBe("filter:blur");
|
|
});
|
|
|
|
it("returns null for an effect-free composition", async () => {
|
|
expect(await detectCssEffectRisk(makeMockPage({}))).toBe(null);
|
|
});
|
|
});
|