UNPKG

6.83 kBPlain TextView Raw
1import type { PayloadAction } from '@reduxjs/toolkit'
2import { createSlice, createAction } from '@reduxjs/toolkit'
3
4describe('createSlice', () => {
5 describe('when slice is undefined', () => {
6 it('should throw an error', () => {
7 expect(() =>
8 // @ts-ignore
9 createSlice({
10 reducers: {
11 increment: (state) => state + 1,
12 multiply: (state, action: PayloadAction<number>) =>
13 state * action.payload,
14 },
15 initialState: 0,
16 })
17 ).toThrowError()
18 })
19 })
20
21 describe('when slice is an empty string', () => {
22 it('should throw an error', () => {
23 expect(() =>
24 createSlice({
25 name: '',
26 reducers: {
27 increment: (state) => state + 1,
28 multiply: (state, action: PayloadAction<number>) =>
29 state * action.payload,
30 },
31 initialState: 0,
32 })
33 ).toThrowError()
34 })
35 })
36
37 describe('when passing slice', () => {
38 const { actions, reducer, caseReducers } = createSlice({
39 reducers: {
40 increment: (state) => state + 1,
41 },
42 initialState: 0,
43 name: 'cool',
44 })
45
46 it('should create increment action', () => {
47 expect(actions.hasOwnProperty('increment')).toBe(true)
48 })
49
50 it('should have the correct action for increment', () => {
51 expect(actions.increment()).toEqual({
52 type: 'cool/increment',
53 payload: undefined,
54 })
55 })
56
57 it('should return the correct value from reducer', () => {
58 expect(reducer(undefined, actions.increment())).toEqual(1)
59 })
60
61 it('should include the generated case reducers', () => {
62 expect(caseReducers).toBeTruthy()
63 expect(caseReducers.increment).toBeTruthy()
64 expect(typeof caseReducers.increment).toBe('function')
65 })
66 })
67
68 describe('when mutating state object', () => {
69 const initialState = { user: '' }
70
71 const { actions, reducer } = createSlice({
72 reducers: {
73 setUserName: (state, action) => {
74 state.user = action.payload
75 },
76 },
77 initialState,
78 name: 'user',
79 })
80
81 it('should set the username', () => {
82 expect(reducer(initialState, actions.setUserName('eric'))).toEqual({
83 user: 'eric',
84 })
85 })
86 })
87
88 describe('when passing extra reducers', () => {
89 const addMore = createAction<{ amount: number }>('ADD_MORE')
90
91 const { reducer } = createSlice({
92 name: 'test',
93 reducers: {
94 increment: (state) => state + 1,
95 multiply: (state, action) => state * action.payload,
96 },
97 extraReducers: {
98 [addMore.type]: (state, action) => state + action.payload.amount,
99 },
100 initialState: 0,
101 })
102
103 it('should call extra reducers when their actions are dispatched', () => {
104 const result = reducer(10, addMore({ amount: 5 }))
105
106 expect(result).toBe(15)
107 })
108
109 describe('alternative builder callback for extraReducers', () => {
110 const increment = createAction<number, 'increment'>('increment')
111
112 test('can be used with actionCreators', () => {
113 const slice = createSlice({
114 name: 'counter',
115 initialState: 0,
116 reducers: {},
117 extraReducers: (builder) =>
118 builder.addCase(
119 increment,
120 (state, action) => state + action.payload
121 ),
122 })
123 expect(slice.reducer(0, increment(5))).toBe(5)
124 })
125
126 test('can be used with string action types', () => {
127 const slice = createSlice({
128 name: 'counter',
129 initialState: 0,
130 reducers: {},
131 extraReducers: (builder) =>
132 builder.addCase(
133 'increment',
134 (state, action: { type: 'increment'; payload: number }) =>
135 state + action.payload
136 ),
137 })
138 expect(slice.reducer(0, increment(5))).toBe(5)
139 })
140
141 test('prevents the same action type from being specified twice', () => {
142 expect(() =>
143 createSlice({
144 name: 'counter',
145 initialState: 0,
146 reducers: {},
147 extraReducers: (builder) =>
148 builder
149 .addCase('increment', (state) => state + 1)
150 .addCase('increment', (state) => state + 1),
151 })
152 ).toThrowErrorMatchingInlineSnapshot(
153 `"addCase cannot be called with two reducers for the same action type"`
154 )
155 })
156
157 test('can be used with addMatcher and type guard functions', () => {
158 const slice = createSlice({
159 name: 'counter',
160 initialState: 0,
161 reducers: {},
162 extraReducers: (builder) =>
163 builder.addMatcher(
164 increment.match,
165 (state, action: { type: 'increment'; payload: number }) =>
166 state + action.payload
167 ),
168 })
169 expect(slice.reducer(0, increment(5))).toBe(5)
170 })
171
172 test('can be used with addDefaultCase', () => {
173 const slice = createSlice({
174 name: 'counter',
175 initialState: 0,
176 reducers: {},
177 extraReducers: (builder) =>
178 builder.addDefaultCase((state, action) => state + action.payload),
179 })
180 expect(slice.reducer(0, increment(5))).toBe(5)
181 })
182
183 // for further tests, see the test of createReducer that goes way more into depth on this
184 })
185 })
186
187 describe('behaviour with enhanced case reducers', () => {
188 it('should pass all arguments to the prepare function', () => {
189 const prepare = jest.fn((payload, somethingElse) => ({ payload }))
190
191 const testSlice = createSlice({
192 name: 'test',
193 initialState: 0,
194 reducers: {
195 testReducer: {
196 reducer: (s) => s,
197 prepare,
198 },
199 },
200 })
201
202 expect(testSlice.actions.testReducer('a', 1)).toEqual({
203 type: 'test/testReducer',
204 payload: 'a',
205 })
206 expect(prepare).toHaveBeenCalledWith('a', 1)
207 })
208
209 it('should call the reducer function', () => {
210 const reducer = jest.fn(() => 5)
211
212 const testSlice = createSlice({
213 name: 'test',
214 initialState: 0,
215 reducers: {
216 testReducer: {
217 reducer,
218 prepare: (payload: any) => ({ payload }),
219 },
220 },
221 })
222
223 testSlice.reducer(0, testSlice.actions.testReducer('testPayload'))
224 expect(reducer).toHaveBeenCalledWith(
225 0,
226 expect.objectContaining({ payload: 'testPayload' })
227 )
228 })
229 })
230})