fix(lint): flag autoAlpha on clip elements (#3692)

This commit is contained in:
Miguel Ángel
2026-09-08 18:11:24 +00:00
committed by GitHub
parent c5b7f6aeab
commit 6c0ee41ac4
2 changed files with 52 additions and 4 deletions
+47
View File
@@ -481,6 +481,53 @@ describe("GSAP rules", () => {
expect(finding?.message).toContain("visibility");
});
it("ERRORS when GSAP animates autoAlpha on a clip element", async () => {
const html = `
<html><body>
<div data-composition-id="c1" data-width="1920" data-height="1080">
<div id="incoming" class="clip" data-start="1.01" data-duration="5" data-track-index="1">
<p>Incoming scene</p>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.fromTo("#incoming", { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.3 }, 1.01);
window.__timelines["c1"] = tl;
</script>
</body></html>`;
const result = await lintHyperframeHtml(html);
const finding = result.findings.find((f) => f.code === "gsap_animates_clip_element");
expect(finding).toBeDefined();
expect(finding?.severity).toBe("error");
expect(finding?.message).toContain("autoAlpha");
expect(finding?.fixHint).toContain("child");
});
it("does NOT flag autoAlpha on content inside a clip", async () => {
const html = `
<html><body>
<div data-composition-id="c1" data-width="1920" data-height="1080">
<div id="incoming" class="clip" data-start="1.01" data-duration="5" data-track-index="1">
<p id="content">Incoming scene</p>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.fromTo("#content", { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.3 }, 1.01);
window.__timelines["c1"] = tl;
</script>
</body></html>`;
const result = await lintHyperframeHtml(html);
const finding = result.findings.find((f) => f.code === "gsap_animates_clip_element");
expect(finding).toBeUndefined();
});
it("ERRORS when GSAP animates display on a clip element", async () => {
const html = `
<html><body>
+5 -4
View File
@@ -1365,24 +1365,25 @@ export const gsapRules: LintRule<LintContext>[] = [
});
}
// gsap_animates_clip_element — only error when GSAP animates visibility/display
// gsap_animates_clip_element — GSAP must not compete with the framework's
// clip visibility window. autoAlpha owns visibility as well as opacity.
for (const win of gsapWindows) {
const sel = win.targetSelector;
const clipInfo = clipIds.get(sel) || clipClasses.get(sel);
if (!clipInfo) continue;
const conflictingProps = win.properties.filter(
(p) => p === "visibility" || p === "display",
(p) => p === "visibility" || p === "display" || p === "autoAlpha",
);
if (conflictingProps.length === 0) continue;
const elDesc = `<${clipInfo.tag}${clipInfo.id ? ` id="${clipInfo.id}"` : ""} class="${clipInfo.classes}">`;
findings.push({
code: "gsap_animates_clip_element",
severity: "error",
message: `GSAP animation sets ${conflictingProps.join(", ")} on a clip element. Selector "${sel}" resolves to element ${elDesc}. The framework manages clip visibility via ${conflictingProps.join("/")} — do not animate these properties on clip elements.`,
message: `GSAP animation sets ${conflictingProps.join(", ")} on a clip element. Selector "${sel}" resolves to element ${elDesc}. The framework manages clip visibility, and autoAlpha writes visibility as well as opacity — do not animate these properties on clip elements.`,
selector: sel,
elementId: clipInfo.id || undefined,
fixHint:
"Remove the visibility/display tween, or move the content into a child <div> and target that instead.",
"Remove the visibility/display/autoAlpha tween, or move the content into a child <div> and target that instead.",
snippet: truncateSnippet(win.raw),
});
}