From 39cd305056670ba429107fcefa1bc1f391776016 Mon Sep 17 00:00:00 2001 From: Andreas Gasser Date: Wed, 3 Apr 2019 15:49:00 +0200 Subject: [PATCH] feat(#104): added / improved test cases for main redux code --- src/redux/__tests__/configureStore.test.js | 55 +++++++++++++++++++--- src/redux/__tests__/rootReducer.test.js | 55 ++++++++++++++++++++++ src/redux/application/index.js | 4 ++ src/redux/configureStore.js | 36 +++++++++----- src/redux/rootReducer.js | 7 ++- 5 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 src/redux/__tests__/rootReducer.test.js diff --git a/src/redux/__tests__/configureStore.test.js b/src/redux/__tests__/configureStore.test.js index 611459a..d736b74 100644 --- a/src/redux/__tests__/configureStore.test.js +++ b/src/redux/__tests__/configureStore.test.js @@ -1,15 +1,18 @@ -import thunkMiddleware from 'redux-thunk'; +/* global testUtils */ import { compose } from 'redux'; -import { createMiddleware as createErrorMiddleware } from '../../util/ErrorHandler'; - -import configureStore, { __testables__ } from '../configureStore'; // +import configureStore, { __testables__ } from '../configureStore'; +import { + applicationMessageAdd, + applicationMessageShow, +} from '../application/message'; + const { getComposeEnhancers, getEnhancers, - getMiddleware, -} = __testables__; // + getErrorOptions, +} = __testables__; it('should create redux store', () => { const { store } = configureStore(); @@ -83,3 +86,43 @@ describe('redux getEnhancers test suite', () => { expect(getEnhancers()).toEqual([]); }); }); + +describe('redux getErrorOptions test sutie', () => { + const mockstore = testUtils.createMockStore(); + + it('should return object with error middleware options', () => { + const options = getErrorOptions(); + expect(options).toBeTruthy(); + + // check for attributes + const { logToUI } = options; + expect(logToUI).toBeInstanceOf(Function); + }); + + it('logToUI should fire "addMessage" redux action', () => { + const error = { + title: 'Error', + detail: 'Error detail', + }; + + const store = mockstore(); + const { dispatch } = store; + + const options = getErrorOptions(); + const { logToUI } = options; + + const expectedActions = [ + applicationMessageAdd({ + title: error.title, + text: JSON.stringify(error.detail), + showRefresh: true, + }), + applicationMessageShow(), + ]; + + // fire action + logToUI(error, dispatch); + + expect(store.getActions()).toEqual(expectedActions); + }); +}); diff --git a/src/redux/__tests__/rootReducer.test.js b/src/redux/__tests__/rootReducer.test.js new file mode 100644 index 0000000..571229a --- /dev/null +++ b/src/redux/__tests__/rootReducer.test.js @@ -0,0 +1,55 @@ +import rootReducer from '../rootReducer'; + +import { + appIdle, + appReset, + __testables__ as applicationTestables, +} from '../application'; +import { + __testables__ as authTestables +} from '../auth'; + +const { applicationDidLoad } = applicationTestables; +const { authSetToken } = authTestables; + +describe('root reducer test suite', () => { + let mockedDateNow; + + beforeAll(() => { + const now = Date.now(); + mockedDateNow = jest.spyOn(Date, 'now').mockImplementation(() => now); + }); + + afterAll(() => { + mockedDateNow.restoreMock(); + }); + + const firstLevelKeys = [ + 'appTime', + 'application', + 'auth', + 'images', + 'labels', + 'faces', + 'user', + ]; + + it('should return root redux tree', () => { + const rootState = rootReducer(undefined, appIdle()); + expect(Object.keys(rootState)).toEqual(firstLevelKeys); + }); + + it('should reset reducer data on APP_RESET', () => { + // fire some actions to reducer + const rootState1 = rootReducer(undefined, appIdle()); + const rootState2 = rootReducer(rootState1, applicationDidLoad()); + const rootState3 = rootReducer(rootState2, authSetToken('invalid token')); + + // check for non empty redux state + expect(rootState3).not.toEqual(rootReducer(undefined, appIdle)); + + // fire reset and check again + expect(rootReducer(rootState3, appReset())) + .toEqual(rootState1); + }); +}); diff --git a/src/redux/application/index.js b/src/redux/application/index.js index 6a1485c..273d823 100644 --- a/src/redux/application/index.js +++ b/src/redux/application/index.js @@ -98,6 +98,10 @@ const status = (state = AppStatus.INITIAL, action) => { } }; +export const __testables__ = { + applicationWillLoad, + applicationDidLoad, +}; export default combineReducers({ status, diff --git a/src/redux/configureStore.js b/src/redux/configureStore.js index 0460f10..75510c8 100644 --- a/src/redux/configureStore.js +++ b/src/redux/configureStore.js @@ -50,18 +50,19 @@ const getComposeEnhancers = () => { const getEnhancers = () => []; +const getErrorOptions = () => ({ + logToUI: (error, dispatch) => { + const message = { + title: error.title, + text: error.detail ? JSON.stringify(error.detail) : null, + showRefresh: true, + }; + addMessage(message)(dispatch); + } +}); + const configureStore = (initialState = {}) => { - // configure error middleware - const errorOptions = { - logToUI: (error, dispatch) => { - const message = { - title: error.title, - text: error.detail ? JSON.stringify(error.detail) : null, - showRefresh: true, - }; - addMessage(message)(dispatch); - } - }; + const errorOptions = getErrorOptions(); const errorMiddleware = createMiddleware(errorOptions); const middleware = [ @@ -74,7 +75,13 @@ const configureStore = (initialState = {}) => { * Not the best design but works for now, redo later on * or in next project find different solution for that... */ - onAuthError: (message) => store.dispatch(logOutUser(message)), //AUTH_LOG_OUT + /* istanbul ignore next */ + onAuthError: (message) => + /* istanbul ignore next */ + store.dispatch( + /* istanbul ignore next */ + logOutUser(message) + ), //AUTH_LOG_OUT }), }), errorMiddleware, @@ -99,15 +106,19 @@ const configureStore = (initialState = {}) => { store.subscribe(configureReactors(store, reactors)); // idle configuration + /* istanbul ignore next */ const idleDispatcher = () => { + /* istanbul ignore next */ store.dispatch({ type: APP_IDLE }); }; // debounce app idle all 30 seconds + /* istanbul ignore next */ const deBounced = debounce(() => { // The requestAnimationFrame ensures it doesn't run when tab isn't active // the requestIdleCallback makes sure the browser isn't busy with something // else. + /* istanbul ignore next */ requestAnimationFrame(() => ric(idleDispatcher, { timeout: 500 })); }, 30000); @@ -131,6 +142,7 @@ export const __testables__ = { // eslint-disable-line no-underscore-dangle getEnhancers, getComposeEnhancers, getPersistedReducer, + getErrorOptions, }; export default configureStore; diff --git a/src/redux/rootReducer.js b/src/redux/rootReducer.js index ba77ec7..32439c7 100644 --- a/src/redux/rootReducer.js +++ b/src/redux/rootReducer.js @@ -9,7 +9,12 @@ import facesReducer from './faces'; import userReducer from './user'; const reducers = combineReducers({ - appTime: Date.now, + /** + * Do not use Date.now as pointer to prevent + * jest mock issues (by using pointer jest mock cannot + * replace function call) + */ + appTime: () => Date.now(), application: applicationReducer, auth: authReducer, images: imagesReducer,