@@ -11,22 +11,26 @@ describe("RefreshController", () => {
1111 setSystemTime ( ) ;
1212 } ) ;
1313
14- it ( "rate-limits multiple schedule() calls (doesn't reset timer)" , async ( ) => {
14+ it ( "debounces schedule() calls (resets timer)" , async ( ) => {
1515 const onRefresh = jest . fn < ( ) => void > ( ) ;
1616 const controller = new RefreshController ( { onRefresh, debounceMs : 50 } ) ;
1717
1818 controller . schedule ( ) ;
1919 await sleep ( 20 ) ;
20- controller . schedule ( ) ; // Shouldn't reset timer
20+ controller . schedule ( ) ; // Resets debounce timer
2121
22- // Should fire at 50ms from first call, not 70ms
23- await sleep ( 40 ) ;
22+ // Not yet: only 30ms since last call (< debounceMs)
23+ await sleep ( 30 ) ;
24+ expect ( onRefresh ) . not . toHaveBeenCalled ( ) ;
25+
26+ // Now past debounceMs since last call
27+ await sleep ( 30 ) ;
2428 expect ( onRefresh ) . toHaveBeenCalledTimes ( 1 ) ;
2529
2630 controller . dispose ( ) ;
2731 } ) ;
2832
29- it ( "coalesces calls during rate-limit window" , async ( ) => {
33+ it ( "coalesces calls during debounce window" , async ( ) => {
3034 const onRefresh = jest . fn < ( ) => void > ( ) ;
3135 const controller = new RefreshController ( { onRefresh, debounceMs : 50 } ) ;
3236
@@ -43,7 +47,7 @@ describe("RefreshController", () => {
4347 controller . dispose ( ) ;
4448 } ) ;
4549
46- it ( "requestImmediate() bypasses rate-limit timer" , async ( ) => {
50+ it ( "requestImmediate() bypasses debounce timer" , async ( ) => {
4751 const onRefresh = jest . fn < ( ) => void > ( ) ;
4852 const controller = new RefreshController ( { onRefresh, debounceMs : 50 } ) ;
4953
@@ -81,8 +85,6 @@ describe("RefreshController", () => {
8185 } ) ;
8286
8387 it ( "schedule() during in-flight queues refresh for after completion" , async ( ) => {
84- setSystemTime ( 0 ) ;
85-
8688 const resolvers : Array < ( ) => void > = [ ] ;
8789 const onRefresh = jest . fn (
8890 ( ) =>
@@ -91,24 +93,25 @@ describe("RefreshController", () => {
9193 } )
9294 ) ;
9395
94- const controller = new RefreshController ( { onRefresh, debounceMs : 50 } ) ;
96+ const controller = new RefreshController ( { onRefresh, debounceMs : 20 } ) ;
9597
9698 // Start first refresh
9799 controller . requestImmediate ( ) ;
98100 expect ( onRefresh ) . toHaveBeenCalledTimes ( 1 ) ;
99101 expect ( resolvers ) . toHaveLength ( 1 ) ;
100102
101- // schedule() while in-flight should queue, not start timer
103+ // schedule() while in-flight should queue, not start a second refresh
102104 controller . schedule ( ) ;
103105
104- // Complete the first refresh and let .finally() run
106+ // Ensure the debounce timer has fired while we're still in-flight.
107+ await sleep ( 30 ) ;
108+ expect ( onRefresh ) . toHaveBeenCalledTimes ( 1 ) ;
109+
110+ // Complete the first refresh and let .finally() run.
105111 resolvers [ 0 ] ( ) ;
106112 await Promise . resolve ( ) ;
107113 await Promise . resolve ( ) ; // Extra tick for .finally()
108114
109- // Make the follow-up refresh eligible immediately (skip MIN_REFRESH_INTERVAL_MS wait)
110- setSystemTime ( 1000 ) ;
111-
112115 // Allow post-flight setTimeout(0) to run
113116 await sleep ( 0 ) ;
114117 await sleep ( 10 ) ;
@@ -168,110 +171,4 @@ describe("RefreshController", () => {
168171
169172 expect ( onRefresh ) . not . toHaveBeenCalled ( ) ;
170173 } ) ;
171-
172- it ( "requestImmediate() bypasses isPaused check (for manual refresh)" , async ( ) => {
173- const onRefresh = jest . fn < ( ) => void > ( ) ;
174- const paused = true ;
175- const controller = new RefreshController ( {
176- onRefresh,
177- debounceMs : 50 ,
178- isPaused : ( ) => paused ,
179- } ) ;
180-
181- // schedule() should be blocked by isPaused
182- controller . schedule ( ) ;
183- await sleep ( 60 ) ;
184- expect ( onRefresh ) . not . toHaveBeenCalled ( ) ;
185-
186- // requestImmediate() should bypass isPaused (manual refresh)
187- controller . requestImmediate ( ) ;
188- expect ( onRefresh ) . toHaveBeenCalledTimes ( 1 ) ;
189-
190- controller . dispose ( ) ;
191- } ) ;
192-
193- it ( "schedule() respects isPaused and flushes on notifyUnpaused" , async ( ) => {
194- const onRefresh = jest . fn < ( ) => void > ( ) ;
195- let paused = true ;
196- const controller = new RefreshController ( {
197- onRefresh,
198- debounceMs : 50 ,
199- isPaused : ( ) => paused ,
200- } ) ;
201-
202- // schedule() should queue but not execute while paused
203- controller . schedule ( ) ;
204- await sleep ( 60 ) ;
205- expect ( onRefresh ) . not . toHaveBeenCalled ( ) ;
206-
207- // Unpausing should flush the pending refresh
208- paused = false ;
209- controller . notifyUnpaused ( ) ;
210- expect ( onRefresh ) . toHaveBeenCalledTimes ( 1 ) ;
211-
212- controller . dispose ( ) ;
213- } ) ;
214-
215- it ( "lastRefreshInfo tracks trigger and timestamp" , async ( ) => {
216- // This test needs to avoid MIN_REFRESH_INTERVAL_MS; use setSystemTime() to simulate time passing.
217- setSystemTime ( 0 ) ;
218-
219- const onRefresh = jest . fn < ( ) => void > ( ) ;
220- const controller = new RefreshController ( { onRefresh, debounceMs : 20 } ) ;
221-
222- expect ( controller . lastRefreshInfo ) . toBeNull ( ) ;
223-
224- // Manual refresh should record "manual" trigger
225- const beforeManual = Date . now ( ) ;
226- controller . requestImmediate ( ) ;
227- expect ( controller . lastRefreshInfo ) . not . toBeNull ( ) ;
228- expect ( controller . lastRefreshInfo ! . trigger ) . toBe ( "manual" ) ;
229- expect ( controller . lastRefreshInfo ! . timestamp ) . toBeGreaterThanOrEqual ( beforeManual ) ;
230-
231- // Scheduled refresh should record "scheduled" trigger
232- setSystemTime ( 1000 ) ;
233- controller . schedule ( ) ;
234- await sleep ( 30 ) ;
235- expect ( controller . lastRefreshInfo ! . trigger ) . toBe ( "scheduled" ) ;
236-
237- // Priority refresh should record "priority" trigger
238- setSystemTime ( 2000 ) ;
239- controller . schedulePriority ( ) ;
240- await sleep ( 30 ) ;
241- expect ( controller . lastRefreshInfo ! . trigger ) . toBe ( "priority" ) ;
242-
243- controller . dispose ( ) ;
244- } ) ;
245-
246- it ( "onRefreshComplete callback is called with refresh info" , async ( ) => {
247- // This test needs to avoid MIN_REFRESH_INTERVAL_MS; use setSystemTime() to simulate time passing.
248- setSystemTime ( 0 ) ;
249-
250- const onRefresh = jest . fn < ( ) => void > ( ) ;
251- const onRefreshComplete = jest . fn < ( info : { trigger : string ; timestamp : number } ) => void > ( ) ;
252- const controller = new RefreshController ( {
253- onRefresh,
254- onRefreshComplete,
255- debounceMs : 20 ,
256- } ) ;
257-
258- expect ( onRefreshComplete ) . not . toHaveBeenCalled ( ) ;
259-
260- controller . requestImmediate ( ) ;
261- expect ( onRefreshComplete ) . toHaveBeenCalledTimes ( 1 ) ;
262- expect ( onRefreshComplete ) . toHaveBeenCalledWith (
263- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
264- expect . objectContaining ( { trigger : "manual" , timestamp : expect . any ( Number ) } )
265- ) ;
266-
267- setSystemTime ( 1000 ) ;
268- controller . schedule ( ) ;
269- await sleep ( 30 ) ;
270- expect ( onRefreshComplete ) . toHaveBeenCalledTimes ( 2 ) ;
271- expect ( onRefreshComplete ) . toHaveBeenLastCalledWith (
272- expect . objectContaining ( { trigger : "scheduled" } )
273- ) ;
274-
275- controller . dispose ( ) ;
276- } ) ;
277174} ) ;
0 commit comments