This repository has been archived by the owner on Jan 16, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
provide original store slice state before in went through Tesler redu…
…cer as a reducer argument to allow overriding built-in behavior
- Loading branch information
Showing
6 changed files
with
167 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { $do } from '../../actions/actions' | ||
import { configureStore } from '../configureStore' | ||
import * as router from '../../Provider' | ||
|
||
jest.spyOn(router, 'parseLocation').mockImplementation(() => { | ||
return { screenName: null, viewName: null, type: null, path: null, params: null } | ||
}) | ||
|
||
describe('configureStore', () => { | ||
it('handles built-in actions by built-in reducers', () => { | ||
const store = configureStore({}, null, false, null) | ||
expect(store.getState().session.active).toBe(false) | ||
store.dispatch($do.loginDone({ screens: null })) | ||
expect(store.getState().session.active).toBe(true) | ||
}) | ||
|
||
it('applies custom reducer after Tesler built-in reducer', () => { | ||
const mock = jest.fn() | ||
const storeInstance = configureStore( | ||
{ | ||
session: { | ||
initialState: {}, | ||
reducer: (state, action, store, originalState) => { | ||
mock('success') | ||
return state | ||
} | ||
} | ||
}, | ||
null, | ||
false, | ||
null | ||
) | ||
expect(storeInstance.getState().session.active).toBe(false) | ||
storeInstance.dispatch($do.loginDone({ screens: null })) | ||
expect(storeInstance.getState().session.active).toBe(true) | ||
expect(mock).toBeCalledWith('success') | ||
}) | ||
|
||
it('allows custom reducer to override built-in implementation ', () => { | ||
const mock = jest.fn() | ||
const storeInstance = configureStore( | ||
{ | ||
session: { | ||
initialState: {}, | ||
reducer: (state, action, store, originalState) => { | ||
mock('success') | ||
return originalState | ||
} | ||
} | ||
}, | ||
null, | ||
false, | ||
null | ||
) | ||
expect(storeInstance.getState().session.active).toBe(false) | ||
storeInstance.dispatch($do.loginDone({ screens: null })) | ||
expect(storeInstance.getState().session.active).toBe(false) | ||
expect(mock).toBeCalledWith('success') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { Action, applyMiddleware, compose, createStore, Middleware, Store, StoreCreator } from 'redux' | ||
import { createEpicMiddleware, Epic, combineEpics as legacyCombineEpics } from 'redux-observable' | ||
import { reducers as coreReducers } from '../reducers/index' | ||
import combineEpics from '../utils/combineEpics' | ||
import { legacyCoreEpics } from '../epics' | ||
import { combineMiddlewares } from '../utils/combineMiddlewares' | ||
import { middlewares as coreMiddlewares } from '../middlewares' | ||
import CustomEpics, { isLegacyCustomEpics } from '../interfaces/customEpics' | ||
import { combineReducers } from '../utils/redux' | ||
import { AnyAction } from '../actions/actions' | ||
import { ClientReducersMapObject, CombinedReducersMapObject, CoreReducer, Store as CoreStore } from '../interfaces/store' | ||
import { CustomMiddlewares } from '../interfaces/customMiddlewares' | ||
|
||
/** | ||
* TODO | ||
* | ||
* @param storeCreator | ||
*/ | ||
function withLogger(storeCreator: StoreCreator): StoreCreator { | ||
return (window as any).devToolsExtension ? (window as any).devToolsExtension()(storeCreator) : storeCreator | ||
} | ||
|
||
/** | ||
* Configures Redux store by apply redux-observable epic middleware and custom version of `combineReducers` function | ||
* | ||
* @param customReducers Client application reducers | ||
* @param customEpics Client application epics | ||
* @param useEpics Can be set to `false` if client application does not provide redux-observable peer dependency | ||
* and does not rely on Tesler epics (e.g. importing only UI components) | ||
* @param customMiddlewares Any additional middlewares provided by client application | ||
*/ | ||
export function configureStore<ClientState, ClientActions extends Action<any>>( | ||
customReducers = {} as ClientReducersMapObject<ClientState, ClientActions>, | ||
customEpics: CustomEpics | Epic<any, ClientState> = null, | ||
useEpics = true, | ||
customMiddlewares: CustomMiddlewares = null | ||
): Store<ClientState & CoreStore> { | ||
type CombinedActions = AnyAction & ClientActions | ||
// If core reducer slices have a matching client app reducer slice | ||
// launch the core first and then client | ||
// TODO: Extract this to an utility | ||
const reducers = { ...coreReducers } as CombinedReducersMapObject<CoreStore & ClientState, CombinedActions> | ||
Object.keys(customReducers).forEach((reducerName: Extract<keyof ClientState, string>) => { | ||
const coreInitialState = coreReducers[reducerName]?.(undefined, { type: ' UNKNOWN ACTION ' }) | ||
const reducerInitialState = { | ||
...(coreInitialState || ({} as ClientState)), | ||
...customReducers[reducerName].initialState | ||
} | ||
|
||
if (reducers[reducerName as keyof ClientState] && !customReducers[reducerName].override) { | ||
const combined: CoreReducer<ClientState[keyof ClientState], CombinedActions> = ( | ||
state = reducerInitialState, | ||
action, | ||
getStore | ||
) => { | ||
const storeAfterCore = coreReducers[reducerName](state, action, getStore) | ||
return customReducers[reducerName as keyof ClientState].reducer(storeAfterCore, action, getStore, state) | ||
} | ||
reducers[reducerName as keyof ClientState] = combined | ||
} else { | ||
reducers[reducerName as keyof ClientState] = customReducers[reducerName].reducer | ||
} | ||
}) | ||
|
||
const middlewares: Middleware[] = combineMiddlewares(coreMiddlewares, customMiddlewares) | ||
|
||
if (useEpics) { | ||
const epics = isLegacyCustomEpics(customEpics) ? legacyCombineEpics(legacyCoreEpics, customEpics) : combineEpics(customEpics) | ||
middlewares.push(createEpicMiddleware(epics)) | ||
} | ||
return compose(applyMiddleware(...middlewares))(withLogger(createStore))(combineReducers(reducers)) | ||
} |