1+ import { renderHook , act } from '@testing-library/react' ;
2+ import { useDebounceValue } from '../src/utils/useDebounce' ;
3+ import { jest , afterEach , beforeEach , describe , expect , test } from "@jest/globals" ;
4+
5+ describe ( 'useDebounceValue' , ( ) => {
6+ beforeEach ( ( ) => {
7+ jest . useFakeTimers ( ) ;
8+ } ) ;
9+
10+ afterEach ( ( ) => {
11+ jest . useRealTimers ( ) ;
12+ } ) ;
13+
14+ test ( 'should return the initial value immediately' , ( ) => {
15+ const initialValue = 'initial' ;
16+ const { result } = renderHook ( ( ) => useDebounceValue ( initialValue , 500 ) ) ;
17+
18+ expect ( result . current ) . toBe ( initialValue ) ;
19+ } ) ;
20+
21+ test ( 'should delay updating the value until after the specified interval' , ( ) => {
22+ const initialValue = 'initial' ;
23+ const { result, rerender } = renderHook (
24+ ( { value, interval } ) => useDebounceValue ( value , interval ) ,
25+ { initialProps : { value : initialValue , interval : 500 } }
26+ ) ;
27+
28+ expect ( result . current ) . toBe ( initialValue ) ;
29+
30+ // Change the value
31+ const newValue = 'updated' ;
32+ rerender ( { value : newValue , interval : 500 } ) ;
33+
34+ // Value should not have changed yet
35+ expect ( result . current ) . toBe ( initialValue ) ;
36+
37+ // Fast-forward time by 499ms (just before the debounce interval)
38+ act ( ( ) => {
39+ jest . advanceTimersByTime ( 499 ) ;
40+ } ) ;
41+
42+ // Value should still be the initial value
43+ expect ( result . current ) . toBe ( initialValue ) ;
44+
45+ // Fast-forward the remaining 1ms to reach the debounce interval
46+ act ( ( ) => {
47+ jest . advanceTimersByTime ( 1 ) ;
48+ } ) ;
49+
50+ // Value should now be updated
51+ expect ( result . current ) . toBe ( newValue ) ;
52+ } ) ;
53+
54+ test ( 'should reset the debounce timer when the value changes again' , ( ) => {
55+ const initialValue = 'initial' ;
56+ const { result, rerender } = renderHook (
57+ ( { value, interval } ) => useDebounceValue ( value , interval ) ,
58+ { initialProps : { value : initialValue , interval : 500 } }
59+ ) ;
60+
61+ // Change the value once
62+ rerender ( { value : 'intermediate' , interval : 500 } ) ;
63+
64+ // Fast-forward time by 250ms (halfway through debounce interval)
65+ act ( ( ) => {
66+ jest . advanceTimersByTime ( 250 ) ;
67+ } ) ;
68+
69+ // Value should still be initial
70+ expect ( result . current ) . toBe ( initialValue ) ;
71+
72+ // Change the value again
73+ rerender ( { value : 'final' , interval : 500 } ) ;
74+
75+ // Fast-forward another 250ms (would reach the first interval, but timer was reset)
76+ act ( ( ) => {
77+ jest . advanceTimersByTime ( 250 ) ;
78+ } ) ;
79+
80+ // Value should still be initial because timer was reset
81+ expect ( result . current ) . toBe ( initialValue ) ;
82+
83+ // Fast-forward to reach the new timer completion
84+ act ( ( ) => {
85+ jest . advanceTimersByTime ( 250 ) ;
86+ } ) ;
87+
88+ // Value should now be the final value
89+ expect ( result . current ) . toBe ( 'final' ) ;
90+ } ) ;
91+
92+ test ( 'should respect the new interval when interval changes' , ( ) => {
93+ const initialValue = 'initial' ;
94+ const { result, rerender } = renderHook (
95+ ( { value, interval } ) => useDebounceValue ( value , interval ) ,
96+ { initialProps : { value : initialValue , interval : 500 } }
97+ ) ;
98+
99+ // Change value and interval
100+ rerender ( { value : 'updated' , interval : 1000 } ) ;
101+
102+ // Fast-forward by 500ms (the original interval)
103+ act ( ( ) => {
104+ jest . advanceTimersByTime ( 500 ) ;
105+ } ) ;
106+
107+ // Value should still be initial because new interval is 1000ms
108+ expect ( result . current ) . toBe ( initialValue ) ;
109+
110+ // Fast-forward by another 500ms to reach new 1000ms interval
111+ act ( ( ) => {
112+ jest . advanceTimersByTime ( 500 ) ;
113+ } ) ;
114+
115+ // Value should now be updated
116+ expect ( result . current ) . toBe ( 'updated' ) ;
117+ } ) ;
118+
119+ test ( 'should work with different data types' , ( ) => {
120+ // Test with object
121+ const initialObject = { name : 'John' } ;
122+ const { result : objectResult , rerender : objectRerender } = renderHook (
123+ ( { value, interval } ) => useDebounceValue ( value , interval ) ,
124+ { initialProps : { value : initialObject , interval : 200 } }
125+ ) ;
126+
127+ const newObject = { name : 'Jane' } ;
128+ objectRerender ( { value : newObject , interval : 200 } ) ;
129+
130+ act ( ( ) => {
131+ jest . advanceTimersByTime ( 200 ) ;
132+ } ) ;
133+
134+ expect ( objectResult . current ) . toEqual ( newObject ) ;
135+
136+ // Test with number
137+ const { result : numberResult , rerender : numberRerender } = renderHook (
138+ ( { value, interval } ) => useDebounceValue ( value , interval ) ,
139+ { initialProps : { value : 1 , interval : 200 } }
140+ ) ;
141+
142+ numberRerender ( { value : 2 , interval : 200 } ) ;
143+
144+ act ( ( ) => {
145+ jest . advanceTimersByTime ( 200 ) ;
146+ } ) ;
147+
148+ expect ( numberResult . current ) . toBe ( 2 ) ;
149+ } ) ;
150+
151+ test ( 'should clean up timeout on unmount' , ( ) => {
152+ const clearTimeoutSpy = jest . spyOn ( window , 'clearTimeout' ) ;
153+ const { unmount } = renderHook ( ( ) => useDebounceValue ( 'test' , 500 ) ) ;
154+
155+ unmount ( ) ;
156+
157+ expect ( clearTimeoutSpy ) . toHaveBeenCalled ( ) ;
158+ } ) ;
159+ } ) ;
0 commit comments