From c7fee84620bbc7aed6b380d214a676b944f46b94 Mon Sep 17 00:00:00 2001 From: MrEfrem Date: Sun, 27 Nov 2016 17:26:34 +0400 Subject: [PATCH] Improve error messages. Update tests. --- __tests__/createReducer.test.js | 16 ++++++++----- __tests__/enhanceStore.test.js | 24 +++++++++++--------- __tests__/getState.test.js | 1 + __tests__/registerReducers.test.js | 2 +- package.json | 4 ++-- src/createReducer.js | 6 +---- src/enhanceStore.js | 36 +++++++++++++++--------------- src/reduxSetState.js | 20 +++++++---------- src/registerReducers.js | 5 +---- src/utils/normalize.js | 5 ++--- 10 files changed, 59 insertions(+), 60 deletions(-) diff --git a/__tests__/createReducer.test.js b/__tests__/createReducer.test.js index 93b822c..0aefde2 100644 --- a/__tests__/createReducer.test.js +++ b/__tests__/createReducer.test.js @@ -11,7 +11,9 @@ test('Test invalid signature', () => { expect(createReducer).toThrowError('InitialState must be plain object') expect(createReducer.bind(this, { mountPath: 10, initialState: {} })).toThrowError('Mounting path must be string') expect(createReducer.bind(this, { mountPath: 'ui component' })).toThrowError('InitialState must be plain object') + expect(createReducer.bind(this, { mountPath: 'ui component', initialState: Object.create({ a: 1 }) })).toThrowError('InitialState must be plain object') expect(createReducer.bind(this, { mountPath: 'ui component', initialState: {}, listenActions: 123 })).toThrowError('ListenActions must be plain object') + expect(createReducer.bind(this, { mountPath: 'ui component', initialState: {}, listenActions: Object.create({ a: 1 }) })).toThrowError('ListenActions must be plain object') expect(createReducer.bind(this, { mountPath: 'ui component', initialState: {}, connectToStore: null })).toThrowError('ConnectToStore must be boolean') expect(createReducer.bind(this, { mountPath: 'ui component', initialState: {}, persist: null })).toThrowError('Persist must be boolean') expect(createReducer.bind(this, { mountPath: 'ui component', initialState: {}, actionPrefix: 123 })).toThrowError('ActionPrefix must be non empty string') @@ -629,10 +631,14 @@ test('Test signature reduxSetState', () => { class Component extends React.Component { componentDidMount() { const { reduxSetState } = this.props - expect(reduxSetState).toThrowError('Action type must be non empty string') - expect(reduxSetState.bind(this, 'INCREMENT')).toThrowError('New state must be plain object or function') - expect(reduxSetState.bind(this, 'INCREMENT', 1)).toThrowError('New state must be plain object or function') - expect(reduxSetState.bind(this, 'INCREMENT', () => 123)).toThrowError('New state must be non empty plain object') + expect(reduxSetState).toThrowError('ActionType must be non empty string') + expect(reduxSetState.bind(this, 'INCREMENT')).toThrowError('NewState must be non empty plain object or function') + expect(reduxSetState.bind(this, 'INCREMENT', 1)).toThrowError('NewState must be non empty plain object or function') + expect(reduxSetState.bind(this, 'INCREMENT', {})).toThrowError('NewState must be non empty plain object or function') + expect(reduxSetState.bind(this, 'INCREMENT', Object.create({ a: 1 }))).toThrowError('NewState must be non empty plain object or function') + expect(reduxSetState.bind(this, 'INCREMENT', () => 123)).toThrowError('New state returned from function must be non empty plain object') + expect(reduxSetState.bind(this, 'INCREMENT', () => ({}))).toThrowError('New state returned from function must be non empty plain object') + expect(reduxSetState.bind(this, 'INCREMENT', () => Object.create({ a: 1 }))).toThrowError('New state returned from function must be non empty plain object') } render() { return null } @@ -831,7 +837,7 @@ test('Test is invalid to create of reducer in partially same mounting path (2)', - )).toThrowError('Mounting path "ui component main" already busy') + )).toThrowError('Mounting path "ui component" already busy') }) test('Test is valid to create of reducer after create of reducer', () => { diff --git a/__tests__/enhanceStore.test.js b/__tests__/enhanceStore.test.js index 6738c92..a1135a9 100644 --- a/__tests__/enhanceStore.test.js +++ b/__tests__/enhanceStore.test.js @@ -2,19 +2,23 @@ import enhanceStore from '../src/enhanceStore' import { createStore } from 'redux' test('Test invalid signature', () => { - expect(enhanceStore).toThrowError('Create store must be function') - expect(enhanceStore.bind(this, 123)).toThrowError('Create store must be function') - expect(createStore.bind(this, 123, enhanceStore)).toThrowError('The reducers parameter must be non empty plain object') - expect(createStore.bind(this, null, 123, enhanceStore)).toThrowError('Preloaded state must be plain object') + expect(enhanceStore).toThrowError('CreateStore must be function') + expect(enhanceStore.bind(this, 123)).toThrowError('CreateStore must be function') + expect(createStore.bind(this, 123, enhanceStore)).toThrowError('Reducers must be non empty plain object') + expect(createStore.bind(this, {}, enhanceStore)).toThrowError('Reducers must be non empty plain object') + expect(createStore.bind(this, Object.create({ a: () => ({}) }), enhanceStore)).toThrowError('Reducers must be non empty plain object') + expect(createStore.bind(this, null, 123, enhanceStore)).toThrowError('PreloadedState must be plain object') + expect(createStore.bind(this, null, Object.create({}), enhanceStore)).toThrowError('PreloadedState must be plain object') expect(createStore.bind(this, null, null, 123)).toThrow() - expect(createStore.bind(this, { ui: 123 }, enhanceStore)).toThrowError('The values of reducers parameter must be functions') + expect(createStore.bind(this, { ui: 123 }, enhanceStore)).toThrowError('Reducers has to contain functions') }) test('Test invalid signature registerReducers', () => { const store = createStore(null, enhanceStore) - expect(store.registerReducers).toThrowError('The reducers parameter must be non empty plain object') - expect(store.registerReducers.bind(store, {})).toThrowError('The reducers parameter must be non empty plain object') - expect(store.registerReducers.bind(store, { ui: 123 })).toThrowError('The values of reducers parameter must be functions') + expect(store.registerReducers).toThrowError('Reducers must be non empty plain object') + expect(store.registerReducers.bind(store, {})).toThrowError('Reducers must be non empty plain object') + expect(store.registerReducers.bind(store, Object.create({ ui: () => {} }))).toThrowError('Reducers must be non empty plain object') + expect(store.registerReducers.bind(store, { ui: 123 })).toThrowError('Reducers has to contain functions') expect(store.registerReducers.bind(store, { ui: () => {} })).toThrow() }) @@ -37,13 +41,13 @@ test('Test registerReducers', () => { store.registerReducers({ ' ui ': reducer1 }) expect(store.getState().ui).toBe(initialState1) expect(store.registerReducers({ ui: reducer1 })).toBeUndefined() - expect(store.registerReducers.bind(store, { 'ui component ': reducer1 })).toThrowError('Reducer mounting path "ui" already busy') + expect(store.registerReducers.bind(store, { 'ui component ': reducer1 })).toThrowError('Mounting path "ui" already busy') store.dispatch({ type: 'UPDATE-COMPONENT', text: 'My first updated todo 1' }) expect(JSON.stringify(store.getState())).toBe('{\"ui\":{\"text\":\"My first updated todo 1\"}}') store.registerReducers({ ' todo list ': reducer2 }) expect(store.registerReducers({ 'todo list': reducer2 })).toBeUndefined() - expect(store.registerReducers.bind(store, { 'todo ': reducer2 })).toThrowError('Reducer mounting path "todo list" already busy') + expect(store.registerReducers.bind(store, { 'todo ': reducer2 })).toThrowError('Mounting path "todo" already busy') expect(store.registerReducers({ 'todo list1': () => ({}) })).toBeUndefined() expect(JSON.stringify(store.getState())).toBe('{\"ui\":{\"text\":\"My first updated todo 1\"},\"todo\":{\"list\":{\"text\":\"My second todo\"},\"list1\":{}}}') diff --git a/__tests__/getState.test.js b/__tests__/getState.test.js index 81777fc..22fcaff 100644 --- a/__tests__/getState.test.js +++ b/__tests__/getState.test.js @@ -6,6 +6,7 @@ test('Test invalid signature', () => { expect(getState.bind(this, '')).toThrowError('Mounting path must be non empty string') expect(getState('ui component')).toThrowError('State must be plain object') expect(getState('ui component').bind(this, 123)).toThrowError('State must be plain object') + expect(getState('ui component').bind(this, Object.create({ a: 1 }))).toThrowError('State must be plain object') }) test('Test valid signature', () => { diff --git a/__tests__/registerReducers.test.js b/__tests__/registerReducers.test.js index 1e3d8b3..fda8d7a 100644 --- a/__tests__/registerReducers.test.js +++ b/__tests__/registerReducers.test.js @@ -246,7 +246,7 @@ test('Test is invalid to register of reducer in partially same mounting path (2) - )).toThrowError('Mounting path "ui component main" already busy') + )).toThrowError('Mounting path "ui component" already busy') }) test('Test is valid to register of reducer after register of reducer', () => { diff --git a/package.json b/package.json index 80b90d7..17809ec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "redux-fly", - "version": "0.0.2", - "description": "Simple redux", + "version": "0.0.3", + "description": "Reduce Redux boilerplate", "browser": "dist/redux-fly.js", "main": "lib/index.js", "module": "es/index.js", diff --git a/src/createReducer.js b/src/createReducer.js index 71d587a..9154a5c 100644 --- a/src/createReducer.js +++ b/src/createReducer.js @@ -143,11 +143,7 @@ export default ({ } const _mountPath = normalizeMountPath(`${propMountPath || ''} ${mountPath || ''}`) - - if (this.reduxMountPaths.some(path => - ((path.indexOf(_mountPath) === 0 && !((path.substr(_mountPath.length)[0] || '').trim())) || - (_mountPath.indexOf(path) === 0 && !((_mountPath.substr(path.length)[0] || '').trim())))) - ) { + if (this.reduxMountPaths.some(path => path === _mountPath)) { throw new Error(`Mounting path "${_mountPath}" already busy`) } if (this.lastReduxMountPath && _mountPath.indexOf(this.lastReduxMountPath) === -1) { diff --git a/src/enhanceStore.js b/src/enhanceStore.js index a6fa394..da7b0a0 100644 --- a/src/enhanceStore.js +++ b/src/enhanceStore.js @@ -22,29 +22,29 @@ import { normalizeMountPath } from './utils/normalize' */ const enhanceStore = (createStore: Function) => { if (typeof createStore !== 'function') { - throw new Error('Create store must be function') + throw new Error('CreateStore must be function') } return (reducer?: Object, preloadedState?: Object, enhancer?: Function) => { let store - let reducers = {} + let combinedReducers = {} let rawReducers = {} let rawReducersMap = [] if (preloadedState && !isPlainObject(preloadedState)) { - throw new Error('Preloaded state must be plain object') + throw new Error('PreloadedState must be plain object') } // Create store with middleware for process batch actions if (reducer) { registerReducers(reducer) - store = createStore(reducers, preloadedState, enhancer) + store = createStore(combinedReducers, preloadedState, enhancer) } else { store = createStore(() => ({}), undefined, enhancer) } // Recreate reducers tree and replace them in store function recreateReducers () { - reducers = {} + combinedReducers = {} function recreate(node) { if (isPlainObject(node)) { const newReducers = {} @@ -59,9 +59,9 @@ const enhanceStore = (createStore: Function) => { return node } } - reducers = recreate(rawReducers) + combinedReducers = recreate(rawReducers) if (store) { - store.replaceReducer(reducers) + store.replaceReducer(combinedReducers) } } @@ -72,27 +72,27 @@ const enhanceStore = (createStore: Function) => { /** * Add reducers in store - * @param {Object} newReducers + * @param {Object} reducers * @return {void} */ - function registerReducers(newReducers: Object) { - if (!isPlainObject(newReducers) || Object.keys(newReducers).length === 0) { - throw new Error('The reducers parameter must be non empty plain object') + function registerReducers(reducers: Object) { + if (!isPlainObject(reducers) || Object.keys(reducers).length === 0) { + throw new Error('Reducers must be non empty plain object') } - Object.keys(newReducers).forEach(key => { - if (typeof newReducers[key] !== 'function') { - throw new Error('The values of reducers parameter must be functions') + Object.keys(reducers).forEach(key => { + if (typeof reducers[key] !== 'function') { + throw new Error('Reducers has to contain functions') } }) - Object.keys(newReducers).forEach(key => { + Object.keys(reducers).forEach(key => { const normalizedKey = normalizeMountPath(key) rawReducersMap.forEach(key1 => { if (((normalizedKey.indexOf(key1) === 0 && !((normalizedKey.substr(key1.length)[0] || '').trim())) || (key1.indexOf(normalizedKey) === 0 && !((key1.substr(normalizedKey.length)[0] || '').trim()))) && key1 !== normalizedKey ) { - throw new Error(`Reducer mounting path "${key1}" already busy`) + throw new Error(`Mounting path "${key1.length < normalizedKey.length ? key1 : normalizedKey}" already busy`) } }) const keys = normalizedKey.split(' ') @@ -108,9 +108,9 @@ const enhanceStore = (createStore: Function) => { }, rawReducers) const lastKey = keys.slice(-1) if (preloadedState1 && preloadedState1[lastKey]) { - result[lastKey] = wrapperReducerPreloadedState(newReducers[key], preloadedState1[lastKey]) + result[lastKey] = wrapperReducerPreloadedState(reducers[key], preloadedState1[lastKey]) } else { - result[lastKey] = newReducers[key] + result[lastKey] = reducers[key] } if (rawReducersMap.indexOf(normalizedKey) === -1) { rawReducersMap.push(normalizedKey) diff --git a/src/reduxSetState.js b/src/reduxSetState.js index ae928dc..395463b 100644 --- a/src/reduxSetState.js +++ b/src/reduxSetState.js @@ -17,25 +17,21 @@ import getStateByMountPath from './getState' export default (mountPath: string, dispatch: Function, getState: Function, actionPrefix: string) => (actionType: string, newState: Object | Function) => { if (typeof actionType !== 'string' || !actionType.length) { - throw new Error('Action type must be non empty string') + throw new Error('ActionType must be non empty string') } - - if ((typeof newState !== 'object' && typeof newState !== 'function') && - (process.env.NODE_ENV === 'production' || process.env.NODE_ENV !== 'production' && !isPlainObject(newState)) + if ((typeof newState !== 'object' && typeof newState !== 'function') || + (typeof newState === 'object' && (!Object.keys(newState).length || (process.env.NODE_ENV !== 'production' && !isPlainObject(newState)))) ) { - throw new Error('New state must be plain object or function') + throw new Error('NewState must be non empty plain object or function') } - let _newState + let _newState = newState if (typeof newState === 'function') { // Pass last state as param in newState _newState = newState(getStateByMountPath(mountPath)(getState())) - } else { - _newState = newState - } - - if (typeof _newState !== 'object' || !Object.keys(_newState).length || (process.env.NODE_ENV !== 'production' && !isPlainObject(_newState))) { - throw new Error('New state must be non empty plain object') + if (typeof _newState !== 'object' || !Object.keys(_newState).length || (process.env.NODE_ENV !== 'production' && !isPlainObject(_newState))) { + throw new Error('New state returned from function must be non empty plain object') + } } // Else dispatch calculated state diff --git a/src/registerReducers.js b/src/registerReducers.js index 9f77c1f..0e23017 100644 --- a/src/registerReducers.js +++ b/src/registerReducers.js @@ -64,10 +64,7 @@ export default ( let _normReducers = {} Object.keys(_reducers).forEach(key => { const normalizedMountPath = normalizeMountPath(`${propMountPath || ''} ${key || ''}`) - if (this.reduxMountPaths.some(path => - ((path.indexOf(normalizedMountPath) === 0 && !((path.substr(normalizedMountPath.length)[0] || '').trim())) || - (normalizedMountPath.indexOf(path) === 0 && !((normalizedMountPath.substr(path.length)[0] || '').trim())))) - ) { + if (this.reduxMountPaths.some(path => path === normalizedMountPath)) { throw new Error(`Mounting path "${normalizedMountPath}" already busy`) } if (this.lastReduxMountPath && normalizedMountPath.indexOf(this.lastReduxMountPath) === -1) { diff --git a/src/utils/normalize.js b/src/utils/normalize.js index 6299f81..98ad196 100644 --- a/src/utils/normalize.js +++ b/src/utils/normalize.js @@ -1,10 +1,9 @@ // @flow +import { checkMountPath } from './checks' /** * Trimmed and collapsed spaces in mounting path */ export const normalizeMountPath = (path: any): string => { - if (typeof path !== 'string') { - throw new Error('Mounting path must be string') - } + checkMountPath(path) return path.trim().replace(/\s{2,}/g,' ') }