@@ -3,14 +3,20 @@ import { act, renderHook, waitFor } from '@testing-library/react'
3
3
import { baseErrorMap , inputSchema , outputSchema } from '../../../contract/tests/shared'
4
4
import { useServerAction } from './action-hooks'
5
5
6
+ beforeEach ( ( ) => {
7
+ vi . clearAllMocks ( )
8
+ } )
9
+
6
10
describe ( 'useServerAction' , ( ) => {
11
+ const handler = vi . fn ( async ( { input } ) => {
12
+ return { output : Number ( input ?. input ?? 0 ) }
13
+ } )
14
+
7
15
const action = os
8
16
. input ( inputSchema . optional ( ) )
9
17
. errors ( baseErrorMap )
10
18
. output ( outputSchema )
11
- . handler ( async ( { input } ) => {
12
- return { output : Number ( input ?. input ?? 0 ) }
13
- } )
19
+ . handler ( handler )
14
20
. actionable ( )
15
21
16
22
it ( 'on success' , async ( ) => {
@@ -110,6 +116,57 @@ describe('useServerAction', () => {
110
116
expect ( result . current . error ) . toBe ( null )
111
117
} )
112
118
119
+ it ( 'on action calling error' , async ( ) => {
120
+ const { result } = renderHook ( ( ) => useServerAction ( ( ) => {
121
+ throw new Error ( 'failed to call' )
122
+ } ) )
123
+
124
+ expect ( result . current . status ) . toBe ( 'idle' )
125
+ expect ( result . current . isIdle ) . toBe ( true )
126
+ expect ( result . current . isPending ) . toBe ( false )
127
+ expect ( result . current . isSuccess ) . toBe ( false )
128
+ expect ( result . current . isError ) . toBe ( false )
129
+ expect ( result . current . input ) . toBe ( undefined )
130
+ expect ( result . current . data ) . toBe ( undefined )
131
+ expect ( result . current . error ) . toBe ( null )
132
+
133
+ act ( ( ) => {
134
+ result . current . execute ( { input : 123 } )
135
+ } )
136
+
137
+ expect ( result . current . status ) . toBe ( 'pending' )
138
+ expect ( result . current . isIdle ) . toBe ( false )
139
+ expect ( result . current . isPending ) . toBe ( true )
140
+ expect ( result . current . isSuccess ) . toBe ( false )
141
+ expect ( result . current . isError ) . toBe ( false )
142
+ expect ( result . current . input ) . toEqual ( { input : 123 } )
143
+ expect ( result . current . data ) . toBe ( undefined )
144
+ expect ( result . current . error ) . toBe ( null )
145
+
146
+ await waitFor ( ( ) => expect ( result . current . status ) . toBe ( 'error' ) )
147
+ expect ( result . current . isIdle ) . toBe ( false )
148
+ expect ( result . current . isPending ) . toBe ( false )
149
+ expect ( result . current . isSuccess ) . toBe ( false )
150
+ expect ( result . current . isError ) . toBe ( true )
151
+ expect ( result . current . input ) . toEqual ( { input : 123 } )
152
+ expect ( result . current . data ) . toBe ( undefined )
153
+ expect ( result . current . error ) . toBeInstanceOf ( Error )
154
+ expect ( result . current . error ! . message ) . toBe ( 'failed to call' )
155
+
156
+ act ( ( ) => {
157
+ result . current . reset ( )
158
+ } )
159
+
160
+ expect ( result . current . status ) . toBe ( 'idle' )
161
+ expect ( result . current . isIdle ) . toBe ( true )
162
+ expect ( result . current . isPending ) . toBe ( false )
163
+ expect ( result . current . isSuccess ) . toBe ( false )
164
+ expect ( result . current . isError ) . toBe ( false )
165
+ expect ( result . current . input ) . toBe ( undefined )
166
+ expect ( result . current . data ) . toBe ( undefined )
167
+ expect ( result . current . error ) . toBe ( null )
168
+ } )
169
+
113
170
it ( 'interceptors' , async ( ) => {
114
171
const interceptor = vi . fn ( ( { next } ) => next ( ) )
115
172
const executeInterceptor = vi . fn ( ( { next } ) => next ( ) )
@@ -150,4 +207,107 @@ describe('useServerAction', () => {
150
207
expect ( await executeInterceptor . mock . results [ 0 ] ! . value ) . toEqual ( { output : '123' } )
151
208
} )
152
209
} )
210
+
211
+ it ( 'multiple execute calls' , async ( ) => {
212
+ const { result } = renderHook ( ( ) => useServerAction ( action ) )
213
+
214
+ expect ( result . current . status ) . toBe ( 'idle' )
215
+
216
+ handler . mockImplementationOnce ( async ( ) => {
217
+ await new Promise ( resolve => setTimeout ( resolve , 20 ) )
218
+ return { output : 123 }
219
+ } )
220
+
221
+ let promise : Promise < any >
222
+
223
+ act ( ( ) => {
224
+ promise = result . current . execute ( { input : 123 } )
225
+ } )
226
+
227
+ expect ( result . current . status ) . toBe ( 'pending' )
228
+ expect ( result . current . executedAt ) . toBeDefined ( )
229
+ expect ( result . current . input ) . toEqual ( { input : 123 } )
230
+ expect ( result . current . data ) . toBeUndefined ( )
231
+ expect ( result . current . error ) . toBeNull ( )
232
+
233
+ handler . mockImplementationOnce ( async ( ) => {
234
+ await new Promise ( resolve => setTimeout ( resolve , 40 ) )
235
+ return { output : 456 }
236
+ } )
237
+
238
+ let promise2 : Promise < any >
239
+
240
+ act ( ( ) => {
241
+ promise2 = result . current . execute ( { input : 456 } )
242
+ } )
243
+
244
+ expect ( result . current . status ) . toBe ( 'pending' )
245
+ expect ( result . current . executedAt ) . toBeDefined ( )
246
+ expect ( result . current . input ) . toEqual ( { input : 456 } )
247
+ expect ( result . current . data ) . toBeUndefined ( )
248
+ expect ( result . current . error ) . toBeNull ( )
249
+
250
+ await act ( async ( ) => {
251
+ expect ( ( await promise ! ) [ 1 ] ) . toEqual ( { output : '123' } )
252
+ } )
253
+
254
+ expect ( result . current . status ) . toBe ( 'pending' )
255
+ expect ( result . current . executedAt ) . toBeDefined ( )
256
+ expect ( result . current . input ) . toEqual ( { input : 456 } )
257
+ expect ( result . current . data ) . toBeUndefined ( )
258
+ expect ( result . current . error ) . toBeNull ( )
259
+
260
+ await act ( async ( ) => {
261
+ expect ( ( await promise2 ! ) [ 1 ] ) . toEqual ( { output : '456' } )
262
+ } )
263
+
264
+ expect ( result . current . status ) . toBe ( 'success' )
265
+ expect ( result . current . executedAt ) . toBeDefined ( )
266
+ expect ( result . current . input ) . toEqual ( { input : 456 } )
267
+ expect ( result . current . data ) . toEqual ( { output : '456' } )
268
+ expect ( result . current . error ) . toBeNull ( )
269
+ } )
270
+
271
+ it ( 'reset while executing' , async ( ) => {
272
+ const { result } = renderHook ( ( ) => useServerAction ( action ) )
273
+
274
+ expect ( result . current . status ) . toBe ( 'idle' )
275
+
276
+ handler . mockImplementationOnce ( async ( ) => {
277
+ await new Promise ( resolve => setTimeout ( resolve , 20 ) )
278
+ return { output : 123 }
279
+ } )
280
+
281
+ let promise : Promise < any >
282
+
283
+ act ( ( ) => {
284
+ promise = result . current . execute ( { input : 123 } )
285
+ } )
286
+
287
+ expect ( result . current . status ) . toBe ( 'pending' )
288
+ expect ( result . current . executedAt ) . toBeDefined ( )
289
+ expect ( result . current . input ) . toEqual ( { input : 123 } )
290
+ expect ( result . current . data ) . toBeUndefined ( )
291
+ expect ( result . current . error ) . toBeNull ( )
292
+
293
+ act ( ( ) => {
294
+ result . current . reset ( )
295
+ } )
296
+
297
+ expect ( result . current . status ) . toBe ( 'idle' )
298
+ expect ( result . current . executedAt ) . toBeUndefined ( )
299
+ expect ( result . current . input ) . toBeUndefined ( )
300
+ expect ( result . current . data ) . toBeUndefined ( )
301
+ expect ( result . current . error ) . toBeNull ( )
302
+
303
+ await act ( async ( ) => {
304
+ expect ( ( await promise ! ) [ 1 ] ) . toEqual ( { output : '123' } )
305
+ } )
306
+
307
+ expect ( result . current . status ) . toBe ( 'idle' )
308
+ expect ( result . current . executedAt ) . toBeUndefined ( )
309
+ expect ( result . current . input ) . toBeUndefined ( )
310
+ expect ( result . current . data ) . toBeUndefined ( )
311
+ expect ( result . current . error ) . toBeNull ( )
312
+ } )
153
313
} )
0 commit comments