diff --git a/packages/core/src/lint/rules/composition.test.ts b/packages/core/src/lint/rules/composition.test.ts index 025fcc53a..484eb9273 100644 --- a/packages/core/src/lint/rules/composition.test.ts +++ b/packages/core/src/lint/rules/composition.test.ts @@ -2,6 +2,148 @@ import { describe, it, expect } from "vitest"; import { lintHyperframeHtml } from "../hyperframeLinter.js"; describe("composition rules", () => { + describe("subcomposition guidance", () => { + it("warns when any HTML composition file is over 300 lines", () => { + const html = Array.from({ length: 301 }, (_, i) => + i === 0 ? "" : ``, + ).join("\n"); + + const result = lintHyperframeHtml(html, { filePath: "/project/compositions/scene.html" }); + const finding = result.findings.find((f) => f.code === "composition_file_too_large"); + expect(finding).toBeDefined(); + expect(finding?.severity).toBe("warning"); + }); + + it("does not warn when an HTML composition file is exactly 300 lines", () => { + const html = Array.from({ length: 300 }, (_, i) => + i === 0 ? "" : ``, + ).join("\n"); + + const result = lintHyperframeHtml(html, { filePath: "/project/index.html" }); + const finding = result.findings.find((f) => f.code === "composition_file_too_large"); + expect(finding).toBeUndefined(); + }); + + it("does not count a final trailing newline as an extra physical line", () => { + const html = + Array.from({ length: 300 }, (_, i) => + i === 0 ? "" : ``, + ).join("\n") + "\n"; + + const result = lintHyperframeHtml(html, { filePath: "/project/index.html" }); + const finding = result.findings.find((f) => f.code === "composition_file_too_large"); + expect(finding).toBeUndefined(); + }); + + it("warns on large HTML files regardless of path", () => { + const html = Array.from({ length: 301 }, (_, i) => + i === 0 ? "" : ``, + ).join("\n"); + + const result = lintHyperframeHtml(html, { + filePath: "/project/registry/blocks/data-chart.html", + }); + const finding = result.findings.find((f) => f.code === "composition_file_too_large"); + expect(finding).toBeDefined(); + expect(finding?.severity).toBe("warning"); + }); + + it("uses nested split copy for large sub-composition files", () => { + const html = Array.from({ length: 301 }, (_, i) => + i === 0 ? "" : ``, + ).join("\n"); + + const result = lintHyperframeHtml(html, { + filePath: "/project/compositions/scene.html", + isSubComposition: true, + }); + const finding = result.findings.find((f) => f.code === "composition_file_too_large"); + expect(finding?.fixHint).toContain("Split this sub-composition further"); + }); + + it("warns when more than 3 timed elements share the same track", () => { + const html = ` + +
+
A
+
B
+
C
+
D
+
+`; + + const result = lintHyperframeHtml(html, { filePath: "/project/compositions/scene.html" }); + const finding = result.findings.find((f) => f.code === "timeline_track_too_dense"); + expect(finding).toBeDefined(); + expect(finding?.severity).toBe("warning"); + expect(finding?.message).toContain("Track 0 has 4 timed elements"); + }); + + it("does not warn when 3 timed elements share the same track", () => { + const html = ` + +
+
A
+
B
+
C
+
+`; + + const result = lintHyperframeHtml(html, { filePath: "/project/index.html" }); + const finding = result.findings.find((f) => f.code === "timeline_track_too_dense"); + expect(finding).toBeUndefined(); + }); + + it("does not warn when timed elements are split across tracks", () => { + const html = ` + +
+
A
+
B
+
C
+
D
+
+`; + + const result = lintHyperframeHtml(html, { filePath: "/project/index.html" }); + const finding = result.findings.find((f) => f.code === "timeline_track_too_dense"); + expect(finding).toBeUndefined(); + }); + + it("does not count timed media or script/style tags as dense track elements", () => { + const html = ` + +
+ + + + + +
+`; + + const result = lintHyperframeHtml(html, { filePath: "/project/index.html" }); + const finding = result.findings.find((f) => f.code === "timeline_track_too_dense"); + expect(finding).toBeUndefined(); + }); + + it("does not count root composition or mounted sub-compositions as dense elements", () => { + const html = ` + +
+
+
+
+
+
+`; + + const result = lintHyperframeHtml(html, { filePath: "/project/index.html" }); + const finding = result.findings.find((f) => f.code === "timeline_track_too_dense"); + expect(finding).toBeUndefined(); + }); + }); + it("reports info for composition with external CDN script dependency", () => { const html = `