1- import { afterEach , test , vi } from 'vitest'
1+ import { afterEach , describe , expect , test , vi } from 'vitest'
22import { _since } from '../datetime/index.js'
33import { pDelay } from '../promise/index.js'
44import type { AnyFunction , UnixTimestampMillis } from '../types.js'
5- import { _debounce } from './debounce.js'
5+ import { _asyncDebounce , _asyncThrottle , _debounce } from './debounce.js'
66
77afterEach ( ( ) => {
88 vi . useRealTimers ( )
@@ -22,22 +22,98 @@ async function startTimer(fn: AnyFunction, interval: number, count: number): Pro
2222 await pDelay ( 2000 ) // extra wait
2323}
2424
25- test ( '_debounce' , async ( ) => {
26- vi . useFakeTimers ( )
25+ describe ( '_debounce' , ( ) => {
26+ test ( 'should debounce calls with leading/trailing/maxWait' , async ( ) => {
27+ vi . useFakeTimers ( )
2728
28- const fn = _debounce ( originalFn , 20 , { leading : true , trailing : true , maxWait : 300 } )
29+ const fn = _debounce ( originalFn , 20 , { leading : true , trailing : true , maxWait : 300 } )
2930
30- const promise = startTimer ( fn , 10 , 10 )
31- await vi . runAllTimersAsync ( )
32- await promise
31+ const promise = startTimer ( fn , 10 , 10 )
32+ await vi . runAllTimersAsync ( )
33+ await promise
34+ } )
35+
36+ // Test cases:
37+ // _debounce leading=1 trailing=0 (default)
38+ // _debounce leading=1 trailing=1
39+ // _debounce leading=0 trailing=1
40+ // _debounce leading=0 trailing=0
41+ // _throttle leading=1 trailing=1 (default)
42+ // _throttle leading=1 trailing=0
43+ // _throttle leading=0 trailing=1
44+ // _throttle leading=0 trailing=0
45+ } )
46+
47+ describe ( '_asyncDebounce' , ( ) => {
48+ test ( 'should return a real promise (never undefined) resolving with the invocation result' , async ( ) => {
49+ let calls = 0
50+ const fn = _asyncDebounce ( async ( n : number ) => {
51+ calls ++
52+ return n * 2
53+ } , 20 )
54+
55+ const p = fn ( 5 )
56+ expect ( p ) . toBeInstanceOf ( Promise )
57+ expect ( await p ) . toBe ( 10 )
58+ expect ( calls ) . toBe ( 1 )
59+ } )
60+
61+ test ( 'should coalesce rapid calls into one invocation and resolve all callers with that result' , async ( ) => {
62+ let calls = 0
63+ const fn = _asyncDebounce ( async ( n : number ) => {
64+ calls ++
65+ return n
66+ } , 20 )
67+
68+ const results = await Promise . all ( [ fn ( 1 ) , fn ( 2 ) , fn ( 3 ) ] )
69+ expect ( results ) . toEqual ( [ 3 , 3 , 3 ] )
70+ expect ( calls ) . toBe ( 1 )
71+ } )
72+
73+ test ( 'should reject all coalesced callers when the invocation throws' , async ( ) => {
74+ const fn = _asyncDebounce ( async ( ) => {
75+ throw new Error ( 'boom' )
76+ } , 20 )
77+
78+ const p1 = fn ( )
79+ const p2 = fn ( )
80+
81+ await expect ( p1 ) . rejects . toThrow ( 'boom' )
82+ await expect ( p2 ) . rejects . toThrow ( 'boom' )
83+ } )
84+
85+ test ( 'should reject pending promises on cancel()' , async ( ) => {
86+ const fn = _asyncDebounce ( async ( n : number ) => n , 50 )
87+
88+ const p = fn ( 1 )
89+ fn . cancel ( )
90+
91+ await expect ( p ) . rejects . toThrow ( 'asyncDebounce cancelled' )
92+ } )
93+
94+ test ( 'should resolve dropped calls with undefined when the config yields no invocation (trailing: false)' , async ( ) => {
95+ const fn = _asyncDebounce ( async ( n : number ) => n , 20 , { trailing : false } )
96+
97+ expect ( await fn ( 1 ) ) . toBeUndefined ( )
98+ } )
3399} )
34100
35- // Test cases:
36- // _debounce leading=1 trailing=0 (default)
37- // _debounce leading=1 trailing=1
38- // _debounce leading=0 trailing=1
39- // _debounce leading=0 trailing=0
40- // _throttle leading=1 trailing=1 (default)
41- // _throttle leading=1 trailing=0
42- // _throttle leading=0 trailing=1
43- // _throttle leading=0 trailing=0
101+ describe ( '_asyncThrottle' , ( ) => {
102+ test ( 'should invoke on the leading edge immediately' , async ( ) => {
103+ let calls = 0
104+ const fn = _asyncThrottle ( async ( n : number ) => {
105+ calls ++
106+ return n
107+ } , 50 )
108+
109+ expect ( await fn ( 7 ) ) . toBe ( 7 )
110+ expect ( calls ) . toBe ( 1 )
111+ } )
112+
113+ test ( 'should resolve the leading caller but resolve in-window callers with undefined when trailing is disabled' , async ( ) => {
114+ const fn = _asyncThrottle ( async ( n : number ) => n , 50 , { trailing : false } )
115+
116+ expect ( await fn ( 1 ) ) . toBe ( 1 )
117+ expect ( await fn ( 2 ) ) . toBeUndefined ( )
118+ } )
119+ } )
0 commit comments