source: node_modules/@reduxjs/toolkit/src/tests/createSlice.test.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.0 KB
Line 
1import { noop } from '@internal/listenerMiddleware/utils'
2import type { PayloadAction, WithSlice } from '@reduxjs/toolkit'
3import {
4 asyncThunkCreator,
5 buildCreateSlice,
6 combineSlices,
7 configureStore,
8 createAction,
9 createAsyncThunk,
10 createSlice,
11} from '@reduxjs/toolkit'
12
13type CreateSlice = typeof createSlice
14
15describe('createSlice', () => {
16 const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(noop)
17
18 beforeEach(() => {
19 vi.clearAllMocks()
20 })
21
22 afterAll(() => {
23 vi.restoreAllMocks()
24 })
25
26 describe('when slice is undefined', () => {
27 it('should throw an error', () => {
28 expect(() =>
29 // @ts-ignore
30 createSlice({
31 reducers: {
32 increment: (state) => state + 1,
33 multiply: (state, action: PayloadAction<number>) =>
34 state * action.payload,
35 },
36 initialState: 0,
37 }),
38 ).toThrowError()
39 })
40 })
41
42 describe('when slice is an empty string', () => {
43 it('should throw an error', () => {
44 expect(() =>
45 createSlice({
46 name: '',
47 reducers: {
48 increment: (state) => state + 1,
49 multiply: (state, action: PayloadAction<number>) =>
50 state * action.payload,
51 },
52 initialState: 0,
53 }),
54 ).toThrowError()
55 })
56 })
57
58 describe('when initial state is undefined', () => {
59 beforeEach(() => {
60 vi.stubEnv('NODE_ENV', 'development')
61 })
62
63 afterEach(() => {
64 vi.unstubAllEnvs()
65 })
66
67 it('should throw an error', () => {
68 createSlice({
69 name: 'test',
70 reducers: {},
71 initialState: undefined,
72 })
73
74 expect(consoleErrorSpy).toHaveBeenCalledOnce()
75
76 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
77 'You must provide an `initialState` value that is not `undefined`. You may have misspelled `initialState`',
78 )
79 })
80 })
81
82 describe('when passing slice', () => {
83 const { actions, reducer, caseReducers } = createSlice({
84 reducers: {
85 increment: (state) => state + 1,
86 },
87 initialState: 0,
88 name: 'cool',
89 })
90
91 it('should create increment action', () => {
92 expect(actions.hasOwnProperty('increment')).toBe(true)
93 })
94
95 it('should have the correct action for increment', () => {
96 expect(actions.increment()).toEqual({
97 type: 'cool/increment',
98 payload: undefined,
99 })
100 })
101
102 it('should return the correct value from reducer', () => {
103 expect(reducer(undefined, actions.increment())).toEqual(1)
104 })
105
106 it('should include the generated case reducers', () => {
107 expect(caseReducers).toBeTruthy()
108 expect(caseReducers.increment).toBeTruthy()
109 expect(typeof caseReducers.increment).toBe('function')
110 })
111
112 it('getInitialState should return the state', () => {
113 const initialState = 42
114 const slice = createSlice({
115 name: 'counter',
116 initialState,
117 reducers: {},
118 })
119
120 expect(slice.getInitialState()).toBe(initialState)
121 })
122
123 it('should allow non-draftable initial state', () => {
124 expect(() =>
125 createSlice({
126 name: 'params',
127 initialState: new URLSearchParams(),
128 reducers: {},
129 }),
130 ).not.toThrowError()
131 })
132 })
133
134 describe('when initialState is a function', () => {
135 const initialState = () => ({ user: '' })
136
137 const { actions, reducer } = createSlice({
138 reducers: {
139 setUserName: (state, action) => {
140 state.user = action.payload
141 },
142 },
143 initialState,
144 name: 'user',
145 })
146
147 it('should set the username', () => {
148 expect(reducer(undefined, actions.setUserName('eric'))).toEqual({
149 user: 'eric',
150 })
151 })
152
153 it('getInitialState should return the state', () => {
154 const initialState = () => 42
155 const slice = createSlice({
156 name: 'counter',
157 initialState,
158 reducers: {},
159 })
160
161 expect(slice.getInitialState()).toBe(42)
162 })
163
164 it('should allow non-draftable initial state', () => {
165 expect(() =>
166 createSlice({
167 name: 'params',
168 initialState: () => new URLSearchParams(),
169 reducers: {},
170 }),
171 ).not.toThrowError()
172 })
173 })
174
175 describe('when mutating state object', () => {
176 const initialState = { user: '' }
177
178 const { actions, reducer } = createSlice({
179 reducers: {
180 setUserName: (state, action) => {
181 state.user = action.payload
182 },
183 },
184 initialState,
185 name: 'user',
186 })
187
188 it('should set the username', () => {
189 expect(reducer(initialState, actions.setUserName('eric'))).toEqual({
190 user: 'eric',
191 })
192 })
193 })
194
195 describe('when passing extra reducers', () => {
196 const addMore = createAction<{ amount: number }>('ADD_MORE')
197
198 const { reducer } = createSlice({
199 name: 'test',
200 reducers: {
201 increment: (state) => state + 1,
202 multiply: (state, action) => state * action.payload,
203 },
204 extraReducers: (builder) => {
205 builder.addCase(
206 addMore,
207 (state, action) => state + action.payload.amount,
208 )
209 },
210
211 initialState: 0,
212 })
213
214 it('should call extra reducers when their actions are dispatched', () => {
215 const result = reducer(10, addMore({ amount: 5 }))
216
217 expect(result).toBe(15)
218 })
219
220 describe('builder callback for extraReducers', () => {
221 const increment = createAction<number, 'increment'>('increment')
222
223 test('can be used with actionCreators', () => {
224 const slice = createSlice({
225 name: 'counter',
226 initialState: 0,
227 reducers: {},
228 extraReducers: (builder) =>
229 builder.addCase(
230 increment,
231 (state, action) => state + action.payload,
232 ),
233 })
234 expect(slice.reducer(0, increment(5))).toBe(5)
235 })
236
237 test('can be used with string action types', () => {
238 const slice = createSlice({
239 name: 'counter',
240 initialState: 0,
241 reducers: {},
242 extraReducers: (builder) =>
243 builder.addCase(
244 'increment',
245 (state, action: { type: 'increment'; payload: number }) =>
246 state + action.payload,
247 ),
248 })
249 expect(slice.reducer(0, increment(5))).toBe(5)
250 })
251
252 test('prevents the same action type from being specified twice', () => {
253 expect(() => {
254 const slice = createSlice({
255 name: 'counter',
256 initialState: 0,
257 reducers: {},
258 extraReducers: (builder) =>
259 builder
260 .addCase('increment', (state) => state + 1)
261 .addCase('increment', (state) => state + 1),
262 })
263 slice.reducer(undefined, { type: 'unrelated' })
264 }).toThrowErrorMatchingInlineSnapshot(
265 `[Error: \`builder.addCase\` cannot be called with two reducers for the same action type 'increment']`,
266 )
267 })
268
269 test('can be used with addAsyncThunk and async thunks', () => {
270 const asyncThunk = createAsyncThunk('test', (n: number) => n)
271 const slice = createSlice({
272 name: 'counter',
273 initialState: {
274 loading: false,
275 errored: false,
276 value: 0,
277 },
278 reducers: {},
279 extraReducers: (builder) =>
280 builder.addAsyncThunk(asyncThunk, {
281 pending(state) {
282 state.loading = true
283 },
284 fulfilled(state, action) {
285 state.value = action.payload
286 },
287 rejected(state) {
288 state.errored = true
289 },
290 settled(state) {
291 state.loading = false
292 },
293 }),
294 })
295 expect(
296 slice.reducer(undefined, asyncThunk.pending('requestId', 5)),
297 ).toEqual({
298 loading: true,
299 errored: false,
300 value: 0,
301 })
302 expect(
303 slice.reducer(undefined, asyncThunk.fulfilled(5, 'requestId', 5)),
304 ).toEqual({
305 loading: false,
306 errored: false,
307 value: 5,
308 })
309 expect(
310 slice.reducer(
311 undefined,
312 asyncThunk.rejected(new Error(), 'requestId', 5),
313 ),
314 ).toEqual({
315 loading: false,
316 errored: true,
317 value: 0,
318 })
319 })
320
321 test('can be used with addMatcher and type guard functions', () => {
322 const slice = createSlice({
323 name: 'counter',
324 initialState: 0,
325 reducers: {},
326 extraReducers: (builder) =>
327 builder.addMatcher(
328 increment.match,
329 (state, action: { type: 'increment'; payload: number }) =>
330 state + action.payload,
331 ),
332 })
333 expect(slice.reducer(0, increment(5))).toBe(5)
334 })
335
336 test('can be used with addDefaultCase', () => {
337 const slice = createSlice({
338 name: 'counter',
339 initialState: 0,
340 reducers: {},
341 extraReducers: (builder) =>
342 builder.addDefaultCase(
343 (state, action) =>
344 state + (action as PayloadAction<number>).payload,
345 ),
346 })
347 expect(slice.reducer(0, increment(5))).toBe(5)
348 })
349
350 // for further tests, see the test of createReducer that goes way more into depth on this
351 })
352 })
353
354 describe('behavior with enhanced case reducers', () => {
355 it('should pass all arguments to the prepare function', () => {
356 const prepare = vi.fn((payload, somethingElse) => ({ payload }))
357
358 const testSlice = createSlice({
359 name: 'test',
360 initialState: 0,
361 reducers: {
362 testReducer: {
363 reducer: (s) => s,
364 prepare,
365 },
366 },
367 })
368
369 expect(testSlice.actions.testReducer('a', 1)).toEqual({
370 type: 'test/testReducer',
371 payload: 'a',
372 })
373 expect(prepare).toHaveBeenCalledWith('a', 1)
374 })
375
376 it('should call the reducer function', () => {
377 const reducer = vi.fn(() => 5)
378
379 const testSlice = createSlice({
380 name: 'test',
381 initialState: 0,
382 reducers: {
383 testReducer: {
384 reducer,
385 prepare: (payload: any) => ({ payload }),
386 },
387 },
388 })
389
390 testSlice.reducer(0, testSlice.actions.testReducer('testPayload'))
391 expect(reducer).toHaveBeenCalledWith(
392 0,
393 expect.objectContaining({ payload: 'testPayload' }),
394 )
395 })
396 })
397
398 describe('circularity', () => {
399 test('extraReducers can reference each other circularly', () => {
400 const first = createSlice({
401 name: 'first',
402 initialState: 'firstInitial',
403 reducers: {
404 something() {
405 return 'firstSomething'
406 },
407 },
408 extraReducers(builder) {
409 // eslint-disable-next-line @typescript-eslint/no-use-before-define
410 builder.addCase(second.actions.other, () => {
411 return 'firstOther'
412 })
413 },
414 })
415 const second = createSlice({
416 name: 'second',
417 initialState: 'secondInitial',
418 reducers: {
419 other() {
420 return 'secondOther'
421 },
422 },
423 extraReducers(builder) {
424 builder.addCase(first.actions.something, () => {
425 return 'secondSomething'
426 })
427 },
428 })
429
430 expect(first.reducer(undefined, { type: 'unrelated' })).toBe(
431 'firstInitial',
432 )
433 expect(first.reducer(undefined, first.actions.something())).toBe(
434 'firstSomething',
435 )
436 expect(first.reducer(undefined, second.actions.other())).toBe(
437 'firstOther',
438 )
439
440 expect(second.reducer(undefined, { type: 'unrelated' })).toBe(
441 'secondInitial',
442 )
443 expect(second.reducer(undefined, first.actions.something())).toBe(
444 'secondSomething',
445 )
446 expect(second.reducer(undefined, second.actions.other())).toBe(
447 'secondOther',
448 )
449 })
450 })
451
452 describe('Deprecation warnings', () => {
453 beforeEach(() => {
454 vi.resetModules()
455 })
456
457 afterEach(() => {
458 vi.unstubAllEnvs()
459 })
460
461 // NOTE: This needs to be in front of the later `createReducer` call to check the one-time warning
462 it('Throws an error if the legacy object notation is used', async () => {
463 const { createSlice } = await import('../createSlice')
464
465 let dummySlice = (createSlice as CreateSlice)({
466 name: 'dummy',
467 initialState: [],
468 reducers: {},
469 extraReducers: {
470 // @ts-ignore
471 a: () => [],
472 },
473 })
474 let reducer: any
475 // Have to trigger the lazy creation
476 const wrapper = () => {
477 reducer = dummySlice.reducer
478 reducer(undefined, { type: 'dummy' })
479 }
480
481 expect(wrapper).toThrowError(
482 /The object notation for `createSlice.extraReducers` has been removed/,
483 )
484
485 dummySlice = (createSlice as CreateSlice)({
486 name: 'dummy',
487 initialState: [],
488 reducers: {},
489 extraReducers: {
490 // @ts-ignore
491 a: () => [],
492 },
493 })
494 expect(wrapper).toThrowError(
495 /The object notation for `createSlice.extraReducers` has been removed/,
496 )
497 })
498
499 // TODO Determine final production behavior here
500 it.todo('Crashes in production', () => {
501 vi.stubEnv('NODE_ENV', 'production')
502
503 const { createSlice } = require('../createSlice')
504
505 const dummySlice = (createSlice as CreateSlice)({
506 name: 'dummy',
507 initialState: [],
508 reducers: {},
509 // @ts-ignore
510 extraReducers: {},
511 })
512 const wrapper = () => {
513 const { reducer } = dummySlice
514 reducer(undefined, { type: 'dummy' })
515 }
516
517 expect(wrapper).toThrowError(
518 /The object notation for `createSlice.extraReducers` has been removed/,
519 )
520
521 vi.unstubAllEnvs()
522 })
523 })
524 describe('slice selectors', () => {
525 const slice = createSlice({
526 name: 'counter',
527 initialState: 42,
528 reducers: {},
529 selectors: {
530 selectSlice: (state) => state,
531 selectMultiple: Object.assign(
532 (state: number, multiplier: number) => state * multiplier,
533 { test: 0 },
534 ),
535 },
536 })
537 it('expects reducer under slice.reducerPath if no selectState callback passed', () => {
538 const testState = {
539 [slice.reducerPath]: slice.getInitialState(),
540 }
541 const { selectSlice, selectMultiple } = slice.selectors
542 expect(selectSlice(testState)).toBe(slice.getInitialState())
543 expect(selectMultiple(testState, 2)).toBe(slice.getInitialState() * 2)
544 })
545 it('allows passing a selector for a custom location', () => {
546 const customState = {
547 number: slice.getInitialState(),
548 }
549 const { selectSlice, selectMultiple } = slice.getSelectors(
550 (state: typeof customState) => state.number,
551 )
552 expect(selectSlice(customState)).toBe(slice.getInitialState())
553 expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2)
554 })
555 it('allows accessing properties on the selector', () => {
556 expect(slice.selectors.selectMultiple.unwrapped.test).toBe(0)
557 })
558 it('has selectSlice attached to slice, which can go without this', () => {
559 const slice = createSlice({
560 name: 'counter',
561 initialState: 42,
562 reducers: {},
563 })
564 const { selectSlice } = slice
565 expect(() => selectSlice({ counter: 42 })).not.toThrow()
566 expect(selectSlice({ counter: 42 })).toBe(42)
567 })
568 })
569 describe('slice injections', () => {
570 it('uses injectInto to inject slice into combined reducer', () => {
571 const slice = createSlice({
572 name: 'counter',
573 initialState: 42,
574 reducers: {
575 increment: (state) => ++state,
576 },
577 selectors: {
578 selectMultiple: (state, multiplier: number) => state * multiplier,
579 },
580 })
581
582 const { increment } = slice.actions
583
584 const combinedReducer = combineSlices({
585 static: slice.reducer,
586 }).withLazyLoadedSlices<WithSlice<typeof slice>>()
587
588 const uninjectedState = combinedReducer(undefined, increment())
589
590 expect(uninjectedState.counter).toBe(undefined)
591
592 const injectedSlice = slice.injectInto(combinedReducer)
593
594 // selector returns initial state if undefined in real state
595 expect(injectedSlice.selectSlice(uninjectedState)).toBe(
596 slice.getInitialState(),
597 )
598 expect(injectedSlice.selectors.selectMultiple({}, 1)).toBe(
599 slice.getInitialState(),
600 )
601 expect(injectedSlice.getSelectors().selectMultiple(undefined, 1)).toBe(
602 slice.getInitialState(),
603 )
604
605 const injectedState = combinedReducer(undefined, increment())
606
607 expect(injectedSlice.selectSlice(injectedState)).toBe(
608 slice.getInitialState() + 1,
609 )
610 expect(injectedSlice.selectors.selectMultiple(injectedState, 1)).toBe(
611 slice.getInitialState() + 1,
612 )
613 })
614 it('allows providing a custom name to inject under', () => {
615 const slice = createSlice({
616 name: 'counter',
617 reducerPath: 'injected',
618 initialState: 42,
619 reducers: {
620 increment: (state) => ++state,
621 },
622 selectors: {
623 selectMultiple: (state, multiplier: number) => state * multiplier,
624 },
625 })
626
627 const { increment } = slice.actions
628
629 const combinedReducer = combineSlices({
630 static: slice.reducer,
631 }).withLazyLoadedSlices<WithSlice<typeof slice> & { injected2: number }>()
632
633 const uninjectedState = combinedReducer(undefined, increment())
634
635 expect(uninjectedState.injected).toBe(undefined)
636
637 const injected = slice.injectInto(combinedReducer)
638
639 const injectedState = combinedReducer(undefined, increment())
640
641 expect(injected.selectSlice(injectedState)).toBe(
642 slice.getInitialState() + 1,
643 )
644 expect(injected.selectors.selectMultiple(injectedState, 2)).toBe(
645 (slice.getInitialState() + 1) * 2,
646 )
647
648 const injected2 = slice.injectInto(combinedReducer, {
649 reducerPath: 'injected2',
650 })
651
652 const injected2State = combinedReducer(undefined, increment())
653
654 expect(injected2.selectSlice(injected2State)).toBe(
655 slice.getInitialState() + 1,
656 )
657 expect(injected2.selectors.selectMultiple(injected2State, 2)).toBe(
658 (slice.getInitialState() + 1) * 2,
659 )
660 })
661 it('avoids incorrectly caching selectors', () => {
662 const slice = createSlice({
663 name: 'counter',
664 reducerPath: 'injected',
665 initialState: 42,
666 reducers: {
667 increment: (state) => ++state,
668 },
669 selectors: {
670 selectMultiple: (state, multiplier: number) => state * multiplier,
671 },
672 })
673 expect(slice.getSelectors()).toBe(slice.getSelectors())
674 const combinedReducer = combineSlices({
675 static: slice.reducer,
676 }).withLazyLoadedSlices<WithSlice<typeof slice>>()
677
678 const injected = slice.injectInto(combinedReducer)
679
680 expect(injected.getSelectors()).not.toBe(slice.getSelectors())
681
682 expect(injected.getSelectors().selectMultiple(undefined, 1)).toBe(42)
683
684 expect(() =>
685 // @ts-expect-error
686 slice.getSelectors().selectMultiple(undefined, 1),
687 ).toThrowErrorMatchingInlineSnapshot(
688 `[Error: selectState returned undefined for an uninjected slice reducer]`,
689 )
690
691 const injected2 = slice.injectInto(combinedReducer, {
692 reducerPath: 'other',
693 })
694
695 // can use same cache for localised selectors
696 expect(injected.getSelectors()).toBe(injected2.getSelectors())
697 // these should be different
698 expect(injected.selectors).not.toBe(injected2.selectors)
699 })
700 it('caches initial states for selectors', () => {
701 const slice = createSlice({
702 name: 'counter',
703 initialState: () => ({ value: 0 }),
704 reducers: {},
705 selectors: {
706 selectObj: (state) => state,
707 },
708 })
709 // not cached
710 expect(slice.getInitialState()).not.toBe(slice.getInitialState())
711 expect(slice.reducer(undefined, { type: 'dummy' })).not.toBe(
712 slice.reducer(undefined, { type: 'dummy' }),
713 )
714
715 const combinedReducer = combineSlices({
716 static: slice.reducer,
717 }).withLazyLoadedSlices<WithSlice<typeof slice>>()
718
719 const injected = slice.injectInto(combinedReducer)
720
721 // still not cached
722 expect(injected.getInitialState()).not.toBe(injected.getInitialState())
723 expect(injected.reducer(undefined, { type: 'dummy' })).not.toBe(
724 injected.reducer(undefined, { type: 'dummy' }),
725 )
726 // cached
727 expect(injected.selectSlice({})).toBe(injected.selectSlice({}))
728 expect(injected.selectors.selectObj({})).toBe(
729 injected.selectors.selectObj({}),
730 )
731 })
732 })
733 describe('reducers definition with asyncThunks', () => {
734 it('is disabled by default', () => {
735 expect(() =>
736 createSlice({
737 name: 'test',
738 initialState: [] as any[],
739 reducers: (create) => ({ thunk: create.asyncThunk(() => {}) }),
740 }),
741 ).toThrowErrorMatchingInlineSnapshot(
742 `[Error: Cannot use \`create.asyncThunk\` in the built-in \`createSlice\`. Use \`buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })\` to create a customised version of \`createSlice\`.]`,
743 )
744 })
745 const createAppSlice = buildCreateSlice({
746 creators: { asyncThunk: asyncThunkCreator },
747 })
748 function pending(state: any[], action: any) {
749 state.push(['pendingReducer', action])
750 }
751 function fulfilled(state: any[], action: any) {
752 state.push(['fulfilledReducer', action])
753 }
754 function rejected(state: any[], action: any) {
755 state.push(['rejectedReducer', action])
756 }
757 function settled(state: any[], action: any) {
758 state.push(['settledReducer', action])
759 }
760
761 test('successful thunk', async () => {
762 const slice = createAppSlice({
763 name: 'test',
764 initialState: [] as any[],
765 reducers: (create) => ({
766 thunkReducers: create.asyncThunk(
767 function payloadCreator(arg: string, api) {
768 return Promise.resolve('resolved payload')
769 },
770 { pending, fulfilled, rejected, settled },
771 ),
772 }),
773 })
774
775 const store = configureStore({
776 reducer: slice.reducer,
777 })
778 await store.dispatch(slice.actions.thunkReducers('test'))
779 expect(store.getState()).toMatchObject([
780 [
781 'pendingReducer',
782 {
783 type: 'test/thunkReducers/pending',
784 payload: undefined,
785 },
786 ],
787 [
788 'fulfilledReducer',
789 {
790 type: 'test/thunkReducers/fulfilled',
791 payload: 'resolved payload',
792 },
793 ],
794 [
795 'settledReducer',
796 {
797 type: 'test/thunkReducers/fulfilled',
798 payload: 'resolved payload',
799 },
800 ],
801 ])
802 })
803
804 test('rejected thunk', async () => {
805 const slice = createAppSlice({
806 name: 'test',
807 initialState: [] as any[],
808 reducers: (create) => ({
809 thunkReducers: create.asyncThunk(
810 // payloadCreator isn't allowed to return never
811 function payloadCreator(arg: string, api): any {
812 throw new Error('')
813 },
814 { pending, fulfilled, rejected, settled },
815 ),
816 }),
817 })
818
819 const store = configureStore({
820 reducer: slice.reducer,
821 })
822 await store.dispatch(slice.actions.thunkReducers('test'))
823 expect(store.getState()).toMatchObject([
824 [
825 'pendingReducer',
826 {
827 type: 'test/thunkReducers/pending',
828 payload: undefined,
829 },
830 ],
831 [
832 'rejectedReducer',
833 {
834 type: 'test/thunkReducers/rejected',
835 payload: undefined,
836 },
837 ],
838 [
839 'settledReducer',
840 {
841 type: 'test/thunkReducers/rejected',
842 payload: undefined,
843 },
844 ],
845 ])
846 })
847
848 test('with options', async () => {
849 const slice = createAppSlice({
850 name: 'test',
851 initialState: [] as any[],
852 reducers: (create) => ({
853 thunkReducers: create.asyncThunk(
854 function payloadCreator(arg: string, api) {
855 return 'should not call this'
856 },
857 {
858 options: {
859 condition() {
860 return false
861 },
862 dispatchConditionRejection: true,
863 },
864 pending,
865 fulfilled,
866 rejected,
867 settled,
868 },
869 ),
870 }),
871 })
872
873 const store = configureStore({
874 reducer: slice.reducer,
875 })
876 await store.dispatch(slice.actions.thunkReducers('test'))
877 expect(store.getState()).toMatchObject([
878 [
879 'rejectedReducer',
880 {
881 type: 'test/thunkReducers/rejected',
882 payload: undefined,
883 meta: { condition: true },
884 },
885 ],
886 [
887 'settledReducer',
888 {
889 type: 'test/thunkReducers/rejected',
890 payload: undefined,
891 meta: { condition: true },
892 },
893 ],
894 ])
895 })
896
897 test('has caseReducers for the asyncThunk', async () => {
898 const slice = createAppSlice({
899 name: 'test',
900 initialState: [],
901 reducers: (create) => ({
902 thunkReducers: create.asyncThunk(
903 function payloadCreator(arg, api) {
904 return Promise.resolve('resolved payload')
905 },
906 { pending, fulfilled, settled },
907 ),
908 }),
909 })
910
911 expect(slice.caseReducers.thunkReducers.pending).toBe(pending)
912 expect(slice.caseReducers.thunkReducers.fulfilled).toBe(fulfilled)
913 expect(slice.caseReducers.thunkReducers.settled).toBe(settled)
914 // even though it is not defined above, this should at least be a no-op function to match the TypeScript typings
915 // and should be callable as a reducer even if it does nothing
916 expect(() =>
917 slice.caseReducers.thunkReducers.rejected(
918 [],
919 slice.actions.thunkReducers.rejected(
920 new Error('test'),
921 'fakeRequestId',
922 ),
923 ),
924 ).not.toThrow()
925 })
926
927 test('can define reducer with prepare statement using create.preparedReducer', async () => {
928 const slice = createSlice({
929 name: 'test',
930 initialState: [] as any[],
931 reducers: (create) => ({
932 prepared: create.preparedReducer(
933 (p: string, m: number, e: { message: string }) => ({
934 payload: p,
935 meta: m,
936 error: e,
937 }),
938 (state, action) => {
939 state.push(action)
940 },
941 ),
942 }),
943 })
944
945 expect(
946 slice.reducer(
947 [],
948 slice.actions.prepared('test', 1, { message: 'err' }),
949 ),
950 ).toMatchInlineSnapshot(`
951 [
952 {
953 "error": {
954 "message": "err",
955 },
956 "meta": 1,
957 "payload": "test",
958 "type": "test/prepared",
959 },
960 ]
961 `)
962 })
963
964 test('throws an error when invoked with a normal `prepare` object that has not gone through a `create.preparedReducer` call', async () => {
965 expect(() =>
966 createSlice({
967 name: 'test',
968 initialState: [] as any[],
969 reducers: (create) => ({
970 prepared: {
971 prepare: (p: string, m: number, e: { message: string }) => ({
972 payload: p,
973 meta: m,
974 error: e,
975 }),
976 reducer: (state, action) => {
977 state.push(action)
978 },
979 },
980 }),
981 }),
982 ).toThrowErrorMatchingInlineSnapshot(
983 `[Error: Please use the \`create.preparedReducer\` notation for prepared action creators with the \`create\` notation.]`,
984 )
985 })
986 })
987})
Note: See TracBrowser for help on using the repository browser.