@@ -1858,6 +1858,52 @@ describe("bundleToSingleHtml", () => {
18581858 expect ( styleText ( linked ) ) . toContain ( 'url("css/icon1.png")' ) ;
18591859 } ) ;
18601860
1861+ it ( "preserves escaped leading spaces as part of linked CSS asset identity" , async ( ) => {
1862+ const dir = makeTempProject ( {
1863+ "index.html" : `<link rel="stylesheet" href="css/theme.css"><div data-composition-id="root" data-width="320" data-height="180"></div>` ,
1864+ "css/theme.css" : String . raw `.bg { background: url(\ image.png); }` ,
1865+ "css/ image.png" : "leading-space-image" ,
1866+ "css/image.png" : "different-image" ,
1867+ } ) ;
1868+
1869+ const linked = await bundleToSingleHtml ( dir , { inlineAssets : false } ) ;
1870+ expect ( styleText ( linked ) ) . toContain ( "url(css/%20image.png)" ) ;
1871+ const inlined = await bundleToSingleHtml ( dir ) ;
1872+ expect ( styleText ( inlined ) ) . toContain ( inlinedAs ( "image/png" , "leading-space-image" ) ) ;
1873+ expect ( styleText ( inlined ) ) . not . toContain ( inlinedAs ( "image/png" , "different-image" ) ) ;
1874+ } ) ;
1875+
1876+ it . each ( [
1877+ [ "" , String . raw `?v=\ 1` , String . raw `?v=\0000201` ] ,
1878+ [ '"' , String . raw `?v=\22 1` , String . raw `?v=\0000221` ] ,
1879+ [ '"' , String . raw `?v=\22` , String . raw `?v=\000022` ] ,
1880+ [ '"' , String . raw `?v=\3c /style\3e 1` , String . raw `?v=\00003c/style\00003e1` ] ,
1881+ ] ) ( "escapes decoded CSS URL suffixes with delimiter %j" , async ( quote , suffix , escaped ) => {
1882+ const dir = makeTempProject ( {
1883+ "index.html" : `<link rel="stylesheet" href="css/theme.css"><div data-composition-id="root" data-width="320" data-height="180"></div>` ,
1884+ "css/theme.css" : `.bg { background: url(${ quote } icon.png${ suffix } ${ quote } ); }` ,
1885+ "css/icon.png" : "suffix-image" ,
1886+ } ) ;
1887+
1888+ const linked = await bundleToSingleHtml ( dir , { inlineAssets : false } ) ;
1889+ expect ( styleText ( linked ) ) . toContain ( `url(${ quote } css/icon.png${ escaped } ${ quote } )` ) ;
1890+ const inlined = await bundleToSingleHtml ( dir ) ;
1891+ expect ( styleText ( inlined ) ) . toContain ( inlinedAs ( "image/png" , "suffix-image" ) ) ;
1892+ } ) ;
1893+
1894+ it ( "normalizes CSS nulls before resolving linked stylesheet assets" , async ( ) => {
1895+ const dir = makeTempProject ( {
1896+ "index.html" : `<link rel="stylesheet" href="css/theme.css"><div data-composition-id="root" data-width="320" data-height="180"></div>` ,
1897+ "css/theme.css" : ".bg { background: url(\0.png); }" ,
1898+ "css/\uFFFD.png" : "replacement-character-image" ,
1899+ } ) ;
1900+
1901+ const linked = await bundleToSingleHtml ( dir , { inlineAssets : false } ) ;
1902+ expect ( styleText ( linked ) ) . toContain ( "url(css/%EF%BF%BD.png)" ) ;
1903+ const inlined = await bundleToSingleHtml ( dir ) ;
1904+ expect ( styleText ( inlined ) ) . toContain ( inlinedAs ( "image/png" , "replacement-character-image" ) ) ;
1905+ } ) ;
1906+
18611907 it ( "rebases url() paths with ../ traversal in nested @import" , async ( ) => {
18621908 const dir = makeTempProject ( {
18631909 "index.html" : `<!doctype html>
0 commit comments