Files
hyperframes/packages/engine/src/services/threeDProjection-cssEffectRisk.test.ts
T
Vance IngallsandClaude Fable 5 4749fe5716 feat(engine): 3D projection + compositor-effect risk gate (#1918)
* 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>
2026-07-06 16:19:44 -07:00

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);
});
});