@@ -37,6 +37,14 @@ type PluginWatchSubscription = {
3737}
3838
3939const devPath = resolve ( 'plugins' , 'demo' )
40+
41+ /** The catch-up refresh a live subscription schedules; later counts start after it. */
42+ async function flushCatchUpRefresh ( refresh : ReturnType < typeof vi . fn > ) : Promise < void > {
43+ await vi . advanceTimersByTimeAsync ( 300 )
44+ expect ( refresh ) . toHaveBeenCalledOnce ( )
45+ refresh . mockClear ( )
46+ }
47+
4048const originalPlatform = process . platform
4149const subscribeMock = vi . mocked ( subscribeViaWatcherProcess )
4250let subscriptions : PluginWatchSubscription [ ] = [ ]
@@ -230,6 +238,27 @@ describe('PluginDevWatcher', () => {
230238 expect ( refresh ) . toHaveBeenCalledTimes ( 2 )
231239 } )
232240
241+ it ( 'catches up once when a dev root goes live after an edit it could not report' , async ( ) => {
242+ vi . useFakeTimers ( )
243+ const pending : ( ( ) => void ) [ ] = [ ]
244+ subscribeMock . mockImplementation ( ( path , callback , options , hooks = { } ) => {
245+ const unsubscribe = vi . fn ( ) . mockResolvedValue ( undefined )
246+ subscriptions . push ( { path, callback, options, hooks, unsubscribe } )
247+ return new Promise ( ( resolve ) => pending . push ( ( ) => resolve ( { unsubscribe } ) ) )
248+ } )
249+ const { refresh } = startDevWatcher ( vi . fn ( ) , vi . fn ( ) , [ devPath ] )
250+ // The edit lands while the native subscription is still being set up, so no event reports it.
251+ await vi . advanceTimersByTimeAsync ( 1_000 )
252+ expect ( refresh ) . not . toHaveBeenCalled ( )
253+ for ( const goLive of pending ) {
254+ goLive ( )
255+ }
256+ await vi . advanceTimersByTimeAsync ( 300 )
257+ expect ( refresh ) . toHaveBeenCalledOnce ( )
258+ await vi . advanceTimersByTimeAsync ( 60_000 )
259+ expect ( refresh ) . toHaveBeenCalledOnce ( )
260+ } )
261+
233262 it ( 'refreshes the whole plugin projection after an overflow' , ( ) => {
234263 vi . useFakeTimers ( )
235264 const { refresh, onWatcherError } = startDevWatcher ( )
@@ -504,6 +533,7 @@ describe('PluginDevWatcher', () => {
504533 const options = { enabled : true , devPaths : [ path ] , refresh, reapIdle : vi . fn ( ) }
505534 lifecycle . sync ( options )
506535 await Promise . resolve ( )
536+ await flushCatchUpRefresh ( refresh )
507537 for ( let tick = 0 ; tick < 3 ; tick += 1 ) {
508538 await vi . advanceTimersByTimeAsync ( 60_000 )
509539 expect ( checkBindings ) . toHaveBeenCalledTimes ( tick + 1 )
@@ -567,6 +597,7 @@ describe('PluginDevWatcher', () => {
567597 const options = { enabled : true , devPaths : [ path ] , refresh, reapIdle : vi . fn ( ) }
568598 lifecycle . sync ( options )
569599 await Promise . resolve ( )
600+ await flushCatchUpRefresh ( refresh )
570601 const root = pluginRootSubscription ( )
571602
572603 for ( const type of [ 'create' , 'delete' ] as const ) {
@@ -601,22 +632,26 @@ describe('PluginDevWatcher', () => {
601632 const options = { enabled : true , devPaths : [ path ] , refresh, reapIdle : vi . fn ( ) }
602633 lifecycle . sync ( options )
603634 await Promise . resolve ( )
635+ await flushCatchUpRefresh ( refresh )
604636 const root = pluginRootSubscription ( )
605637 await rename ( path , join ( aliasedRoot . base , 'old' ) )
606638 await mkdir ( path )
607639
608640 await vi . advanceTimersByTimeAsync ( 60_000 )
609- await vi . waitFor ( ( ) => expect ( refresh ) . toHaveBeenCalledOnce ( ) )
641+ await vi . waitFor ( ( ) => expect ( subscribeMock ) . toHaveBeenCalledTimes ( 2 ) )
642+ await vi . advanceTimersByTimeAsync ( 300 )
643+ // The rebind refresh, then the replacement root's one catch-up.
644+ expect ( refresh ) . toHaveBeenCalledTimes ( 2 )
610645 expect ( root . unsubscribe ) . toHaveBeenCalledOnce ( )
611646 expect ( subscriptions . map ( ( item ) => ( { path : item . path , options : item . options } ) ) ) . toEqual ( [
612647 { path, options : { backend : 'windows' } } ,
613648 { path, options : { backend : 'windows' } }
614649 ] )
615650 subscriptions . at ( - 1 ) ?. callback ( null , [ { type : 'create' , path : join ( path , 'manifest.json' ) } ] )
616651 await vi . advanceTimersByTimeAsync ( 300 )
617- expect ( refresh ) . toHaveBeenCalledTimes ( 2 )
652+ expect ( refresh ) . toHaveBeenCalledTimes ( 3 )
618653 await vi . advanceTimersByTimeAsync ( 60_000 )
619- expect ( refresh ) . toHaveBeenCalledTimes ( 2 )
654+ expect ( refresh ) . toHaveBeenCalledTimes ( 3 )
620655 expect ( subscribeMock ) . toHaveBeenCalledTimes ( 2 )
621656 lifecycle . dispose ( )
622657 expect ( vi . getTimerCount ( ) ) . toBe ( 0 )
0 commit comments