source: node_modules/@reduxjs/toolkit/src/tests/createAsyncThunk.test-d.ts@ ba17441

Last change on this file since ba17441 was a762898, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Added visualizations

  • Property mode set to 100644
File size: 27.2 KB
Line 
1import type {
2 AsyncThunk,
3 SerializedError,
4 ThunkDispatch,
5 UnknownAction,
6} from '@reduxjs/toolkit'
7import {
8 configureStore,
9 createAsyncThunk,
10 createReducer,
11 createSlice,
12 unwrapResult,
13} from '@reduxjs/toolkit'
14
15import type { TSVersion } from '@phryneas/ts-version'
16import type { AxiosError } from 'axios'
17import apiRequest from 'axios'
18import type { AsyncThunkDispatchConfig } from '@internal/createAsyncThunk'
19
20const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, UnknownAction>
21const unknownAction = { type: 'foo' } as UnknownAction
22
23describe('type tests', () => {
24 test('basic usage', async () => {
25 const asyncThunk = createAsyncThunk('test', (id: number) =>
26 Promise.resolve(id * 2),
27 )
28
29 const reducer = createReducer({}, (builder) =>
30 builder
31 .addCase(asyncThunk.pending, (_, action) => {
32 expectTypeOf(action).toEqualTypeOf<
33 ReturnType<(typeof asyncThunk)['pending']>
34 >()
35 })
36
37 .addCase(asyncThunk.fulfilled, (_, action) => {
38 expectTypeOf(action).toEqualTypeOf<
39 ReturnType<(typeof asyncThunk)['fulfilled']>
40 >()
41
42 expectTypeOf(action.payload).toBeNumber()
43 })
44
45 .addCase(asyncThunk.rejected, (_, action) => {
46 expectTypeOf(action).toEqualTypeOf<
47 ReturnType<(typeof asyncThunk)['rejected']>
48 >()
49
50 expectTypeOf(action.error).toMatchTypeOf<Partial<Error> | undefined>()
51 }),
52 )
53
54 const promise = defaultDispatch(asyncThunk(3))
55
56 expectTypeOf(promise.requestId).toBeString()
57
58 expectTypeOf(promise.arg).toBeNumber()
59
60 expectTypeOf(promise.abort).toEqualTypeOf<(reason?: string) => void>()
61
62 const result = await promise
63
64 if (asyncThunk.fulfilled.match(result)) {
65 expectTypeOf(result).toEqualTypeOf<
66 ReturnType<(typeof asyncThunk)['fulfilled']>
67 >()
68 } else {
69 expectTypeOf(result).toEqualTypeOf<
70 ReturnType<(typeof asyncThunk)['rejected']>
71 >()
72 }
73
74 promise
75 .then(unwrapResult)
76 .then((result) => {
77 expectTypeOf(result).toBeNumber()
78
79 expectTypeOf(result).not.toMatchTypeOf<Error>()
80 })
81 .catch((error) => {
82 // catch is always any-typed, nothing we can do here
83 expectTypeOf(error).toBeAny()
84 })
85 })
86
87 test('More complex usage of thunk args', () => {
88 interface BookModel {
89 id: string
90 title: string
91 }
92
93 type BooksState = BookModel[]
94
95 const fakeBooks: BookModel[] = [
96 { id: 'b', title: 'Second' },
97 { id: 'a', title: 'First' },
98 ]
99
100 const correctDispatch = (() => {}) as ThunkDispatch<
101 BookModel[],
102 { userAPI: Function },
103 UnknownAction
104 >
105
106 // Verify that the the first type args to createAsyncThunk line up right
107 const fetchBooksTAC = createAsyncThunk<
108 BookModel[],
109 number,
110 {
111 state: BooksState
112 extra: { userAPI: Function }
113 }
114 >(
115 'books/fetch',
116 async (arg, { getState, dispatch, extra, requestId, signal }) => {
117 const state = getState()
118
119 expectTypeOf(arg).toBeNumber()
120
121 expectTypeOf(state).toEqualTypeOf<BookModel[]>()
122
123 expectTypeOf(extra).toEqualTypeOf<{ userAPI: Function }>()
124
125 return fakeBooks
126 },
127 )
128
129 correctDispatch(fetchBooksTAC(1))
130 // @ts-expect-error
131 defaultDispatch(fetchBooksTAC(1))
132 })
133
134 test('returning a rejected action from the promise creator is possible', async () => {
135 type ReturnValue = { data: 'success' }
136 type RejectValue = { data: 'error' }
137
138 const fetchBooksTAC = createAsyncThunk<
139 ReturnValue,
140 number,
141 {
142 rejectValue: RejectValue
143 }
144 >('books/fetch', async (arg, { rejectWithValue }) => {
145 return rejectWithValue({ data: 'error' })
146 })
147
148 const returned = await defaultDispatch(fetchBooksTAC(1))
149 if (fetchBooksTAC.rejected.match(returned)) {
150 expectTypeOf(returned.payload).toEqualTypeOf<undefined | RejectValue>()
151
152 expectTypeOf(returned.payload).toBeNullable()
153 } else {
154 expectTypeOf(returned.payload).toEqualTypeOf<ReturnValue>()
155 }
156
157 expectTypeOf(unwrapResult(returned)).toEqualTypeOf<ReturnValue>()
158
159 expectTypeOf(unwrapResult(returned)).not.toMatchTypeOf<RejectValue>()
160 })
161
162 test('regression #1156: union return values fall back to allowing only single member', () => {
163 const fn = createAsyncThunk('session/isAdmin', async () => {
164 const response: boolean = false
165 return response
166 })
167 })
168
169 test('Should handle reject with value within a try catch block. Note: this is a sample code taken from #1605', () => {
170 type ResultType = {
171 text: string
172 }
173 const demoPromise = async (): Promise<ResultType> =>
174 new Promise((resolve, _) => resolve({ text: '' }))
175 const thunk = createAsyncThunk('thunk', async (args, thunkAPI) => {
176 try {
177 const result = await demoPromise()
178 return result
179 } catch (error) {
180 return thunkAPI.rejectWithValue(error)
181 }
182 })
183 createReducer({}, (builder) =>
184 builder.addCase(thunk.fulfilled, (s, action) => {
185 expectTypeOf(action.payload).toEqualTypeOf<ResultType>()
186 }),
187 )
188 })
189
190 test('reject with value', () => {
191 interface Item {
192 name: string
193 }
194
195 interface ErrorFromServer {
196 error: string
197 }
198
199 interface CallsResponse {
200 data: Item[]
201 }
202
203 const fetchLiveCallsError = createAsyncThunk<
204 Item[],
205 string,
206 {
207 rejectValue: ErrorFromServer
208 }
209 >('calls/fetchLiveCalls', async (organizationId, { rejectWithValue }) => {
210 try {
211 const result = await apiRequest.get<CallsResponse>(
212 `organizations/${organizationId}/calls/live/iwill404`,
213 )
214 return result.data.data
215 } catch (err) {
216 const error: AxiosError<ErrorFromServer> = err as any // cast for access to AxiosError properties
217 if (!error.response) {
218 // let it be handled as any other unknown error
219 throw err
220 }
221 return rejectWithValue(error.response && error.response.data)
222 }
223 })
224
225 defaultDispatch(fetchLiveCallsError('asd')).then((result) => {
226 if (fetchLiveCallsError.fulfilled.match(result)) {
227 //success
228 expectTypeOf(result).toEqualTypeOf<
229 ReturnType<(typeof fetchLiveCallsError)['fulfilled']>
230 >()
231
232 expectTypeOf(result.payload).toEqualTypeOf<Item[]>()
233 } else {
234 expectTypeOf(result).toEqualTypeOf<
235 ReturnType<(typeof fetchLiveCallsError)['rejected']>
236 >()
237
238 if (result.payload) {
239 // rejected with value
240 expectTypeOf(result.payload).toEqualTypeOf<ErrorFromServer>()
241 } else {
242 // rejected by throw
243 expectTypeOf(result.payload).toBeUndefined()
244
245 expectTypeOf(result.error).toEqualTypeOf<SerializedError>()
246
247 expectTypeOf(result.error).not.toBeAny()
248 }
249 }
250 defaultDispatch(fetchLiveCallsError('asd'))
251 .then((result) => {
252 expectTypeOf(result.payload).toEqualTypeOf<
253 Item[] | ErrorFromServer | undefined
254 >()
255
256 return result
257 })
258 .then(unwrapResult)
259 .then((unwrapped) => {
260 expectTypeOf(unwrapped).toEqualTypeOf<Item[]>()
261
262 expectTypeOf(unwrapResult).parameter(0).not.toMatchTypeOf(unwrapped)
263 })
264 })
265 })
266
267 describe('payloadCreator first argument type has impact on asyncThunk argument', () => {
268 test('asyncThunk has no argument', () => {
269 const asyncThunk = createAsyncThunk('test', () => 0)
270
271 expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
272
273 expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
274 [undefined?, AsyncThunkDispatchConfig?]
275 >()
276
277 expectTypeOf(asyncThunk).returns.toBeFunction()
278 })
279
280 test('one argument, specified as undefined: asyncThunk has no argument', () => {
281 const asyncThunk = createAsyncThunk('test', (arg: undefined) => 0)
282
283 expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
284
285 expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
286 [undefined?, AsyncThunkDispatchConfig?]
287 >()
288 })
289
290 test('one argument, specified as void: asyncThunk has no argument', () => {
291 const asyncThunk = createAsyncThunk('test', (arg: void) => 0)
292
293 expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
294 })
295
296 test('one argument, specified as optional number: asyncThunk has optional number argument', () => {
297 // this test will fail with strictNullChecks: false, that is to be expected
298 // in that case, we have to forbid this behavior or it will make arguments optional everywhere
299 const asyncThunk = createAsyncThunk('test', (arg?: number) => 0)
300
301 // Per https://github.com/reduxjs/redux-toolkit/issues/3758#issuecomment-1742152774 , this is a bug in
302 // TS 5.1 and 5.2, that is fixed in 5.3. Conditionally run the TS assertion here.
303 type IsTS51Or52 = TSVersion.Major extends 5
304 ? TSVersion.Minor extends 1 | 2
305 ? true
306 : false
307 : false
308
309 type expectedType = IsTS51Or52 extends true
310 ? (arg: number) => any
311 : (arg?: number) => any
312
313 expectTypeOf(asyncThunk).toMatchTypeOf<expectedType>()
314
315 // We _should_ be able to call this with no arguments, but we run into that error in 5.1 and 5.2.
316 // Disabling this for now.
317 // asyncThunk()
318 expectTypeOf(asyncThunk).toBeCallableWith(5)
319
320 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
321 })
322
323 test('one argument, specified as number|undefined: asyncThunk has optional number argument', () => {
324 // this test will fail with strictNullChecks: false, that is to be expected
325 // in that case, we have to forbid this behavior or it will make arguments optional everywhere
326 const asyncThunk = createAsyncThunk(
327 'test',
328 (arg: number | undefined) => 0,
329 )
330
331 expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
332
333 expectTypeOf(asyncThunk).toBeCallableWith()
334
335 expectTypeOf(asyncThunk).toBeCallableWith(undefined)
336
337 expectTypeOf(asyncThunk).toBeCallableWith(5)
338
339 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
340 })
341
342 test('one argument, specified as number|void: asyncThunk has optional number argument', () => {
343 const asyncThunk = createAsyncThunk('test', (arg: number | void) => 0)
344
345 expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
346
347 expectTypeOf(asyncThunk).toBeCallableWith()
348
349 expectTypeOf(asyncThunk).toBeCallableWith(undefined)
350
351 expectTypeOf(asyncThunk).toBeCallableWith(5)
352
353 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
354 })
355
356 test('one argument, specified as any: asyncThunk has required any argument', () => {
357 const asyncThunk = createAsyncThunk('test', (arg: any) => 0)
358
359 expectTypeOf(asyncThunk).parameter(0).toBeAny()
360
361 expectTypeOf(asyncThunk).toBeCallableWith(5)
362
363 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
364 })
365
366 test('one argument, specified as unknown: asyncThunk has required unknown argument', () => {
367 const asyncThunk = createAsyncThunk('test', (arg: unknown) => 0)
368
369 expectTypeOf(asyncThunk).parameter(0).toBeUnknown()
370
371 expectTypeOf(asyncThunk).toBeCallableWith(5)
372
373 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
374 })
375
376 test('one argument, specified as number: asyncThunk has required number argument', () => {
377 const asyncThunk = createAsyncThunk('test', (arg: number) => 0)
378
379 expectTypeOf(asyncThunk).toMatchTypeOf<(arg: number) => any>()
380
381 expectTypeOf(asyncThunk).toBeCallableWith(5)
382
383 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
384 })
385
386 test('two arguments, first specified as undefined: asyncThunk has no argument', () => {
387 const asyncThunk = createAsyncThunk(
388 'test',
389 (arg: undefined, thunkApi) => 0,
390 )
391
392 expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
393
394 expectTypeOf(asyncThunk).toBeCallableWith()
395
396 expectTypeOf(asyncThunk).toBeCallableWith(undefined)
397
398 // cannot be called with an argument
399 expectTypeOf(asyncThunk).parameter(0).not.toBeAny()
400
401 expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
402 [undefined?, AsyncThunkDispatchConfig?]
403 >()
404 })
405
406 test('two arguments, first specified as void: asyncThunk has no argument', () => {
407 const asyncThunk = createAsyncThunk('test', (arg: void, thunkApi) => 0)
408
409 expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
410
411 expectTypeOf(asyncThunk).toBeCallableWith()
412
413 expectTypeOf(asyncThunk).parameter(0).toBeVoid()
414
415 // cannot be called with an argument
416 expectTypeOf(asyncThunk).parameter(0).not.toBeAny()
417
418 expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
419 [undefined?, AsyncThunkDispatchConfig?]
420 >()
421 })
422
423 test('two arguments, first specified as number|undefined: asyncThunk has optional number argument', () => {
424 // this test will fail with strictNullChecks: false, that is to be expected
425 // in that case, we have to forbid this behavior or it will make arguments optional everywhere
426 const asyncThunk = createAsyncThunk(
427 'test',
428 (arg: number | undefined, thunkApi) => 0,
429 )
430
431 expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
432
433 expectTypeOf(asyncThunk).toBeCallableWith()
434
435 expectTypeOf(asyncThunk).toBeCallableWith(undefined)
436
437 expectTypeOf(asyncThunk).toBeCallableWith(5)
438
439 expectTypeOf(asyncThunk).parameter(0).not.toBeString()
440 })
441
442 test('two arguments, first specified as number|void: asyncThunk has optional number argument', () => {
443 const asyncThunk = createAsyncThunk(
444 'test',
445 (arg: number | void, thunkApi) => 0,
446 )
447
448 expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
449
450 expectTypeOf(asyncThunk).toBeCallableWith()
451
452 expectTypeOf(asyncThunk).toBeCallableWith(undefined)
453
454 expectTypeOf(asyncThunk).toBeCallableWith(5)
455
456 expectTypeOf(asyncThunk).parameter(0).not.toBeString()
457 })
458
459 test('two arguments, first specified as any: asyncThunk has required any argument', () => {
460 const asyncThunk = createAsyncThunk('test', (arg: any, thunkApi) => 0)
461
462 expectTypeOf(asyncThunk).parameter(0).toBeAny()
463
464 expectTypeOf(asyncThunk).toBeCallableWith(5)
465
466 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
467 })
468
469 test('two arguments, first specified as unknown: asyncThunk has required unknown argument', () => {
470 const asyncThunk = createAsyncThunk('test', (arg: unknown, thunkApi) => 0)
471
472 expectTypeOf(asyncThunk).parameter(0).toBeUnknown()
473
474 expectTypeOf(asyncThunk).toBeCallableWith(5)
475
476 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
477 })
478
479 test('two arguments, first specified as number: asyncThunk has required number argument', () => {
480 const asyncThunk = createAsyncThunk('test', (arg: number, thunkApi) => 0)
481
482 expectTypeOf(asyncThunk).toMatchTypeOf<(arg: number) => any>()
483
484 expectTypeOf(asyncThunk).parameter(0).toBeNumber()
485
486 expectTypeOf(asyncThunk).toBeCallableWith(5)
487
488 expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
489 })
490 })
491
492 test('createAsyncThunk without generics', () => {
493 const thunk = createAsyncThunk('test', () => {
494 return 'ret' as const
495 })
496
497 expectTypeOf(thunk).toEqualTypeOf<AsyncThunk<'ret', void, {}>>()
498 })
499
500 test('createAsyncThunk without generics, accessing `api` does not break return type', () => {
501 const thunk = createAsyncThunk('test', (_: void, api) => {
502 return 'ret' as const
503 })
504
505 expectTypeOf(thunk).toEqualTypeOf<AsyncThunk<'ret', void, {}>>()
506 })
507
508 test('createAsyncThunk rejectWithValue without generics: Expect correct return type', () => {
509 const asyncThunk = createAsyncThunk(
510 'test',
511 (_: void, { rejectWithValue }) => {
512 try {
513 return Promise.resolve(true)
514 } catch (e) {
515 return rejectWithValue(e)
516 }
517 },
518 )
519
520 defaultDispatch(asyncThunk())
521 .then((result) => {
522 if (asyncThunk.fulfilled.match(result)) {
523 expectTypeOf(result).toEqualTypeOf<
524 ReturnType<(typeof asyncThunk)['fulfilled']>
525 >()
526
527 expectTypeOf(result.payload).toBeBoolean()
528
529 expectTypeOf(result).not.toHaveProperty('error')
530 } else {
531 expectTypeOf(result).toEqualTypeOf<
532 ReturnType<(typeof asyncThunk)['rejected']>
533 >()
534
535 expectTypeOf(result.error).toEqualTypeOf<SerializedError>()
536
537 expectTypeOf(result.payload).toBeUnknown()
538 }
539
540 return result
541 })
542 .then(unwrapResult)
543 .then((unwrapped) => {
544 expectTypeOf(unwrapped).toBeBoolean()
545 })
546 })
547
548 test('createAsyncThunk with generics', () => {
549 type Funky = { somethingElse: 'Funky!' }
550 function funkySerializeError(err: any): Funky {
551 return { somethingElse: 'Funky!' }
552 }
553
554 // has to stay on one line or type tests fail in older TS versions
555 // prettier-ignore
556 // @ts-expect-error
557 const shouldFail = createAsyncThunk('without generics', () => {}, { serializeError: funkySerializeError })
558
559 const shouldWork = createAsyncThunk<
560 any,
561 void,
562 { serializedErrorType: Funky }
563 >('with generics', () => {}, {
564 serializeError: funkySerializeError,
565 })
566
567 if (shouldWork.rejected.match(unknownAction)) {
568 expectTypeOf(unknownAction.error).toEqualTypeOf<Funky>()
569 }
570 })
571
572 test('`idGenerator` option takes no arguments, and returns a string', () => {
573 const returnsNumWithArgs = (foo: any) => 100
574 // has to stay on one line or type tests fail in older TS versions
575 // prettier-ignore
576 // @ts-expect-error
577 const shouldFailNumWithArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithArgs })
578
579 const returnsNumWithoutArgs = () => 100
580 // prettier-ignore
581 // @ts-expect-error
582 const shouldFailNumWithoutArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithoutArgs })
583
584 const returnsStrWithNumberArg = (foo: number) => 'foo'
585 // prettier-ignore
586 // @ts-expect-error
587 const shouldFailWrongArgs = createAsyncThunk('foo', (arg: string) => {}, { idGenerator: returnsStrWithNumberArg })
588
589 const returnsStrWithStringArg = (foo: string) => 'foo'
590 const shoulducceedCorrectArgs = createAsyncThunk(
591 'foo',
592 (arg: string) => {},
593 {
594 idGenerator: returnsStrWithStringArg,
595 },
596 )
597
598 const returnsStrWithoutArgs = () => 'foo'
599 const shouldSucceed = createAsyncThunk('foo', () => {}, {
600 idGenerator: returnsStrWithoutArgs,
601 })
602 })
603
604 test('fulfillWithValue should infer return value', () => {
605 // https://github.com/reduxjs/redux-toolkit/issues/2886
606
607 const initialState = {
608 loading: false,
609 obj: { magic: '' },
610 }
611
612 const getObj = createAsyncThunk(
613 'slice/getObj',
614 async (_: any, { fulfillWithValue, rejectWithValue }) => {
615 try {
616 return fulfillWithValue({ magic: 'object' })
617 } catch (rejected: any) {
618 return rejectWithValue(rejected?.response?.error || rejected)
619 }
620 },
621 )
622
623 createSlice({
624 name: 'slice',
625 initialState,
626 reducers: {},
627 extraReducers: (builder) => {
628 builder.addCase(getObj.fulfilled, (state, action) => {
629 expectTypeOf(action.payload).toEqualTypeOf<{ magic: string }>()
630 })
631 },
632 })
633 })
634
635 test('meta return values', () => {
636 // return values
637 createAsyncThunk<'ret', void, {}>('test', (_, api) => 'ret' as const)
638 createAsyncThunk<'ret', void, {}>('test', async (_, api) => 'ret' as const)
639 createAsyncThunk<'ret', void, { fulfilledMeta: string }>('test', (_, api) =>
640 api.fulfillWithValue('ret' as const, ''),
641 )
642 createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
643 'test',
644 async (_, api) => api.fulfillWithValue('ret' as const, ''),
645 )
646 createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
647 'test',
648 // @ts-expect-error has to be a fulfilledWithValue call
649 (_, api) => 'ret' as const,
650 )
651 createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
652 'test',
653 // @ts-expect-error has to be a fulfilledWithValue call
654 async (_, api) => 'ret' as const,
655 )
656 createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
657 'test', // @ts-expect-error should only allow returning with 'test'
658 (_, api) => api.fulfillWithValue(5, ''),
659 )
660 createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
661 'test', // @ts-expect-error should only allow returning with 'test'
662 async (_, api) => api.fulfillWithValue(5, ''),
663 )
664
665 // reject values
666 createAsyncThunk<'ret', void, { rejectValue: string }>('test', (_, api) =>
667 api.rejectWithValue('ret'),
668 )
669 createAsyncThunk<'ret', void, { rejectValue: string }>(
670 'test',
671 async (_, api) => api.rejectWithValue('ret'),
672 )
673 createAsyncThunk<
674 'ret',
675 void,
676 { rejectValue: string; rejectedMeta: number }
677 >('test', (_, api) => api.rejectWithValue('ret', 5))
678 createAsyncThunk<
679 'ret',
680 void,
681 { rejectValue: string; rejectedMeta: number }
682 >('test', async (_, api) => api.rejectWithValue('ret', 5))
683 createAsyncThunk<
684 'ret',
685 void,
686 { rejectValue: string; rejectedMeta: number }
687 >('test', (_, api) => api.rejectWithValue('ret', 5))
688 createAsyncThunk<
689 'ret',
690 void,
691 { rejectValue: string; rejectedMeta: number }
692 >(
693 'test',
694 // @ts-expect-error wrong rejectedMeta type
695 (_, api) => api.rejectWithValue('ret', ''),
696 )
697 createAsyncThunk<
698 'ret',
699 void,
700 { rejectValue: string; rejectedMeta: number }
701 >(
702 'test',
703 // @ts-expect-error wrong rejectedMeta type
704 async (_, api) => api.rejectWithValue('ret', ''),
705 )
706 createAsyncThunk<
707 'ret',
708 void,
709 { rejectValue: string; rejectedMeta: number }
710 >(
711 'test',
712 // @ts-expect-error wrong rejectValue type
713 (_, api) => api.rejectWithValue(5, ''),
714 )
715 createAsyncThunk<
716 'ret',
717 void,
718 { rejectValue: string; rejectedMeta: number }
719 >(
720 'test',
721 // @ts-expect-error wrong rejectValue type
722 async (_, api) => api.rejectWithValue(5, ''),
723 )
724 })
725
726 test('usage with config override generic', () => {
727 const typedCAT = createAsyncThunk.withTypes<{
728 state: RootState
729 dispatch: AppDispatch
730 rejectValue: string
731 extra: { s: string; n: number }
732 }>()
733
734 // inferred usage
735 const thunk = typedCAT('foo', (arg: number, api) => {
736 // correct getState Type
737 const test1: number = api.getState().foo.value
738 // correct dispatch type
739 const test2: number = api.dispatch((dispatch, getState) => {
740 expectTypeOf(dispatch).toEqualTypeOf<
741 ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
742 >()
743
744 expectTypeOf(getState).toEqualTypeOf<() => { foo: { value: number } }>()
745
746 return getState().foo.value
747 })
748
749 // correct extra type
750 const { s, n } = api.extra
751
752 expectTypeOf(s).toBeString()
753
754 expectTypeOf(n).toBeNumber()
755
756 if (1 < 2)
757 // @ts-expect-error
758 return api.rejectWithValue(5)
759 if (1 < 2) return api.rejectWithValue('test')
760 return test1 + test2
761 })
762
763 // usage with two generics
764 const thunk2 = typedCAT<number, string>('foo', (arg, api) => {
765 expectTypeOf(arg).toBeString()
766
767 // correct getState Type
768 const test1: number = api.getState().foo.value
769 // correct dispatch type
770 const test2: number = api.dispatch((dispatch, getState) => {
771 expectTypeOf(dispatch).toEqualTypeOf<
772 ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
773 >()
774
775 expectTypeOf(getState).toEqualTypeOf<() => { foo: { value: number } }>()
776
777 return getState().foo.value
778 })
779 // correct extra type
780 const { s, n } = api.extra
781
782 expectTypeOf(s).toBeString()
783
784 expectTypeOf(n).toBeNumber()
785
786 if (1 < 2) expectTypeOf(api.rejectWithValue).toBeCallableWith('test')
787
788 expectTypeOf(api.rejectWithValue).parameter(0).not.toBeNumber()
789
790 expectTypeOf(api.rejectWithValue).parameters.toEqualTypeOf<[string]>()
791
792 return api.rejectWithValue('test')
793 })
794
795 // usage with config override generic
796 const thunk3 = typedCAT<number, string, { rejectValue: number }>(
797 'foo',
798 (arg, api) => {
799 expectTypeOf(arg).toBeString()
800
801 // correct getState Type
802 const test1: number = api.getState().foo.value
803 // correct dispatch type
804 const test2: number = api.dispatch((dispatch, getState) => {
805 expectTypeOf(dispatch).toEqualTypeOf<
806 ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
807 >()
808
809 expectTypeOf(getState).toEqualTypeOf<
810 () => { foo: { value: number } }
811 >()
812
813 return getState().foo.value
814 })
815 // correct extra type
816 const { s, n } = api.extra
817
818 expectTypeOf(s).toBeString()
819
820 expectTypeOf(n).toBeNumber()
821
822 if (1 < 2) return api.rejectWithValue(5)
823 if (1 < 2) expectTypeOf(api.rejectWithValue).toBeCallableWith(5)
824
825 expectTypeOf(api.rejectWithValue).parameter(0).not.toBeString()
826
827 expectTypeOf(api.rejectWithValue).parameters.toEqualTypeOf<[number]>()
828
829 return api.rejectWithValue(5)
830 },
831 )
832
833 const slice = createSlice({
834 name: 'foo',
835 initialState: { value: 0 },
836 reducers: {},
837 extraReducers(builder) {
838 builder
839 .addCase(thunk.fulfilled, (state, action) => {
840 state.value += action.payload
841 })
842 .addCase(thunk.rejected, (state, action) => {
843 expectTypeOf(action.payload).toEqualTypeOf<string | undefined>()
844 })
845 .addCase(thunk2.fulfilled, (state, action) => {
846 state.value += action.payload
847 })
848 .addCase(thunk2.rejected, (state, action) => {
849 expectTypeOf(action.payload).toEqualTypeOf<string | undefined>()
850 })
851 .addCase(thunk3.fulfilled, (state, action) => {
852 state.value += action.payload
853 })
854 .addCase(thunk3.rejected, (state, action) => {
855 expectTypeOf(action.payload).toEqualTypeOf<number | undefined>()
856 })
857 },
858 })
859
860 const store = configureStore({
861 reducer: {
862 foo: slice.reducer,
863 },
864 })
865
866 type RootState = ReturnType<typeof store.getState>
867 type AppDispatch = typeof store.dispatch
868 })
869
870 test('rejectedMeta', async () => {
871 const getNewStore = () =>
872 configureStore({
873 reducer(actions = [], action) {
874 return [...actions, action]
875 },
876 })
877
878 const store = getNewStore()
879
880 const fulfilledThunk = createAsyncThunk<
881 string,
882 string,
883 { rejectedMeta: { extraProp: string } }
884 >('test', (arg: string, { rejectWithValue }) => {
885 return rejectWithValue('damn!', { extraProp: 'baz' })
886 })
887
888 const promise = store.dispatch(fulfilledThunk('testArg'))
889
890 const ret = await promise
891
892 if (ret.meta.requestStatus === 'rejected' && ret.meta.rejectedWithValue) {
893 expectTypeOf(ret.meta.extraProp).toBeString()
894 } else {
895 // could be caused by a `throw`, `abort()` or `condition` - no `rejectedMeta` in that case
896 expectTypeOf(ret.meta).not.toHaveProperty('extraProp')
897 }
898 })
899})
Note: See TracBrowser for help on using the repository browser.