@@ -140,6 +140,7 @@ describe("flush()", () => {
140140 ) ;
141141 const adapter = createHttpAdapter ( { projectFilesUrl : BASE } ) ;
142142 void adapter . write ( "comp.html" , "x" ) ; // intentionally not awaited
143+ await Promise . resolve ( ) ; // let path-queue microtask fire so doWrite starts
143144 let flushed = false ;
144145 const flushDone = adapter . flush ( ) . then ( ( ) => {
145146 flushed = true ;
@@ -167,6 +168,62 @@ describe("loadFrom()", () => {
167168 } ) ;
168169} ) ;
169170
171+ // ── write() — per-path serialization ─────────────────────────────────────────
172+
173+ describe ( "write() — per-path serialization" , ( ) => {
174+ it ( "serializes concurrent writes to the same path (second waits for first)" , async ( ) => {
175+ const starts : number [ ] = [ ] ;
176+ let resolveFirst ! : ( ) => void ;
177+ let callCount = 0 ;
178+ vi . stubGlobal (
179+ "fetch" ,
180+ vi . fn ( ) . mockImplementation ( async ( _url : string , init ?: RequestInit ) => {
181+ if ( init ?. method === "PUT" ) {
182+ const n = ++ callCount ;
183+ starts . push ( n ) ;
184+ if ( n === 1 ) await new Promise < void > ( ( r ) => ( resolveFirst = r ) ) ;
185+ }
186+ return { ok : true , status : 200 , json : async ( ) => ( { } ) } ;
187+ } ) ,
188+ ) ;
189+ const adapter = createHttpAdapter ( { projectFilesUrl : BASE } ) ;
190+ const write1 = adapter . write ( "comp.html" , "v1" ) ;
191+ await Promise . resolve ( ) ; // let write1 start
192+ const write2 = adapter . write ( "comp.html" , "v2" ) ;
193+ await Promise . resolve ( ) ; // let write2 attempt to start
194+ expect ( starts ) . toEqual ( [ 1 ] ) ; // write2 has NOT started yet
195+ resolveFirst ( ) ;
196+ await write1 ;
197+ await write2 ;
198+ expect ( starts ) . toEqual ( [ 1 , 2 ] ) ; // write2 started only after write1 finished
199+ } ) ;
200+
201+ it ( "does not block writes to different paths" , async ( ) => {
202+ const starts : string [ ] = [ ] ;
203+ let resolveFirst ! : ( ) => void ;
204+ let callCount = 0 ;
205+ vi . stubGlobal (
206+ "fetch" ,
207+ vi . fn ( ) . mockImplementation ( async ( url : string , init ?: RequestInit ) => {
208+ if ( init ?. method === "PUT" ) {
209+ const n = ++ callCount ;
210+ starts . push ( `${ n } :${ url . split ( "/" ) . pop ( ) } ` ) ;
211+ if ( n === 1 ) await new Promise < void > ( ( r ) => ( resolveFirst = r ) ) ;
212+ }
213+ return { ok : true , status : 200 , json : async ( ) => ( { } ) } ;
214+ } ) ,
215+ ) ;
216+ const adapter = createHttpAdapter ( { projectFilesUrl : BASE } ) ;
217+ const write1 = adapter . write ( "a.html" , "v1" ) ;
218+ await Promise . resolve ( ) ;
219+ void adapter . write ( "b.html" , "v2" ) ; // different path — must not wait for write1
220+ await Promise . resolve ( ) ;
221+ expect ( starts . length ) . toBe ( 2 ) ; // both started concurrently
222+ resolveFirst ( ) ;
223+ await write1 ;
224+ } ) ;
225+ } ) ;
226+
170227// ── on() / unsubscribe ────────────────────────────────────────────────────────
171228
172229describe ( "on() / unsubscribe" , ( ) => {
0 commit comments