@@ -119,6 +119,29 @@ describe("lintProject", () => {
119119 expect ( finding ?. selector ) . toBe ( '[data-composition-id="scene"] .title' ) ;
120120 } ) ;
121121
122+ it ( "lints percent-encoded linked CSS filenames that exist decoded on disk" , ( ) => {
123+ const encodedFilename = "%E6%97%A5%E6%9C%AC%E8%AA%9E.css" ;
124+ const project = makeProject ( validHtml ( ) , {
125+ "scene.html" : `<html><head><link rel="stylesheet" href="${ encodedFilename } "></head><body>
126+ <div id="scene" data-composition-id="scene" data-width="1920" data-height="1080" data-start="0" data-duration="2"></div>
127+ <script>window.__timelines = window.__timelines || {}; window.__timelines["scene"] = gsap.timeline({ paused: true });</script>
128+ </body></html>` ,
129+ } ) ;
130+ writeFileSync (
131+ join ( project . dir , "compositions" , decodeURIComponent ( encodedFilename ) ) ,
132+ '[data-composition-id="scene"] .title { opacity: 0; }' ,
133+ ) ;
134+
135+ const { results } = lintProject ( project ) ;
136+ const subResult = results . find ( ( result ) => result . file === "compositions/scene.html" ) ;
137+ const finding = subResult ?. result . findings . find (
138+ ( item ) => item . code === "composition_self_attribute_selector" ,
139+ ) ;
140+
141+ expect ( finding ) . toBeDefined ( ) ;
142+ expect ( finding ?. selector ) . toBe ( '[data-composition-id="scene"] .title' ) ;
143+ } ) ;
144+
122145 it ( "aggregates errors across index.html and sub-compositions" , ( ) => {
123146 const project = makeProject ( htmlWithMissingMediaId ( ) , {
124147 "overlay.html" : htmlWithMissingMediaId ( ) ,
@@ -528,6 +551,30 @@ describe("texture_mask_asset_not_found", () => {
528551 expect ( finding ) . toBeUndefined ( ) ;
529552 } ) ;
530553
554+ it ( "checks mask-image URLs inside percent-encoded linked CSS filenames" , ( ) => {
555+ const encodedFilename = "%E6%97%A5%E6%9C%AC%E8%AA%9E.css" ;
556+ const project = makeProject ( validHtml ( ) , {
557+ "scene.html" : `<html><head><link rel="stylesheet" href="${ encodedFilename } "></head><body>
558+ <div data-composition-id="scene" data-width="1920" data-height="1080">
559+ <div class="hf-texture-text hf-texture-lava">TEXT</div>
560+ </div>
561+ <script>window.__timelines = window.__timelines || {}; window.__timelines["scene"] = gsap.timeline({ paused: true });</script>
562+ </body></html>` ,
563+ } ) ;
564+ writeFileSync (
565+ join ( project . dir , "compositions" , decodeURIComponent ( encodedFilename ) ) ,
566+ '.hf-texture-lava { mask-image: url("masks/missing.png"); }' ,
567+ ) ;
568+
569+ const { results } = lintProject ( project ) ;
570+ const finding = results [ 0 ] ?. result . findings . find (
571+ ( item ) => item . code === "texture_mask_asset_not_found" ,
572+ ) ;
573+
574+ expect ( finding ) . toBeDefined ( ) ;
575+ expect ( finding ?. message ) . toContain ( "masks/missing.png" ) ;
576+ } ) ;
577+
531578 it ( "resolves root-absolute mask-image URLs from the project root" , ( ) => {
532579 const html = `<html><body>
533580 <div data-composition-id="main" data-width="1920" data-height="1080">
0 commit comments