UNPKG

10.3 kBPlain TextView Raw
1/* eslint-disable no-lone-blocks */
2import type { Dispatch, AnyAction, Middleware, Reducer, Store } from 'redux'
3import { applyMiddleware } from 'redux'
4import type { PayloadAction } from '@reduxjs/toolkit'
5import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
6import type { ThunkMiddleware, ThunkAction } from 'redux-thunk'
7import thunk, { ThunkDispatch } from 'redux-thunk'
8import { expectNotAny, expectType } from './helpers'
9
10const _anyMiddleware: any = () => () => () => {}
11
12/*
13 * Test: configureStore() requires a valid reducer or reducer map.
14 */
15{
16 configureStore({
17 reducer: (state, action) => 0,
18 })
19
20 configureStore({
21 reducer: {
22 counter1: () => 0,
23 counter2: () => 1,
24 },
25 })
26
27 // @ts-expect-error
28 configureStore({ reducer: 'not a reducer' })
29
30 // @ts-expect-error
31 configureStore({ reducer: { a: 'not a reducer' } })
32
33 // @ts-expect-error
34 configureStore({})
35}
36
37/*
38 * Test: configureStore() infers the store state type.
39 */
40{
41 const reducer: Reducer<number> = () => 0
42 const store = configureStore({ reducer })
43 const numberStore: Store<number, AnyAction> = store
44
45 // @ts-expect-error
46 const stringStore: Store<string, AnyAction> = store
47}
48
49/*
50 * Test: configureStore() infers the store action type.
51 */
52{
53 const reducer: Reducer<number, PayloadAction<number>> = () => 0
54 const store = configureStore({ reducer })
55 const numberStore: Store<number, PayloadAction<number>> = store
56
57 // @ts-expect-error
58 const stringStore: Store<number, PayloadAction<string>> = store
59}
60
61/*
62 * Test: configureStore() accepts middleware array.
63 */
64{
65 const middleware: Middleware = (store) => (next) => next
66
67 configureStore({
68 reducer: () => 0,
69 middleware: [middleware],
70 })
71
72 configureStore({
73 reducer: () => 0,
74 // @ts-expect-error
75 middleware: ['not middleware'],
76 })
77}
78
79/*
80 * Test: configureStore() accepts devTools flag.
81 */
82{
83 configureStore({
84 reducer: () => 0,
85 devTools: true,
86 })
87
88 configureStore({
89 reducer: () => 0,
90 // @ts-expect-error
91 devTools: 'true',
92 })
93}
94
95/*
96 * Test: configureStore() accepts devTools EnhancerOptions.
97 */
98{
99 configureStore({
100 reducer: () => 0,
101 devTools: { name: 'myApp' },
102 })
103
104 configureStore({
105 reducer: () => 0,
106 // @ts-expect-error
107 devTools: { appname: 'myApp' },
108 })
109}
110
111/*
112 * Test: configureStore() accepts preloadedState.
113 */
114{
115 configureStore({
116 reducer: () => 0,
117 preloadedState: 0,
118 })
119
120 configureStore({
121 reducer: () => 0,
122 // @ts-expect-error
123 preloadedState: 'non-matching state type',
124 })
125}
126
127/*
128 * Test: configureStore() accepts store enhancer.
129 */
130{
131 configureStore({
132 reducer: () => 0,
133 enhancers: [applyMiddleware((store) => (next) => next)],
134 })
135
136 configureStore({
137 reducer: () => 0,
138 // @ts-expect-error
139 enhancers: ['not a store enhancer'],
140 })
141}
142
143/**
144 * Test: configureStore() state type inference works when specifying both a
145 * reducer object and a partial preloaded state.
146 */
147{
148 let counterReducer1: Reducer<number> = () => 0
149 let counterReducer2: Reducer<number> = () => 0
150
151 const store = configureStore({
152 reducer: {
153 counter1: counterReducer1,
154 counter2: counterReducer2,
155 },
156 preloadedState: {
157 counter1: 0,
158 },
159 })
160
161 const counter1: number = store.getState().counter1
162 const counter2: number = store.getState().counter2
163}
164
165/**
166 * Test: Dispatch typings
167 */
168{
169 type StateA = number
170 const reducerA = () => 0
171 function thunkA() {
172 return (() => {}) as any as ThunkAction<Promise<'A'>, StateA, any, any>
173 }
174
175 type StateB = string
176 function thunkB() {
177 return (dispatch: Dispatch, getState: () => StateB) => {}
178 }
179 /**
180 * Test: by default, dispatching Thunks is possible
181 */
182 {
183 const store = configureStore({
184 reducer: reducerA,
185 })
186
187 store.dispatch(thunkA())
188 // @ts-expect-error
189 store.dispatch(thunkB())
190 }
191 /**
192 * Test: removing the Thunk Middleware
193 */
194 {
195 const store = configureStore({
196 reducer: reducerA,
197 middleware: [],
198 })
199 // @ts-expect-error
200 store.dispatch(thunkA())
201 // @ts-expect-error
202 store.dispatch(thunkB())
203 }
204 /**
205 * Test: adding the thunk middleware by hand
206 */
207 {
208 const store = configureStore({
209 reducer: reducerA,
210 middleware: [thunk] as [ThunkMiddleware<StateA>],
211 })
212 store.dispatch(thunkA())
213 // @ts-expect-error
214 store.dispatch(thunkB())
215 }
216 /**
217 * Test: using getDefaultMiddleware
218 */
219 {
220 const store = configureStore({
221 reducer: reducerA,
222 middleware: getDefaultMiddleware<StateA>(),
223 })
224
225 store.dispatch(thunkA())
226 // @ts-expect-error
227 store.dispatch(thunkB())
228 }
229 /**
230 * Test: custom middleware
231 */
232 {
233 const store = configureStore({
234 reducer: reducerA,
235 middleware: [] as any as [Middleware<(a: StateA) => boolean, StateA>],
236 })
237 const result: boolean = store.dispatch(5)
238 // @ts-expect-error
239 const result2: string = store.dispatch(5)
240 }
241 /**
242 * Test: multiple custom middleware
243 */
244 {
245 const store = configureStore({
246 reducer: reducerA,
247 middleware: [] as any as [
248 Middleware<(a: 'a') => 'A', StateA>,
249 Middleware<(b: 'b') => 'B', StateA>,
250 ThunkMiddleware<StateA>
251 ],
252 })
253 const result: 'A' = store.dispatch('a')
254 const result2: 'B' = store.dispatch('b')
255 const result3: Promise<'A'> = store.dispatch(thunkA())
256 }
257 /**
258 * Accepts thunk with `unknown`, `undefined` or `null` ThunkAction extraArgument per default
259 */
260 {
261 const store = configureStore({ reducer: {} })
262 // undefined is the default value for the ThunkMiddleware extraArgument
263 store.dispatch(function () {} as ThunkAction<
264 void,
265 {},
266 undefined,
267 AnyAction
268 >)
269 // null was previously documented in the redux docs
270 store.dispatch(function () {} as ThunkAction<void, {}, null, AnyAction>)
271 // unknown is the best way to type a ThunkAction if you do not care
272 // about the value of the extraArgument, as it will always work with every
273 // ThunkMiddleware, no matter the actual extraArgument type
274 store.dispatch(function () {} as ThunkAction<void, {}, unknown, AnyAction>)
275 // @ts-expect-error
276 store.dispatch(function () {} as ThunkAction<void, {}, boolean, AnyAction>)
277 }
278
279 /**
280 * Test: custom middleware and getDefaultMiddleware
281 */
282 {
283 const store = configureStore({
284 reducer: reducerA,
285 middleware: [
286 (() => {}) as any as Middleware<(a: 'a') => 'A', StateA>,
287 ...getDefaultMiddleware<StateA>(),
288 ] as const,
289 })
290 const result1: 'A' = store.dispatch('a')
291 const result2: Promise<'A'> = store.dispatch(thunkA())
292 // @ts-expect-error
293 store.dispatch(thunkB())
294 }
295
296 /**
297 * Test: custom middleware and getDefaultMiddleware, using prepend
298 */
299 {
300 const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware
301 const concatenated = getDefaultMiddleware<StateA>().prepend(otherMiddleware)
302
303 expectType<
304 ReadonlyArray<typeof otherMiddleware | ThunkMiddleware | Middleware<{}>>
305 >(concatenated)
306
307 const store = configureStore({
308 reducer: reducerA,
309 middleware: concatenated,
310 })
311 const result1: 'A' = store.dispatch('a')
312 const result2: Promise<'A'> = store.dispatch(thunkA())
313 // @ts-expect-error
314 store.dispatch(thunkB())
315 }
316
317 /**
318 * Test: custom middleware and getDefaultMiddleware, using concat
319 */
320 {
321 const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware
322 const concatenated = getDefaultMiddleware<StateA>().concat(otherMiddleware)
323
324 expectType<
325 ReadonlyArray<typeof otherMiddleware | ThunkMiddleware | Middleware<{}>>
326 >(concatenated)
327
328 const store = configureStore({
329 reducer: reducerA,
330 middleware: concatenated,
331 })
332 const result1: 'A' = store.dispatch('a')
333 const result2: Promise<'A'> = store.dispatch(thunkA())
334 // @ts-expect-error
335 store.dispatch(thunkB())
336 }
337
338 /**
339 * Test: middlewareBuilder notation, getDefaultMiddleware (unconfigured)
340 */
341 {
342 const store = configureStore({
343 reducer: reducerA,
344 middleware: (getDefaultMiddleware) =>
345 [
346 (() => {}) as any as Middleware<(a: 'a') => 'A', StateA>,
347 ...getDefaultMiddleware(),
348 ] as const,
349 })
350 const result1: 'A' = store.dispatch('a')
351 const result2: Promise<'A'> = store.dispatch(thunkA())
352 // @ts-expect-error
353 store.dispatch(thunkB())
354 }
355
356 /**
357 * Test: middlewareBuilder notation, getDefaultMiddleware, concat & prepend
358 */
359 {
360 const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware
361 const otherMiddleware2: Middleware<(a: 'b') => 'B', StateA> = _anyMiddleware
362 const store = configureStore({
363 reducer: reducerA,
364 middleware: (getDefaultMiddleware) =>
365 getDefaultMiddleware()
366 .concat(otherMiddleware)
367 .prepend(otherMiddleware2),
368 })
369 const result1: 'A' = store.dispatch('a')
370 const result2: Promise<'A'> = store.dispatch(thunkA())
371 const result3: 'B' = store.dispatch('b')
372 // @ts-expect-error
373 store.dispatch(thunkB())
374 }
375
376 /**
377 * Test: middlewareBuilder notation, getDefaultMiddleware (thunk: false)
378 */
379 {
380 const store = configureStore({
381 reducer: reducerA,
382 middleware: (getDefaultMiddleware) =>
383 [
384 (() => {}) as any as Middleware<(a: 'a') => 'A', StateA>,
385 ...getDefaultMiddleware({ thunk: false }),
386 ] as const,
387 })
388 const result1: 'A' = store.dispatch('a')
389 // @ts-expect-error
390 store.dispatch(thunkA())
391 }
392
393 /**
394 * Test: badly typed middleware won't make `dispatch` `any`
395 */
396 {
397 const store = configureStore({
398 reducer: reducerA,
399 middleware: (getDefaultMiddleware) =>
400 getDefaultMiddleware().concat(_anyMiddleware as Middleware<any>),
401 })
402
403 expectNotAny(store.dispatch)
404 }
405}