@@ -77,6 +77,13 @@ describe('debounce', () => {
7777 expect ( fn ) . toHaveBeenCalledWith ( 'z' ) ;
7878 expect ( fn ) . toHaveBeenCalledTimes ( 1 ) ;
7979 } ) ;
80+
81+ it ( 'handles flush when no pending args' , ( ) => {
82+ const fn = jest . fn ( ) ;
83+ const deb = debounce ( fn , 100 ) ;
84+ deb . flush ( ) ; // Should not call fn when no pending args
85+ expect ( fn ) . not . toHaveBeenCalled ( ) ;
86+ } ) ;
8087} ) ;
8188
8289describe ( 'throttle' , ( ) => {
@@ -109,6 +116,32 @@ describe('throttle', () => {
109116 expect ( fn ) . toHaveBeenCalledWith ( 'b' ) ;
110117 expect ( fn ) . toHaveBeenCalledTimes ( 1 ) ;
111118 } ) ;
119+
120+ it ( 'handles timer cleanup when remaining <= 0' , ( ) => {
121+ const fn = jest . fn ( ) ;
122+ const thr = throttle ( fn , 100 , { leading : true , trailing : true } ) ;
123+
124+ // First call should execute immediately
125+ thr ( 'first' ) ;
126+ expect ( fn ) . toHaveBeenCalledWith ( 'first' ) ;
127+ expect ( fn ) . toHaveBeenCalledTimes ( 1 ) ;
128+
129+ // Wait for throttle period to expire
130+ jest . advanceTimersByTime ( 100 ) ;
131+
132+ // This call should execute immediately since enough time has passed
133+ thr ( 'second' ) ;
134+ expect ( fn ) . toHaveBeenCalledWith ( 'second' ) ;
135+ expect ( fn ) . toHaveBeenCalledTimes ( 2 ) ;
136+ } ) ;
137+
138+ it ( 'does nothing when both leading and trailing are false' , ( ) => {
139+ const fn = jest . fn ( ) ;
140+ const thr = throttle ( fn , 100 , { leading : false , trailing : false } ) ;
141+ thr ( 'test' ) ;
142+ jest . advanceTimersByTime ( 200 ) ;
143+ expect ( fn ) . not . toHaveBeenCalled ( ) ;
144+ } ) ;
112145} ) ;
113146
114147describe ( 'retry' , ( ) => {
@@ -164,6 +197,11 @@ describe('withTimeout', () => {
164197 const slow = new Promise < void > ( ( ) => { } ) ;
165198 await expect ( withTimeout ( slow , 10 , 'timeout!' ) ) . rejects . toThrow ( 'timeout!' ) ;
166199 } ) ;
200+
201+ it ( 'rejects if promise rejects before timeout' , async ( ) => {
202+ const failing = Promise . reject ( new Error ( 'original error' ) ) ;
203+ await expect ( withTimeout ( failing , 100 ) ) . rejects . toThrow ( 'original error' ) ;
204+ } ) ;
167205} ) ;
168206
169207describe ( 'runInBatches' , ( ) => {
@@ -255,6 +293,16 @@ describe('createTaskQueue', () => {
255293 expect ( q . length ) . toBe ( 1 ) ;
256294 expect ( q [ 'isPaused' ] ) . toBe ( true ) ;
257295 } ) ;
296+
297+ it ( 'handles task rejection properly' , async ( ) => {
298+ const q = createTaskQueue ( 1 ) ;
299+ const error = new Error ( 'task failed' ) ;
300+ await expect (
301+ q ( async ( ) => {
302+ throw error ;
303+ } )
304+ ) . rejects . toBe ( error ) ;
305+ } ) ;
258306} ) ;
259307
260308describe ( 'runInSeries' , ( ) => {
@@ -294,6 +342,29 @@ describe('memoizeAsync', () => {
294342 await expect ( memo ( 1 , 2 ) ) . resolves . toBe ( 3 ) ;
295343 expect ( fn ) . toHaveBeenCalledTimes ( 1 ) ;
296344 } ) ;
345+
346+ it ( 'cleans up expired cache entries properly' , async ( ) => {
347+ jest . useFakeTimers ( ) ;
348+ let callCount = 0 ;
349+ const fn = jest . fn ( async ( x : number ) => {
350+ callCount ++ ;
351+ return x + callCount ;
352+ } ) ;
353+ const memo = memoizeAsync ( fn , { ttl : 100 } ) ;
354+
355+ // First call
356+ await expect ( memo ( 1 ) ) . resolves . toBe ( 2 ) ; // 1 + 1
357+ expect ( fn ) . toHaveBeenCalledTimes ( 1 ) ;
358+
359+ // Expire the cache
360+ jest . advanceTimersByTime ( 101 ) ;
361+
362+ // Second call should not use cache
363+ await expect ( memo ( 1 ) ) . resolves . toBe ( 3 ) ; // 1 + 2
364+ expect ( fn ) . toHaveBeenCalledTimes ( 2 ) ;
365+
366+ jest . useRealTimers ( ) ;
367+ } ) ;
297368} ) ;
298369
299370describe ( 'abortable' , ( ) => {
@@ -353,6 +424,25 @@ describe('rateLimit', () => {
353424 expect ( fn ) . toHaveBeenCalledTimes ( 4 ) ;
354425 jest . useRealTimers ( ) ;
355426 } ) ;
427+
428+ it ( 'handles edge case where delay calculation results in very small values' , async ( ) => {
429+ jest . useFakeTimers ( ) ;
430+ const fn = jest . fn ( async ( x : number ) => x ) ;
431+ const limited = rateLimit ( fn , 1 , 100 ) ;
432+
433+ // Make first call
434+ const p1 = limited ( 1 ) ;
435+
436+ // Advance time to make delay calculation very small
437+ jest . advanceTimersByTime ( 99 ) ;
438+ const p2 = limited ( 2 ) ;
439+
440+ jest . advanceTimersByTime ( 2 ) ; // Should use Math.max(1, delay)
441+
442+ await expect ( Promise . all ( [ p1 , p2 ] ) ) . resolves . toEqual ( [ 1 , 2 ] ) ;
443+ expect ( fn ) . toHaveBeenCalledTimes ( 2 ) ;
444+ jest . useRealTimers ( ) ;
445+ } ) ;
356446} ) ;
357447
358448describe ( 'circuitBreaker' , ( ) => {
@@ -471,4 +561,20 @@ describe('runWithConcurrency', () => {
471561 ctrl . abort ( ) ;
472562 await expect ( runWithConcurrency ( [ async ( ) => 1 ] , { signal : ctrl . signal } ) ) . rejects . toThrow ( 'Aborted' ) ;
473563 } ) ;
564+
565+ it ( 'handles error in onProgress callback gracefully' , async ( ) => {
566+ const consoleSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
567+ const tasks = [ 1 , 2 ] . map ( n => async ( ) => n ) ;
568+
569+ await expect (
570+ runWithConcurrency ( tasks , {
571+ concurrency : 2 ,
572+ onProgress : ( ) => {
573+ throw new Error ( 'Progress callback error' ) ;
574+ }
575+ } )
576+ ) . resolves . toEqual ( [ 1 , 2 ] ) ;
577+
578+ consoleSpy . mockRestore ( ) ;
579+ } ) ;
474580} ) ;
0 commit comments