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

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

Added visualizations

  • Property mode set to 100644
File size: 28.7 KB
RevLine 
[a762898]1import type {
2 Action,
3 ActionCreatorWithNonInferrablePayload,
4 ActionCreatorWithOptionalPayload,
5 ActionCreatorWithPayload,
6 ActionCreatorWithPreparedPayload,
7 ActionCreatorWithoutPayload,
8 ActionReducerMapBuilder,
9 AsyncThunk,
10 CaseReducer,
11 PayloadAction,
12 PayloadActionCreator,
13 Reducer,
14 ReducerCreators,
15 SerializedError,
16 SliceCaseReducers,
17 ThunkDispatch,
18 UnknownAction,
19 ValidateSliceCaseReducers,
20} from '@reduxjs/toolkit'
21import {
22 asyncThunkCreator,
23 buildCreateSlice,
24 configureStore,
25 createAction,
26 createAsyncThunk,
27 createSlice,
28 isRejected,
29} from '@reduxjs/toolkit'
30import { castDraft } from 'immer'
31
32describe('type tests', () => {
33 const counterSlice = createSlice({
34 name: 'counter',
35 initialState: 0,
36 reducers: {
37 increment: (state: number, action) => state + action.payload,
38 decrement: (state: number, action) => state - action.payload,
39 },
40 })
41
42 test('Slice name is strongly typed.', () => {
43 const uiSlice = createSlice({
44 name: 'ui',
45 initialState: 0,
46 reducers: {
47 goToNext: (state: number, action) => state + action.payload,
48 goToPrevious: (state: number, action) => state - action.payload,
49 },
50 })
51
52 const actionCreators = {
53 [counterSlice.name]: { ...counterSlice.actions },
54 [uiSlice.name]: { ...uiSlice.actions },
55 }
56
57 expectTypeOf(counterSlice.actions).toEqualTypeOf(actionCreators.counter)
58
59 expectTypeOf(uiSlice.actions).toEqualTypeOf(actionCreators.ui)
60
61 expectTypeOf(actionCreators).not.toHaveProperty('anyKey')
62 })
63
64 test("createSlice() infers the returned slice's type.", () => {
65 const firstAction = createAction<{ count: number }>('FIRST_ACTION')
66
67 const slice = createSlice({
68 name: 'counter',
69 initialState: 0,
70 reducers: {
71 increment: (state: number, action) => state + action.payload,
72 decrement: (state: number, action) => state - action.payload,
73 },
74 extraReducers: (builder) => {
75 builder.addCase(
76 firstAction,
77 (state, action) => state + action.payload.count,
78 )
79 },
80 })
81
82 test('Reducer', () => {
83 expectTypeOf(slice.reducer).toMatchTypeOf<
84 Reducer<number, PayloadAction>
85 >()
86
87 expectTypeOf(slice.reducer).not.toMatchTypeOf<
88 Reducer<string, PayloadAction>
89 >()
90 })
91
92 test('Actions', () => {
93 slice.actions.increment(1)
94 slice.actions.decrement(1)
95
96 expectTypeOf(slice.actions).not.toHaveProperty('other')
97 })
98 })
99
100 test('Slice action creator types are inferred.', () => {
101 const counter = createSlice({
102 name: 'counter',
103 initialState: 0,
104 reducers: {
105 increment: (state) => state + 1,
106 decrement: (
107 state,
108 { payload = 1 }: PayloadAction<number | undefined>,
109 ) => state - payload,
110 multiply: (state, { payload }: PayloadAction<number | number[]>) =>
111 Array.isArray(payload)
112 ? payload.reduce((acc, val) => acc * val, state)
113 : state * payload,
114 addTwo: {
115 reducer: (s, { payload }: PayloadAction<number>) => s + payload,
116 prepare: (a: number, b: number) => ({
117 payload: a + b,
118 }),
119 },
120 },
121 })
122
123 expectTypeOf(
124 counter.actions.increment,
125 ).toMatchTypeOf<ActionCreatorWithoutPayload>()
126
127 counter.actions.increment()
128
129 expectTypeOf(counter.actions.decrement).toMatchTypeOf<
130 ActionCreatorWithOptionalPayload<number | undefined>
131 >()
132
133 counter.actions.decrement()
134 counter.actions.decrement(2)
135
136 expectTypeOf(counter.actions.multiply).toMatchTypeOf<
137 ActionCreatorWithPayload<number | number[]>
138 >()
139
140 counter.actions.multiply(2)
141 counter.actions.multiply([2, 3, 4])
142
143 expectTypeOf(counter.actions.addTwo).toMatchTypeOf<
144 ActionCreatorWithPreparedPayload<[number, number], number>
145 >()
146
147 counter.actions.addTwo(1, 2)
148
149 expectTypeOf(counter.actions.multiply).parameters.not.toMatchTypeOf<[]>()
150
151 expectTypeOf(counter.actions.multiply).parameter(0).not.toBeString()
152
153 expectTypeOf(counter.actions.addTwo).parameters.not.toMatchTypeOf<
154 [number]
155 >()
156
157 expectTypeOf(counter.actions.addTwo).parameters.toEqualTypeOf<
158 [number, number]
159 >()
160 })
161
162 test('Slice action creator types properties are strongly typed', () => {
163 const counter = createSlice({
164 name: 'counter',
165 initialState: 0,
166 reducers: {
167 increment: (state) => state + 1,
168 decrement: (state) => state - 1,
169 multiply: (state, { payload }: PayloadAction<number | number[]>) =>
170 Array.isArray(payload)
171 ? payload.reduce((acc, val) => acc * val, state)
172 : state * payload,
173 },
174 })
175
176 expectTypeOf(
177 counter.actions.increment.type,
178 ).toEqualTypeOf<'counter/increment'>()
179
180 expectTypeOf(
181 counter.actions.increment().type,
182 ).toEqualTypeOf<'counter/increment'>()
183
184 expectTypeOf(
185 counter.actions.decrement.type,
186 ).toEqualTypeOf<'counter/decrement'>()
187
188 expectTypeOf(
189 counter.actions.decrement().type,
190 ).toEqualTypeOf<'counter/decrement'>()
191
192 expectTypeOf(
193 counter.actions.multiply.type,
194 ).toEqualTypeOf<'counter/multiply'>()
195
196 expectTypeOf(
197 counter.actions.multiply(1).type,
198 ).toEqualTypeOf<'counter/multiply'>()
199
200 expectTypeOf(
201 counter.actions.increment.type,
202 ).not.toMatchTypeOf<'increment'>()
203 })
204
205 test('Slice action creator types are inferred for enhanced reducers.', () => {
206 const counter = createSlice({
207 name: 'test',
208 initialState: { counter: 0, concat: '' },
209 reducers: {
210 incrementByStrLen: {
211 reducer: (state, action: PayloadAction<number>) => {
212 state.counter += action.payload
213 },
214 prepare: (payload: string) => ({
215 payload: payload.length,
216 }),
217 },
218 concatMetaStrLen: {
219 reducer: (state, action: PayloadAction<string>) => {
220 state.concat += action.payload
221 },
222 prepare: (payload: string) => ({
223 payload,
224 meta: payload.length,
225 }),
226 },
227 },
228 })
229
230 expectTypeOf(
231 counter.actions.incrementByStrLen('test').type,
232 ).toEqualTypeOf<'test/incrementByStrLen'>()
233
234 expectTypeOf(counter.actions.incrementByStrLen('test').payload).toBeNumber()
235
236 expectTypeOf(counter.actions.concatMetaStrLen('test').payload).toBeString()
237
238 expectTypeOf(counter.actions.concatMetaStrLen('test').meta).toBeNumber()
239
240 expectTypeOf(
241 counter.actions.incrementByStrLen('test').payload,
242 ).not.toBeString()
243
244 expectTypeOf(counter.actions.concatMetaStrLen('test').meta).not.toBeString()
245 })
246
247 test('access meta and error from reducer', () => {
248 const counter = createSlice({
249 name: 'test',
250 initialState: { counter: 0, concat: '' },
251 reducers: {
252 // case: meta and error not used in reducer
253 testDefaultMetaAndError: {
254 reducer(_, action: PayloadAction<number, string>) {},
255 prepare: (payload: number) => ({
256 payload,
257 meta: 'meta' as 'meta',
258 error: 'error' as 'error',
259 }),
260 },
261 // case: meta and error marked as "unknown" in reducer
262 testUnknownMetaAndError: {
263 reducer(
264 _,
265 action: PayloadAction<number, string, unknown, unknown>,
266 ) {},
267 prepare: (payload: number) => ({
268 payload,
269 meta: 'meta' as 'meta',
270 error: 'error' as 'error',
271 }),
272 },
273 // case: meta and error are typed in the reducer as returned by prepare
274 testMetaAndError: {
275 reducer(_, action: PayloadAction<number, string, 'meta', 'error'>) {},
276 prepare: (payload: number) => ({
277 payload,
278 meta: 'meta' as 'meta',
279 error: 'error' as 'error',
280 }),
281 },
282 // case: meta is typed differently in the reducer than returned from prepare
283 testErroneousMeta: {
284 reducer(_, action: PayloadAction<number, string, 'meta', 'error'>) {},
285 // @ts-expect-error
286 prepare: (payload: number) => ({
287 payload,
288 meta: 1,
289 error: 'error' as 'error',
290 }),
291 },
292 // case: error is typed differently in the reducer than returned from prepare
293 testErroneousError: {
294 reducer(_, action: PayloadAction<number, string, 'meta', 'error'>) {},
295 // @ts-expect-error
296 prepare: (payload: number) => ({
297 payload,
298 meta: 'meta' as 'meta',
299 error: 1,
300 }),
301 },
302 },
303 })
304 })
305
306 test('returned case reducer has the correct type', () => {
307 const counter = createSlice({
308 name: 'counter',
309 initialState: 0,
310 reducers: {
311 increment(state, action: PayloadAction<number>) {
312 return state + action.payload
313 },
314 decrement: {
315 reducer(state, action: PayloadAction<number>) {
316 return state - action.payload
317 },
318 prepare(amount: number) {
319 return { payload: amount }
320 },
321 },
322 },
323 })
324
325 test('Should match positively', () => {
326 expectTypeOf(counter.caseReducers.increment).toMatchTypeOf<
327 (state: number, action: PayloadAction<number>) => number | void
328 >()
329 })
330
331 test('Should match positively for reducers with prepare callback', () => {
332 expectTypeOf(counter.caseReducers.decrement).toMatchTypeOf<
333 (state: number, action: PayloadAction<number>) => number | void
334 >()
335 })
336
337 test("Should not mismatch the payload if it's a simple reducer", () => {
338 expectTypeOf(counter.caseReducers.increment).not.toMatchTypeOf<
339 (state: number, action: PayloadAction<string>) => number | void
340 >()
341 })
342
343 test("Should not mismatch the payload if it's a reducer with a prepare callback", () => {
344 expectTypeOf(counter.caseReducers.decrement).not.toMatchTypeOf<
345 (state: number, action: PayloadAction<string>) => number | void
346 >()
347 })
348
349 test("Should not include entries that don't exist", () => {
350 expectTypeOf(counter.caseReducers).not.toHaveProperty(
351 'someThingNonExistent',
352 )
353 })
354 })
355
356 test('prepared payload does not match action payload - should cause an error.', () => {
357 const counter = createSlice({
358 name: 'counter',
359 initialState: { counter: 0 },
360 reducers: {
361 increment: {
362 reducer(state, action: PayloadAction<string>) {
363 state.counter += action.payload.length
364 },
365 // @ts-expect-error
366 prepare(x: string) {
367 return {
368 payload: 6,
369 }
370 },
371 },
372 },
373 })
374 })
375
376 test('if no Payload Type is specified, accept any payload', () => {
377 // see https://github.com/reduxjs/redux-toolkit/issues/165
378
379 const initialState = {
380 name: null,
381 }
382
383 const mySlice = createSlice({
384 name: 'name',
385 initialState,
386 reducers: {
387 setName: (state, action) => {
388 state.name = action.payload
389 },
390 },
391 })
392
393 expectTypeOf(
394 mySlice.actions.setName,
395 ).toMatchTypeOf<ActionCreatorWithNonInferrablePayload>()
396
397 const x = mySlice.actions.setName
398
399 mySlice.actions.setName(null)
400 mySlice.actions.setName('asd')
401 mySlice.actions.setName(5)
402 })
403
404 test('actions.x.match()', () => {
405 const mySlice = createSlice({
406 name: 'name',
407 initialState: { name: 'test' },
408 reducers: {
409 setName: (state, action: PayloadAction<string>) => {
410 state.name = action.payload
411 },
412 },
413 })
414
415 const x: Action<string> = {} as any
416 if (mySlice.actions.setName.match(x)) {
417 expectTypeOf(x.type).toEqualTypeOf<'name/setName'>()
418
419 expectTypeOf(x.payload).toBeString()
420 } else {
421 expectTypeOf(x.type).not.toMatchTypeOf<'name/setName'>()
422
423 expectTypeOf(x).not.toHaveProperty('payload')
424 }
425 })
426
427 test('builder callback for extraReducers', () => {
428 createSlice({
429 name: 'test',
430 initialState: 0,
431 reducers: {},
432 extraReducers: (builder) => {
433 expectTypeOf(builder).toEqualTypeOf<ActionReducerMapBuilder<number>>()
434 },
435 })
436 })
437
438 test('wrapping createSlice should be possible', () => {
439 interface GenericState<T> {
440 data?: T
441 status: 'loading' | 'finished' | 'error'
442 }
443
444 const createGenericSlice = <
445 T,
446 Reducers extends SliceCaseReducers<GenericState<T>>,
447 >({
448 name = '',
449 initialState,
450 reducers,
451 }: {
452 name: string
453 initialState: GenericState<T>
454 reducers: ValidateSliceCaseReducers<GenericState<T>, Reducers>
455 }) => {
456 return createSlice({
457 name,
458 initialState,
459 reducers: {
460 start(state) {
461 state.status = 'loading'
462 },
463 success(state: GenericState<T>, action: PayloadAction<T>) {
464 state.data = action.payload
465 state.status = 'finished'
466 },
467 ...reducers,
468 },
469 })
470 }
471
472 const wrappedSlice = createGenericSlice({
473 name: 'test',
474 initialState: { status: 'loading' } as GenericState<string>,
475 reducers: {
476 magic(state) {
477 expectTypeOf(state).toEqualTypeOf<GenericState<string>>()
478
479 expectTypeOf(state).not.toMatchTypeOf<GenericState<number>>()
480
481 state.status = 'finished'
482 state.data = 'hocus pocus'
483 },
484 },
485 })
486
487 expectTypeOf(wrappedSlice.actions.success).toMatchTypeOf<
488 ActionCreatorWithPayload<string>
489 >()
490
491 expectTypeOf(wrappedSlice.actions.magic).toMatchTypeOf<
492 ActionCreatorWithoutPayload<string>
493 >()
494 })
495
496 test('extraReducers', () => {
497 interface GenericState<T> {
498 data: T | null
499 }
500
501 function createDataSlice<
502 T,
503 Reducers extends SliceCaseReducers<GenericState<T>>,
504 >(
505 name: string,
506 reducers: ValidateSliceCaseReducers<GenericState<T>, Reducers>,
507 initialState: GenericState<T>,
508 ) {
509 const doNothing = createAction<undefined>('doNothing')
510 const setData = createAction<T>('setData')
511
512 const slice = createSlice({
513 name,
514 initialState,
515 reducers,
516 extraReducers: (builder) => {
517 builder.addCase(doNothing, (state) => {
518 return { ...state }
519 })
520 builder.addCase(setData, (state, { payload }) => {
521 return {
522 ...state,
523 data: payload,
524 }
525 })
526 },
527 })
528 return { doNothing, setData, slice }
529 }
530 })
531
532 test('slice selectors', () => {
533 const sliceWithoutSelectors = createSlice({
534 name: '',
535 initialState: '',
536 reducers: {},
537 })
538
539 expectTypeOf(sliceWithoutSelectors.selectors).not.toHaveProperty('foo')
540
541 const sliceWithSelectors = createSlice({
542 name: 'counter',
543 initialState: { value: 0 },
544 reducers: {
545 increment: (state) => {
546 state.value += 1
547 },
548 },
549 selectors: {
550 selectValue: (state) => state.value,
551 selectMultiply: (state, multiplier: number) => state.value * multiplier,
552 selectToFixed: Object.assign(
553 (state: { value: number }) => state.value.toFixed(2),
554 { static: true },
555 ),
556 },
557 })
558
559 const rootState = {
560 [sliceWithSelectors.reducerPath]: sliceWithSelectors.getInitialState(),
561 }
562
563 const { selectValue, selectMultiply, selectToFixed } =
564 sliceWithSelectors.selectors
565
566 expectTypeOf(selectValue(rootState)).toBeNumber()
567
568 expectTypeOf(selectMultiply(rootState, 2)).toBeNumber()
569
570 expectTypeOf(selectToFixed(rootState)).toBeString()
571
572 expectTypeOf(selectToFixed.unwrapped.static).toBeBoolean()
573
574 const nestedState = {
575 nested: rootState,
576 }
577
578 const nestedSelectors = sliceWithSelectors.getSelectors(
579 (rootState: typeof nestedState) => rootState.nested.counter,
580 )
581
582 expectTypeOf(nestedSelectors.selectValue(nestedState)).toBeNumber()
583
584 expectTypeOf(nestedSelectors.selectMultiply(nestedState, 2)).toBeNumber()
585
586 expectTypeOf(nestedSelectors.selectToFixed(nestedState)).toBeString()
587 })
588
589 test('reducer callback', () => {
590 interface TestState {
591 foo: string
592 }
593
594 interface TestArg {
595 test: string
596 }
597
598 interface TestReturned {
599 payload: string
600 }
601
602 interface TestReject {
603 cause: string
604 }
605
606 const slice = createSlice({
607 name: 'test',
608 initialState: {} as TestState,
609 reducers: (create) => {
610 const preTypedAsyncThunk = create.asyncThunk.withTypes<{
611 rejectValue: TestReject
612 }>()
613
614 // @ts-expect-error
615 create.asyncThunk<any, any, { state: StoreState }>(() => {})
616
617 // @ts-expect-error
618 create.asyncThunk.withTypes<{
619 rejectValue: string
620 dispatch: StoreDispatch
621 }>()
622
623 return {
624 normalReducer: create.reducer<string>((state, action) => {
625 expectTypeOf(state).toEqualTypeOf<TestState>()
626
627 expectTypeOf(action.payload).toBeString()
628 }),
629 optionalReducer: create.reducer<string | undefined>(
630 (state, action) => {
631 expectTypeOf(state).toEqualTypeOf<TestState>()
632
633 expectTypeOf(action.payload).toEqualTypeOf<string | undefined>()
634 },
635 ),
636 noActionReducer: create.reducer((state) => {
637 expectTypeOf(state).toEqualTypeOf<TestState>()
638 }),
639 preparedReducer: create.preparedReducer(
640 (payload: string) => ({
641 payload,
642 meta: 'meta' as const,
643 error: 'error' as const,
644 }),
645 (state, action) => {
646 expectTypeOf(state).toEqualTypeOf<TestState>()
647
648 expectTypeOf(action.payload).toBeString()
649
650 expectTypeOf(action.meta).toEqualTypeOf<'meta'>()
651
652 expectTypeOf(action.error).toEqualTypeOf<'error'>()
653 },
654 ),
655 testInferVoid: create.asyncThunk(() => {}, {
656 pending(state, action) {
657 expectTypeOf(state).toEqualTypeOf<TestState>()
658
659 expectTypeOf(action.meta.arg).toBeVoid()
660 },
661 fulfilled(state, action) {
662 expectTypeOf(state).toEqualTypeOf<TestState>()
663
664 expectTypeOf(action.meta.arg).toBeVoid()
665
666 expectTypeOf(action.payload).toBeVoid()
667 },
668 rejected(state, action) {
669 expectTypeOf(state).toEqualTypeOf<TestState>()
670
671 expectTypeOf(action.meta.arg).toBeVoid()
672
673 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
674 },
675 settled(state, action) {
676 expectTypeOf(state).toEqualTypeOf<TestState>()
677
678 expectTypeOf(action.meta.arg).toBeVoid()
679
680 if (isRejected(action)) {
681 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
682 } else {
683 expectTypeOf(action.payload).toBeVoid()
684 }
685 },
686 }),
687 testInfer: create.asyncThunk(
688 function payloadCreator(arg: TestArg, api) {
689 return Promise.resolve<TestReturned>({ payload: 'foo' })
690 },
691 {
692 pending(state, action) {
693 expectTypeOf(state).toEqualTypeOf<TestState>()
694
695 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
696 },
697 fulfilled(state, action) {
698 expectTypeOf(state).toEqualTypeOf<TestState>()
699
700 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
701
702 expectTypeOf(action.payload).toEqualTypeOf<TestReturned>()
703 },
704 rejected(state, action) {
705 expectTypeOf(state).toEqualTypeOf<TestState>()
706
707 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
708
709 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
710 },
711 settled(state, action) {
712 expectTypeOf(state).toEqualTypeOf<TestState>()
713
714 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
715
716 if (isRejected(action)) {
717 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
718 } else {
719 expectTypeOf(action.payload).toEqualTypeOf<TestReturned>()
720 }
721 },
722 },
723 ),
724 testExplicitType: create.asyncThunk<
725 TestReturned,
726 TestArg,
727 {
728 rejectValue: TestReject
729 }
730 >(
731 function payloadCreator(arg, api) {
732 // here would be a circular reference
733 expectTypeOf(api.getState()).toBeUnknown()
734 // here would be a circular reference
735 expectTypeOf(api.dispatch).toMatchTypeOf<
736 ThunkDispatch<any, any, any>
737 >()
738
739 // so you need to cast inside instead
740 const getState = api.getState as () => StoreState
741 const dispatch = api.dispatch as StoreDispatch
742
743 expectTypeOf(arg).toEqualTypeOf<TestArg>()
744
745 expectTypeOf(api.rejectWithValue).toMatchTypeOf<
746 (value: TestReject) => any
747 >()
748
749 return Promise.resolve({ payload: 'foo' })
750 },
751 {
752 pending(state, action) {
753 expectTypeOf(state).toEqualTypeOf<TestState>()
754
755 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
756 },
757 fulfilled(state, action) {
758 expectTypeOf(state).toEqualTypeOf<TestState>()
759
760 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
761
762 expectTypeOf(action.payload).toEqualTypeOf<TestReturned>()
763 },
764 rejected(state, action) {
765 expectTypeOf(state).toEqualTypeOf<TestState>()
766
767 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
768
769 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
770
771 expectTypeOf(action.payload).toEqualTypeOf<
772 TestReject | undefined
773 >()
774 },
775 settled(state, action) {
776 expectTypeOf(state).toEqualTypeOf<TestState>()
777
778 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
779
780 if (isRejected(action)) {
781 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
782
783 expectTypeOf(action.payload).toEqualTypeOf<
784 TestReject | undefined
785 >()
786 } else {
787 expectTypeOf(action.payload).toEqualTypeOf<TestReturned>()
788 }
789 },
790 },
791 ),
792 testPreTyped: preTypedAsyncThunk(
793 function payloadCreator(arg: TestArg, api) {
794 expectTypeOf(api.rejectWithValue).toMatchTypeOf<
795 (value: TestReject) => any
796 >()
797
798 return Promise.resolve<TestReturned>({ payload: 'foo' })
799 },
800 {
801 pending(state, action) {
802 expectTypeOf(state).toEqualTypeOf<TestState>()
803
804 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
805 },
806 fulfilled(state, action) {
807 expectTypeOf(state).toEqualTypeOf<TestState>()
808
809 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
810
811 expectTypeOf(action.payload).toEqualTypeOf<TestReturned>()
812 },
813 rejected(state, action) {
814 expectTypeOf(state).toEqualTypeOf<TestState>()
815
816 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
817
818 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
819
820 expectTypeOf(action.payload).toEqualTypeOf<
821 TestReject | undefined
822 >()
823 },
824 settled(state, action) {
825 expectTypeOf(state).toEqualTypeOf<TestState>()
826
827 expectTypeOf(action.meta.arg).toEqualTypeOf<TestArg>()
828
829 if (isRejected(action)) {
830 expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
831
832 expectTypeOf(action.payload).toEqualTypeOf<
833 TestReject | undefined
834 >()
835 } else {
836 expectTypeOf(action.payload).toEqualTypeOf<TestReturned>()
837 }
838 },
839 },
840 ),
841 }
842 },
843 })
844
845 const store = configureStore({ reducer: { test: slice.reducer } })
846
847 type StoreState = ReturnType<typeof store.getState>
848
849 type StoreDispatch = typeof store.dispatch
850
851 expectTypeOf(slice.actions.normalReducer).toMatchTypeOf<
852 PayloadActionCreator<string>
853 >()
854
855 expectTypeOf(slice.actions.normalReducer).toBeCallableWith('')
856
857 expectTypeOf(slice.actions.normalReducer).parameters.not.toMatchTypeOf<[]>()
858
859 expectTypeOf(slice.actions.normalReducer).parameters.not.toMatchTypeOf<
860 [number]
861 >()
862
863 expectTypeOf(slice.actions.optionalReducer).toMatchTypeOf<
864 ActionCreatorWithOptionalPayload<string | undefined>
865 >()
866
867 expectTypeOf(slice.actions.optionalReducer).toBeCallableWith()
868
869 expectTypeOf(slice.actions.optionalReducer).toBeCallableWith('')
870
871 expectTypeOf(slice.actions.optionalReducer).parameter(0).not.toBeNumber()
872
873 expectTypeOf(
874 slice.actions.noActionReducer,
875 ).toMatchTypeOf<ActionCreatorWithoutPayload>()
876
877 expectTypeOf(slice.actions.noActionReducer).toBeCallableWith()
878
879 expectTypeOf(slice.actions.noActionReducer).parameter(0).not.toBeString()
880
881 expectTypeOf(slice.actions.preparedReducer).toEqualTypeOf<
882 ActionCreatorWithPreparedPayload<
883 [string],
884 string,
885 'test/preparedReducer',
886 'error',
887 'meta'
888 >
889 >()
890
891 expectTypeOf(slice.actions.testInferVoid).toEqualTypeOf<
892 AsyncThunk<void, void, {}>
893 >()
894
895 expectTypeOf(slice.actions.testInferVoid).toBeCallableWith()
896
897 expectTypeOf(slice.actions.testInfer).toEqualTypeOf<
898 AsyncThunk<TestReturned, TestArg, {}>
899 >()
900
901 expectTypeOf(slice.actions.testExplicitType).toEqualTypeOf<
902 AsyncThunk<TestReturned, TestArg, { rejectValue: TestReject }>
903 >()
904
905 type TestInferThunk = AsyncThunk<TestReturned, TestArg, {}>
906
907 expectTypeOf(slice.caseReducers.testInfer.pending).toEqualTypeOf<
908 CaseReducer<TestState, ReturnType<TestInferThunk['pending']>>
909 >()
910
911 expectTypeOf(slice.caseReducers.testInfer.fulfilled).toEqualTypeOf<
912 CaseReducer<TestState, ReturnType<TestInferThunk['fulfilled']>>
913 >()
914
915 expectTypeOf(slice.caseReducers.testInfer.rejected).toEqualTypeOf<
916 CaseReducer<TestState, ReturnType<TestInferThunk['rejected']>>
917 >()
918 })
919
920 test('wrapping createSlice should be possible, with callback', () => {
921 interface GenericState<T> {
922 data?: T
923 status: 'loading' | 'finished' | 'error'
924 }
925
926 const createGenericSlice = <
927 T,
928 Reducers extends SliceCaseReducers<GenericState<T>>,
929 >({
930 name = '',
931 initialState,
932 reducers,
933 }: {
934 name: string
935 initialState: GenericState<T>
936 reducers: (create: ReducerCreators<GenericState<T>>) => Reducers
937 }) => {
938 return createSlice({
939 name,
940 initialState,
941 reducers: (create) => ({
942 start: create.reducer((state) => {
943 state.status = 'loading'
944 }),
945 success: create.reducer<T>((state, action) => {
946 state.data = castDraft(action.payload)
947 state.status = 'finished'
948 }),
949 ...reducers(create),
950 }),
951 })
952 }
953
954 const wrappedSlice = createGenericSlice({
955 name: 'test',
956 initialState: { status: 'loading' } as GenericState<string>,
957 reducers: (create) => ({
958 magic: create.reducer((state) => {
959 expectTypeOf(state).toEqualTypeOf<GenericState<string>>()
960
961 expectTypeOf(state).not.toMatchTypeOf<GenericState<number>>()
962
963 state.status = 'finished'
964 state.data = 'hocus pocus'
965 }),
966 }),
967 })
968
969 expectTypeOf(wrappedSlice.actions.success).toMatchTypeOf<
970 ActionCreatorWithPayload<string>
971 >()
972
973 expectTypeOf(wrappedSlice.actions.magic).toMatchTypeOf<
974 ActionCreatorWithoutPayload<string>
975 >()
976 })
977
978 test('selectSlice', () => {
979 expectTypeOf(counterSlice.selectSlice({ counter: 0 })).toBeNumber()
980
981 // We use `not.toEqualTypeOf` instead of `not.toMatchTypeOf`
982 // because `toMatchTypeOf` allows missing properties
983 expectTypeOf(counterSlice.selectSlice).parameter(0).not.toEqualTypeOf<{}>()
984 })
985
986 test('buildCreateSlice', () => {
987 expectTypeOf(buildCreateSlice()).toEqualTypeOf(createSlice)
988
989 buildCreateSlice({
990 // @ts-expect-error not possible to recreate shape because symbol is not exported
991 creators: { asyncThunk: { [Symbol()]: createAsyncThunk } },
992 })
993 buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })
994 })
995})
Note: See TracBrowser for help on using the repository browser.