1 | import type { PayloadAction } from '@reduxjs/toolkit'
|
2 | import { createSlice, createAction } from '@reduxjs/toolkit'
|
3 |
|
4 | describe('createSlice', () => {
|
5 | describe('when slice is undefined', () => {
|
6 | it('should throw an error', () => {
|
7 | expect(() =>
|
8 |
|
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 |
|
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 | })
|