@@ -12,13 +12,7 @@ afterEach(() => {
1212 console . error = originalConsoleError ;
1313} ) ;
1414
15- function useSuspendingHook ( promise : Promise < string > ) {
16- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17- // @ts -ignore: React 18 does not have `use` hook
18- return React . use ( promise ) ;
19- }
20-
21- test ( 'gives committed result' , async ( ) => {
15+ test ( 'renders hook and waits for effects to complete' , async ( ) => {
2216 const { result } = await renderHook ( ( ) => {
2317 const [ state , setState ] = React . useState ( 1 ) ;
2418
@@ -32,7 +26,41 @@ test('gives committed result', async () => {
3226 expect ( result . current ) . toEqual ( [ 2 , expect . any ( Function ) ] ) ;
3327} ) ;
3428
35- test ( 'allows rerendering' , async ( ) => {
29+ test ( 'handles multiple state updates in single effect' , async ( ) => {
30+ function useTestHook ( ) {
31+ const [ first , setFirst ] = React . useState ( 1 ) ;
32+ const [ second , setSecond ] = React . useState ( 2 ) ;
33+
34+ React . useEffect ( ( ) => {
35+ setFirst ( 10 ) ;
36+ setSecond ( 20 ) ;
37+ } , [ ] ) ;
38+
39+ return { first, second } ;
40+ }
41+
42+ const { result } = await renderHook ( useTestHook ) ;
43+ expect ( result . current ) . toEqual ( { first : 10 , second : 20 } ) ;
44+ } ) ;
45+
46+ test ( 'renders hook with initialProps' , async ( ) => {
47+ function useTestHook ( props : { value : number } ) {
48+ const [ state , setState ] = React . useState ( props . value ) ;
49+
50+ React . useEffect ( ( ) => {
51+ setState ( props . value * 2 ) ;
52+ } , [ props . value ] ) ;
53+
54+ return state ;
55+ }
56+
57+ const { result } = await renderHook ( useTestHook , {
58+ initialProps : { value : 5 } ,
59+ } ) ;
60+ expect ( result . current ) . toEqual ( 10 ) ;
61+ } ) ;
62+
63+ test ( 'rerenders hook with new props' , async ( ) => {
3664 const { result, rerender } = await renderHook (
3765 ( props : { branch : 'left' | 'right' } ) => {
3866 const [ left , setLeft ] = React . useState ( 'left' ) ;
@@ -57,49 +85,7 @@ test('allows rerendering', async () => {
5785 expect ( result . current ) . toEqual ( [ 'right' , expect . any ( Function ) ] ) ;
5886} ) ;
5987
60- test ( 'allows wrapper components' , async ( ) => {
61- const Context = React . createContext ( 'default' ) ;
62- function Wrapper ( { children } : { children : ReactNode } ) {
63- return < Context . Provider value = "provided" > { children } </ Context . Provider > ;
64- }
65- const { result } = await renderHook (
66- ( ) => {
67- return React . useContext ( Context ) ;
68- } ,
69- {
70- wrapper : Wrapper ,
71- } ,
72- ) ;
73-
74- expect ( result . current ) . toEqual ( 'provided' ) ;
75- } ) ;
76-
77- function useMyHook < T > ( param : T ) {
78- return { param } ;
79- }
80-
81- test ( 'props type is inferred correctly when initial props is defined' , async ( ) => {
82- const { result, rerender } = await renderHook ( ( num : number ) => useMyHook ( num ) , {
83- initialProps : 5 ,
84- } ) ;
85- expect ( result . current . param ) . toBe ( 5 ) ;
86-
87- await rerender ( 6 ) ;
88- expect ( result . current . param ) . toBe ( 6 ) ;
89- } ) ;
90-
91- test ( 'props type is inferred correctly when initial props is explicitly undefined' , async ( ) => {
92- const { result, rerender } = await renderHook ( ( num : number | undefined ) => useMyHook ( num ) , {
93- initialProps : undefined ,
94- } ) ;
95-
96- expect ( result . current . param ) . toBeUndefined ( ) ;
97-
98- await rerender ( 6 ) ;
99- expect ( result . current . param ) . toBe ( 6 ) ;
100- } ) ;
101-
102- test ( 'rerender function updates hook asynchronously' , async ( ) => {
88+ test ( 'rerender updates hook state based on props' , async ( ) => {
10389 function useTestHook ( props : { value : number } ) {
10490 const [ state , setState ] = React . useState ( props . value ) ;
10591
@@ -119,7 +105,24 @@ test('rerender function updates hook asynchronously', async () => {
119105 expect ( result . current ) . toEqual ( 20 ) ;
120106} ) ;
121107
122- test ( 'unmount function unmounts hook asynchronously' , async ( ) => {
108+ test ( 'supports wrapper option for context providers' , async ( ) => {
109+ const Context = React . createContext ( 'default' ) ;
110+ function Wrapper ( { children } : { children : ReactNode } ) {
111+ return < Context . Provider value = "provided" > { children } </ Context . Provider > ;
112+ }
113+ const { result } = await renderHook (
114+ ( ) => {
115+ return React . useContext ( Context ) ;
116+ } ,
117+ {
118+ wrapper : Wrapper ,
119+ } ,
120+ ) ;
121+
122+ expect ( result . current ) . toEqual ( 'provided' ) ;
123+ } ) ;
124+
125+ test ( 'unmount triggers cleanup effects' , async ( ) => {
123126 let cleanupCalled = false ;
124127
125128 function useTestHook ( ) {
@@ -139,24 +142,75 @@ test('unmount function unmounts hook asynchronously', async () => {
139142 expect ( cleanupCalled ) . toBe ( true ) ;
140143} ) ;
141144
142- test ( 'handles multiple state updates in effects' , async ( ) => {
143- function useTestHook ( ) {
144- const [ first , setFirst ] = React . useState ( 1 ) ;
145- const [ second , setSecond ] = React . useState ( 2 ) ;
145+ test ( 'handles cleanup effects on rerender and unmount' , async ( ) => {
146+ let effectCount = 0 ;
147+ let cleanupCount = 0 ;
148+
149+ function useTestHook ( props : { key : string } ) {
150+ const [ value , setValue ] = React . useState ( props . key ) ;
146151
147152 React . useEffect ( ( ) => {
148- setFirst ( 10 ) ;
149- setSecond ( 20 ) ;
150- } , [ ] ) ;
153+ effectCount ++ ;
154+ setValue ( `${ props . key } -effect` ) ;
151155
152- return { first, second } ;
156+ return ( ) => {
157+ cleanupCount ++ ;
158+ } ;
159+ } , [ props . key ] ) ;
160+
161+ return value ;
153162 }
154163
155- const { result } = await renderHook ( useTestHook ) ;
156- expect ( result . current ) . toEqual ( { first : 10 , second : 20 } ) ;
164+ const { result, rerender, unmount } = await renderHook ( useTestHook , {
165+ initialProps : { key : 'initial' } ,
166+ } ) ;
167+
168+ expect ( result . current ) . toBe ( 'initial-effect' ) ;
169+ expect ( effectCount ) . toBe ( 1 ) ;
170+ expect ( cleanupCount ) . toBe ( 0 ) ;
171+
172+ await rerender ( { key : 'updated' } ) ;
173+ expect ( result . current ) . toBe ( 'updated-effect' ) ;
174+ expect ( effectCount ) . toBe ( 2 ) ;
175+ expect ( cleanupCount ) . toBe ( 1 ) ;
176+
177+ await unmount ( ) ;
178+ expect ( effectCount ) . toBe ( 2 ) ;
179+ expect ( cleanupCount ) . toBe ( 2 ) ;
180+ } ) ;
181+
182+ function useMyHook < T > ( param : T ) {
183+ return { param } ;
184+ }
185+
186+ test ( 'infers props type from initialProps' , async ( ) => {
187+ const { result, rerender } = await renderHook ( ( num : number ) => useMyHook ( num ) , {
188+ initialProps : 5 ,
189+ } ) ;
190+ expect ( result . current . param ) . toBe ( 5 ) ;
191+
192+ await rerender ( 6 ) ;
193+ expect ( result . current . param ) . toBe ( 6 ) ;
157194} ) ;
158195
159- test ( 'handles hook with suspense' , async ( ) => {
196+ test ( 'infers props type when initialProps is undefined' , async ( ) => {
197+ const { result, rerender } = await renderHook ( ( num : number | undefined ) => useMyHook ( num ) , {
198+ initialProps : undefined ,
199+ } ) ;
200+
201+ expect ( result . current . param ) . toBeUndefined ( ) ;
202+
203+ await rerender ( 6 ) ;
204+ expect ( result . current . param ) . toBe ( 6 ) ;
205+ } ) ;
206+
207+ function useSuspendingHook ( promise : Promise < string > ) {
208+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
209+ // @ts -ignore: React 18 does not have `use` hook
210+ return React . use ( promise ) ;
211+ }
212+
213+ test ( 'supports React Suspense' , async ( ) => {
160214 let resolvePromise : ( value : string ) => void ;
161215 const promise = new Promise < string > ( ( resolve ) => {
162216 resolvePromise = resolve ;
@@ -195,7 +249,7 @@ class ErrorBoundary extends React.Component<
195249 }
196250}
197251
198- test ( 'handles hook suspense with error boundary' , async ( ) => {
252+ test ( 'handles Suspense errors with error boundary' , async ( ) => {
199253 const ERROR_MESSAGE = 'Hook Promise Rejected In Test' ;
200254 // eslint-disable-next-line no-console
201255 console . error = excludeConsoleMessage ( console . error , ERROR_MESSAGE ) ;
@@ -224,7 +278,7 @@ test('handles hook suspense with error boundary', async () => {
224278 expect ( result . current ) . toBeNull ( ) ;
225279} ) ;
226280
227- test ( 'handles custom hooks with complex logic ' , async ( ) => {
281+ test ( 'handles hooks with callbacks and complex state ' , async ( ) => {
228282 function useCounter ( initialValue : number ) {
229283 const [ count , setCount ] = React . useState ( initialValue ) ;
230284
@@ -264,40 +318,3 @@ test('handles custom hooks with complex logic', async () => {
264318 } ) ;
265319 expect ( result . current . count ) . toBe ( 4 ) ;
266320} ) ;
267-
268- test ( 'handles hook with cleanup and re-initialization' , async ( ) => {
269- let effectCount = 0 ;
270- let cleanupCount = 0 ;
271-
272- function useTestHook ( props : { key : string } ) {
273- const [ value , setValue ] = React . useState ( props . key ) ;
274-
275- React . useEffect ( ( ) => {
276- effectCount ++ ;
277- setValue ( `${ props . key } -effect` ) ;
278-
279- return ( ) => {
280- cleanupCount ++ ;
281- } ;
282- } , [ props . key ] ) ;
283-
284- return value ;
285- }
286-
287- const { result, rerender, unmount } = await renderHook ( useTestHook , {
288- initialProps : { key : 'initial' } ,
289- } ) ;
290-
291- expect ( result . current ) . toBe ( 'initial-effect' ) ;
292- expect ( effectCount ) . toBe ( 1 ) ;
293- expect ( cleanupCount ) . toBe ( 0 ) ;
294-
295- await rerender ( { key : 'updated' } ) ;
296- expect ( result . current ) . toBe ( 'updated-effect' ) ;
297- expect ( effectCount ) . toBe ( 2 ) ;
298- expect ( cleanupCount ) . toBe ( 1 ) ;
299-
300- await unmount ( ) ;
301- expect ( effectCount ) . toBe ( 2 ) ;
302- expect ( cleanupCount ) . toBe ( 2 ) ;
303- } ) ;
0 commit comments