@@ -71,6 +71,24 @@ describe("core rules", () => {
7171 expect ( finding ?. message ) . toContain ( "without initializing" ) ;
7272 } ) ;
7373
74+ it ( "reports error when dot timeline registry is assigned without initializing" , async ( ) => {
75+ const html = `
76+ <html><body>
77+ <div id="root" data-composition-id="c1" data-width="1920" data-height="1080">
78+ <div id="stage"></div>
79+ </div>
80+ <script>
81+ const tl = gsap.timeline({ paused: true });
82+ tl.to("#stage", { opacity: 1, duration: 1 }, 0);
83+ window.__timelines.c1 = tl;
84+ </script>
85+ </body></html>` ;
86+ const result = await lintHyperframeHtml ( html ) ;
87+ const finding = result . findings . find ( ( f ) => f . code === "timeline_registry_missing_init" ) ;
88+ expect ( finding ) . toBeDefined ( ) ;
89+ expect ( finding ?. severity ) . toBe ( "error" ) ;
90+ } ) ;
91+
7492 it ( "does not flag timeline assignment when init guard is present" , async ( ) => {
7593 const validComposition = `
7694<html>
@@ -92,6 +110,54 @@ describe("core rules", () => {
92110 expect ( finding ) . toBeUndefined ( ) ;
93111 } ) ;
94112
113+ describe ( "timeline_id_mismatch" , ( ) => {
114+ it ( "accepts dot timeline registration" , async ( ) => {
115+ const html = `
116+ <html><body>
117+ <div data-composition-id="launch" data-width="1920" data-height="1080"></div>
118+ <script>
119+ window.__timelines = window.__timelines || {};
120+ const tl = gsap.timeline({ paused: true });
121+ window.__timelines.launch = tl;
122+ </script>
123+ </body></html>` ;
124+ const result = await lintHyperframeHtml ( html ) ;
125+ const finding = result . findings . find ( ( f ) => f . code === "timeline_id_mismatch" ) ;
126+ expect ( finding ) . toBeUndefined ( ) ;
127+ } ) ;
128+
129+ it ( "reports mismatched dot timeline registration" , async ( ) => {
130+ const html = `
131+ <html><body>
132+ <div data-composition-id="launch" data-width="1920" data-height="1080"></div>
133+ <script>
134+ window.__timelines = window.__timelines || {};
135+ const tl = gsap.timeline({ paused: true });
136+ window.__timelines.intro = tl;
137+ </script>
138+ </body></html>` ;
139+ const result = await lintHyperframeHtml ( html ) ;
140+ const finding = result . findings . find ( ( f ) => f . code === "timeline_id_mismatch" ) ;
141+ expect ( finding ) . toBeDefined ( ) ;
142+ expect ( finding ?. message ) . toContain ( 'Timeline registered as "intro"' ) ;
143+ } ) ;
144+
145+ it ( "accepts bracket timeline registration for hyphenated ids" , async ( ) => {
146+ const html = `
147+ <html><body>
148+ <div data-composition-id="product-launch" data-width="1920" data-height="1080"></div>
149+ <script>
150+ window.__timelines = window.__timelines || {};
151+ const tl = gsap.timeline({ paused: true });
152+ window.__timelines["product-launch"] = tl;
153+ </script>
154+ </body></html>` ;
155+ const result = await lintHyperframeHtml ( html ) ;
156+ const finding = result . findings . find ( ( f ) => f . code === "timeline_id_mismatch" ) ;
157+ expect ( finding ) . toBeUndefined ( ) ;
158+ } ) ;
159+ } ) ;
160+
95161 it ( "warns when a timeline-visible element has no stable id for Studio editing" , async ( ) => {
96162 const html = `
97163<html><body>
0 commit comments