mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
fix(lint): detect GSAP animations targeting clip elements (tab crash) (#114)
* fix(lint): detect GSAP animations targeting clip elements (tab crash) The runtime manages clip visibility via inline styles. When GSAP also writes inline styles on the same element, both systems trigger style recalculations every frame, creating a runaway loop that crashes the browser tab. New rule gsap_animates_clip_element (error severity): - Builds map of all elements with class="clip" (by id and class) - Checks if any GSAP selector resolves to a clip element - Nested selectors like "#overlay .title" are correctly ignored - Merged into existing GSAP script loop (no redundant parsing) * fix: remove non-null assertions and add missing test coverage - Replace `!` assertions with optional chaining in lint.ts and tests - Add shouldBlockRender tests for --strict-all without --strict - Add clip element test for class-only detection (no id) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use optional chaining for array access in lintProject tests TypeScript's strict mode flags array indexing as possibly undefined. Use optional chaining and fallbacks instead of non-null assertions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
cf0ec5d27d
commit
a97dc75702
@@ -67,7 +67,9 @@ describe("lintProject", () => {
|
||||
expect(totalErrors).toBe(0);
|
||||
expect(totalWarnings).toBe(0);
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0]!.file).toBe("index.html");
|
||||
const first = results[0];
|
||||
expect(first).toBeDefined();
|
||||
expect(first?.file).toBe("index.html");
|
||||
});
|
||||
|
||||
it("detects errors in index.html", () => {
|
||||
@@ -75,7 +77,9 @@ describe("lintProject", () => {
|
||||
const { totalErrors, results } = lintProject(project);
|
||||
|
||||
expect(totalErrors).toBeGreaterThan(0);
|
||||
const mediaFinding = results[0]!.result.findings.find((f) => f.code === "media_missing_id");
|
||||
const first = results[0];
|
||||
expect(first).toBeDefined();
|
||||
const mediaFinding = first?.result.findings.find((f) => f.code === "media_missing_id");
|
||||
expect(mediaFinding).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -86,9 +90,11 @@ describe("lintProject", () => {
|
||||
const { totalErrors, results } = lintProject(project);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results[1]!.file).toBe("compositions/captions.html");
|
||||
const second = results[1];
|
||||
expect(second).toBeDefined();
|
||||
expect(second?.file).toBe("compositions/captions.html");
|
||||
expect(totalErrors).toBeGreaterThan(0);
|
||||
const subFindings = results[1]!.result.findings;
|
||||
const subFindings = second?.result.findings ?? [];
|
||||
expect(subFindings.some((f) => f.code === "media_missing_id")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -99,9 +105,13 @@ describe("lintProject", () => {
|
||||
const { totalErrors, results } = lintProject(project);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
const first = results[0];
|
||||
const second = results[1];
|
||||
expect(first).toBeDefined();
|
||||
expect(second).toBeDefined();
|
||||
// Both files have media_missing_id errors
|
||||
const rootErrors = results[0]!.result.errorCount;
|
||||
const subErrors = results[1]!.result.errorCount;
|
||||
const rootErrors = first?.result.errorCount ?? 0;
|
||||
const subErrors = second?.result.errorCount ?? 0;
|
||||
expect(totalErrors).toBe(rootErrors + subErrors);
|
||||
});
|
||||
|
||||
@@ -113,7 +123,9 @@ describe("lintProject", () => {
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(totalWarnings).toBeGreaterThan(0);
|
||||
const preloadWarning = results[1]!.result.findings.find((f) => f.code === "media_preload_none");
|
||||
const second = results[1];
|
||||
expect(second).toBeDefined();
|
||||
const preloadWarning = second?.result.findings.find((f) => f.code === "media_preload_none");
|
||||
expect(preloadWarning).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -166,4 +178,12 @@ describe("shouldBlockRender", () => {
|
||||
it("--strict-all: does not block when clean", () => {
|
||||
expect(shouldBlockRender(true, true, 0, 0)).toBe(false);
|
||||
});
|
||||
|
||||
it("--strict-all alone: blocks on errors", () => {
|
||||
expect(shouldBlockRender(false, true, 1, 0)).toBe(true);
|
||||
});
|
||||
|
||||
it("--strict-all alone: blocks on warnings", () => {
|
||||
expect(shouldBlockRender(false, true, 0, 1)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user