Skip to content

Commit 379c6f1

Browse files
committed
add alternative api for extraReducers, move shared type tests to mapBuilders.typetest.ts
1 parent 6de0dd0 commit 379c6f1

File tree

5 files changed

+142
-47
lines changed

5 files changed

+142
-47
lines changed

src/createSlice.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,57 @@ describe('createSlice', () => {
105105

106106
expect(result).toBe(15)
107107
})
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+
// for further tests, see the test of createReducer that goes way more into depth on this
158+
})
108159
})
109160

110161
describe('behaviour with enhanced case reducers', () => {

src/createSlice.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import {
88
ActionCreatorWithPreparedPayload
99
} from './createAction'
1010
import { createReducer, CaseReducers, CaseReducer } from './createReducer'
11+
import {
12+
ActionReducerMapBuilder,
13+
executeReducerBuilderCallback
14+
} from './mapBuilders'
1115

1216
/**
1317
* An action creator atttached to a slice.
@@ -73,7 +77,9 @@ export interface CreateSliceOptions<
7377
* functions. These reducers should have existing action types used
7478
* as the keys, and action creators will _not_ be generated.
7579
*/
76-
extraReducers?: CaseReducers<NoInfer<State>, any>
80+
extraReducers?:
81+
| CaseReducers<NoInfer<State>, any>
82+
| ((builder: ActionReducerMapBuilder<NoInfer<State>>) => void)
7783
}
7884

7985
type PayloadActions<Types extends keyof any = string> = Record<
@@ -201,7 +207,13 @@ export function createSlice<
201207
throw new Error('`name` is a required option for createSlice')
202208
}
203209
const reducers = options.reducers || {}
204-
const extraReducers = options.extraReducers || {}
210+
const extraReducers =
211+
typeof options.extraReducers === 'undefined'
212+
? {}
213+
: typeof options.extraReducers === 'function'
214+
? executeReducerBuilderCallback(options.extraReducers)
215+
: options.extraReducers
216+
205217
const reducerNames = Object.keys(reducers)
206218

207219
const sliceCaseReducersByName: Record<string, CaseReducer> = {}

type-tests/files/createReducer.typetest.ts

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Reducer } from 'redux'
2-
import { createReducer, createAction } from '../../src'
2+
import { createReducer, createAction, ActionReducerMapBuilder } from '../../src'
33

44
function expectType<T>(p: T) {}
55

@@ -67,49 +67,10 @@ function expectType<T>(p: T) {}
6767
/** Test: alternative builder callback for actionMap */
6868
{
6969
const increment = createAction<number, 'increment'>('increment')
70-
const decrement = createAction<number, 'decrement'>('decrement')
71-
72-
const reducer = createReducer(0, builder => {
73-
builder.addCase(increment, (state, action) => {
74-
expectType<number>(state)
75-
expectType<{ type: 'increment'; payload: number }>(action)
76-
// typings:expect-error
77-
expectType<string>(state)
78-
// typings:expect-error
79-
expectType<{ type: 'increment'; payload: string }>(action)
80-
// typings:expect-error
81-
expectType<{ type: 'decrement'; payload: number }>(action)
82-
})
83-
84-
builder.addCase('increment', (state, action) => {
85-
expectType<number>(state)
86-
expectType<{ type: 'increment' }>(action)
87-
// typings:expect-error
88-
expectType<{ type: 'decrement' }>(action)
89-
// typings:expect-error - this cannot be inferred and has to be manually specified
90-
expectType<{ type: 'increment'; payload: number }>(action)
91-
})
92-
93-
builder.addCase(
94-
increment,
95-
(state, action: ReturnType<typeof increment>) => state
96-
)
97-
// typings:expect-error
98-
builder.addCase(
99-
increment,
100-
(state, action: ReturnType<typeof decrement>) => state
101-
)
102-
103-
builder.addCase(
104-
'increment',
105-
(state, action: ReturnType<typeof increment>) => state
106-
)
107-
// typings:expect-error
108-
builder.addCase(
109-
'decrement',
110-
(state, action: ReturnType<typeof increment>) => state
111-
)
112-
})
70+
71+
const reducer = createReducer(0, builder =>
72+
expectType<ActionReducerMapBuilder<number>>(builder)
73+
)
11374

11475
expectType<number>(reducer(0, increment(5)))
11576
// typings:expect-error

type-tests/files/createSlice.typetest.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { AnyAction, Reducer, Action } from 'redux'
2-
import { createSlice, PayloadAction, createAction } from '../../src'
2+
import {
3+
createSlice,
4+
PayloadAction,
5+
createAction,
6+
ActionReducerMapBuilder
7+
} from '../../src'
38

49
function expectType<T>(t: T) {
510
return t
@@ -272,3 +277,15 @@ function expectType<T>(t: T) {
272277
expectType<string>(x.payload)
273278
}
274279
}
280+
281+
/** Test: alternative builder callback for extraReducers */
282+
{
283+
createSlice({
284+
name: 'test',
285+
initialState: 0,
286+
reducers: {},
287+
extraReducers: builder => {
288+
expectType<ActionReducerMapBuilder<number>>(builder)
289+
}
290+
})
291+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { executeReducerBuilderCallback } from 'src/mapBuilders'
2+
import { createAction, CaseReducers } from 'src'
3+
4+
function expectType<T>(t: T) {
5+
return t
6+
}
7+
8+
/** Test: alternative builder callback for actionMap */
9+
{
10+
const increment = createAction<number, 'increment'>('increment')
11+
const decrement = createAction<number, 'decrement'>('decrement')
12+
13+
executeReducerBuilderCallback<number>(builder => {
14+
builder.addCase(increment, (state, action) => {
15+
expectType<number>(state)
16+
expectType<{ type: 'increment'; payload: number }>(action)
17+
// typings:expect-error
18+
expectType<string>(state)
19+
// typings:expect-error
20+
expectType<{ type: 'increment'; payload: string }>(action)
21+
// typings:expect-error
22+
expectType<{ type: 'decrement'; payload: number }>(action)
23+
})
24+
25+
builder.addCase('increment', (state, action) => {
26+
expectType<number>(state)
27+
expectType<{ type: 'increment' }>(action)
28+
// typings:expect-error
29+
expectType<{ type: 'decrement' }>(action)
30+
// typings:expect-error - this cannot be inferred and has to be manually specified
31+
expectType<{ type: 'increment'; payload: number }>(action)
32+
})
33+
34+
builder.addCase(
35+
increment,
36+
(state, action: ReturnType<typeof increment>) => state
37+
)
38+
// typings:expect-error
39+
builder.addCase(
40+
increment,
41+
(state, action: ReturnType<typeof decrement>) => state
42+
)
43+
44+
builder.addCase(
45+
'increment',
46+
(state, action: ReturnType<typeof increment>) => state
47+
)
48+
// typings:expect-error
49+
builder.addCase(
50+
'decrement',
51+
(state, action: ReturnType<typeof increment>) => state
52+
)
53+
})
54+
}

0 commit comments

Comments
 (0)