import type { StoreEnhancer, StoreEnhancerStoreCreator } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import * as RTK from '@reduxjs/toolkit' import * as redux from 'redux' import * as devtools from '@internal/devtoolsExtension' describe('configureStore', () => { jest.spyOn(redux, 'applyMiddleware') jest.spyOn(redux, 'combineReducers') jest.spyOn(redux, 'compose') jest.spyOn(redux, 'createStore') jest.spyOn(devtools, 'composeWithDevTools') // @remap-prod-remove-line const reducer: redux.Reducer = (state = {}, _action) => state beforeEach(() => jest.clearAllMocks()) describe('given a function reducer', () => { it('calls createStore with the reducer', () => { configureStore({ reducer }) expect(configureStore({ reducer })).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalled() expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) }) describe('given an object of reducers', () => { it('calls createStore with the combined reducers', () => { const reducer = { reducer() { return true }, } expect(configureStore({ reducer })).toBeInstanceOf(Object) expect(redux.combineReducers).toHaveBeenCalledWith(reducer) expect(redux.applyMiddleware).toHaveBeenCalled() expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( expect.any(Function), undefined, expect.any(Function) ) }) }) describe('given no reducer', () => { it('throws', () => { expect(configureStore).toThrow( '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' ) }) }) describe('given no middleware', () => { it('calls createStore without any middleware', () => { expect(configureStore({ middleware: [], reducer })).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) }) describe('given undefined middleware', () => { it('calls createStore with default middleware', () => { expect(configureStore({ middleware: undefined, reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalledWith( expect.any(Function), // thunk expect.any(Function), // immutableCheck expect.any(Function) // serializableCheck ) expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) }) describe('given a middleware creation function that returns undefined', () => { it('throws an error', () => { const invalidBuilder = jest.fn((getDefaultMiddleware) => undefined as any) expect(() => configureStore({ middleware: invalidBuilder, reducer }) ).toThrow( 'when using a middleware builder function, an array of middleware must be returned' ) }) }) describe('given a middleware creation function that returns an array with non-functions', () => { it('throws an error', () => { const invalidBuilder = jest.fn((getDefaultMiddleware) => [true] as any) expect(() => configureStore({ middleware: invalidBuilder, reducer }) ).toThrow('each middleware provided to configureStore must be a function') }) }) describe('given custom middleware that contains non-functions', () => { it('throws an error', () => { expect(() => configureStore({ middleware: [true] as any, reducer }) ).toThrow('each middleware provided to configureStore must be a function') }) }) describe('given custom middleware', () => { it('calls createStore with custom middleware and without default middleware', () => { const thank: redux.Middleware = (_store) => (next) => (action) => next(action) expect(configureStore({ middleware: [thank], reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) }) describe('middleware builder notation', () => { it('calls builder, passes getDefaultMiddleware and uses returned middlewares', () => { const thank = jest.fn( ((_store) => (next) => (action) => 'foobar') as redux.Middleware ) const builder = jest.fn((getDefaultMiddleware) => { expect(getDefaultMiddleware).toEqual(expect.any(Function)) expect(getDefaultMiddleware()).toEqual(expect.any(Array)) return [thank] }) const store = configureStore({ middleware: builder, reducer }) expect(builder).toHaveBeenCalled() expect(store.dispatch({ type: 'test' })).toBe('foobar') }) }) describe('with devTools disabled', () => { it('calls createStore without devTools enhancer', () => { expect(configureStore({ devTools: false, reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalled() expect(redux.compose).toHaveBeenCalled() expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) }) describe('with devTools options', () => { it('calls createStore with devTools enhancer and option', () => { const options = { name: 'myApp', trace: true, } expect(configureStore({ devTools: options, reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalled() expect(devtools.composeWithDevTools).toHaveBeenCalledWith(options) // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) }) describe('given preloadedState', () => { it('calls createStore with preloadedState', () => { expect(configureStore({ reducer })).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalled() expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) }) describe('given enhancers', () => { it('calls createStore with enhancers', () => { const enhancer: redux.StoreEnhancer = (next) => next expect(configureStore({ enhancers: [enhancer], reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalled() expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) }) it('accepts a callback for customizing enhancers', () => { let dummyEnhancerCalled = false const dummyEnhancer: StoreEnhancer = (createStore: StoreEnhancerStoreCreator) => (reducer, ...args: any[]) => { dummyEnhancerCalled = true return createStore(reducer, ...args) } const reducer = () => ({}) const store = configureStore({ reducer, enhancers: (defaultEnhancers) => { return [...defaultEnhancers, dummyEnhancer] }, }) expect(dummyEnhancerCalled).toBe(true) }) }) })