@@ -89,6 +89,120 @@ describe('beasties', () => {
8989 expect ( result ) . toMatchSnapshot ( )
9090 } )
9191
92+ it ( 'should preserve order of external stylesheets' , async ( ) => {
93+ const beasties = new Beasties ( {
94+ reduceInlineStyles : false ,
95+ path : fixtureDir ,
96+ } )
97+
98+ const html = fs . readFileSync ( path . join ( fixtureDir , 'multiple-stylesheets.html' ) , 'utf-8' )
99+
100+ const result = await beasties . process ( html )
101+ expect ( result ) . toMatchSnapshot ( )
102+ } )
103+
104+ it ( 'should preserve order of external stylesheets with variable load times' , async ( ) => {
105+ vi . useFakeTimers ( )
106+
107+ const beasties = new Beasties ( {
108+ reduceInlineStyles : false ,
109+ path : '/' ,
110+ mergeStylesheets : false , // Keep separate to verify order
111+ } )
112+
113+ // Simulate variable latency - second file loads fastest
114+ const assets : Record < string , { content : string , delay : number } > = {
115+ '/first.css' : { content : 'h1 { color: red; }' , delay : 50 } ,
116+ '/second.css' : { content : 'h2 { color: blue; }' , delay : 10 } ,
117+ '/third.css' : { content : 'h3 { color: green; }' , delay : 30 } ,
118+ }
119+
120+ beasties . readFile = ( filename ) => {
121+ const key = filename . replace ( / ^ \w : / , '' ) . replace ( / \\ / g, '/' )
122+ const asset = assets [ key ]
123+ if ( ! asset )
124+ return Promise . resolve ( '' )
125+ return new Promise ( ( resolve ) => {
126+ setTimeout ( ( ) => resolve ( asset . content ) , asset . delay )
127+ } )
128+ }
129+
130+ const processPromise = beasties . process ( trim `
131+ <html>
132+ <head>
133+ <link rel="stylesheet" href="/first.css">
134+ <link rel="stylesheet" href="/second.css">
135+ <link rel="stylesheet" href="/third.css">
136+ </head>
137+ <body>
138+ <h1>First</h1>
139+ <h2>Second</h2>
140+ <h3>Third</h3>
141+ </body>
142+ </html>
143+ ` )
144+
145+ // Advance all timers
146+ await vi . runAllTimersAsync ( )
147+ const result = await processPromise
148+
149+ vi . useRealTimers ( )
150+
151+ // Verify style tags are in correct order (first, second, third)
152+ const styleOrder = [ ...result . matchAll ( / < s t y l e > ( [ ^ < ] + ) < \/ s t y l e > / g) ] . map ( m => m [ 1 ] )
153+ expect ( styleOrder ) . toEqual ( [
154+ 'h1{color:red}' ,
155+ 'h2{color:blue}' ,
156+ 'h3{color:green}' ,
157+ ] )
158+
159+ // Verify body links are in correct order
160+ const bodyLinkMatches = result . match ( / < b o d y > [ \s \S ] * < \/ b o d y > / ) ?. [ 0 ] || ''
161+ const linkOrder = [ ...bodyLinkMatches . matchAll ( / h r e f = " \/ ( [ ^ " ] + ) \. c s s " / g) ] . map ( m => m [ 1 ] )
162+ expect ( linkOrder ) . toEqual ( [ 'first' , 'second' , 'third' ] )
163+ } )
164+
165+ it ( 'should use custom embedLinkedStylesheet when overridden' , async ( ) => {
166+ const embeddedLinks : string [ ] = [ ]
167+
168+ class CustomBeasties extends Beasties {
169+ override async embedLinkedStylesheet ( link : Parameters < Beasties [ 'embedLinkedStylesheet' ] > [ 0 ] , document : Parameters < Beasties [ 'embedLinkedStylesheet' ] > [ 1 ] ) {
170+ const href = link . getAttribute ( 'href' )
171+ if ( href ) {
172+ embeddedLinks . push ( href )
173+ }
174+ return super . embedLinkedStylesheet ( link , document )
175+ }
176+ }
177+
178+ const beasties = new CustomBeasties ( {
179+ reduceInlineStyles : false ,
180+ path : '/' ,
181+ } )
182+
183+ const assets : Record < string , string > = {
184+ '/a.css' : 'h1 { color: red; }' ,
185+ '/b.css' : 'h2 { color: blue; }' ,
186+ }
187+ beasties . readFile = filename => assets [ filename . replace ( / ^ \w : / , '' ) . replace ( / \\ / g, '/' ) ] !
188+
189+ await beasties . process ( trim `
190+ <html>
191+ <head>
192+ <link rel="stylesheet" href="/a.css">
193+ <link rel="stylesheet" href="/b.css">
194+ </head>
195+ <body>
196+ <h1>Hello</h1>
197+ <h2>World</h2>
198+ </body>
199+ </html>
200+ ` )
201+
202+ // Verify that our custom embedLinkedStylesheet was called for each stylesheet
203+ expect ( embeddedLinks ) . toEqual ( [ '/a.css' , '/b.css' ] )
204+ } )
205+
92206 it ( 'does not encode HTML' , async ( ) => {
93207 const beasties = new Beasties ( {
94208 reduceInlineStyles : false ,
0 commit comments