From 03750fe2922f215d30b395f493f5d5cf3311c909 Mon Sep 17 00:00:00 2001 From: VIctor Alvarez Date: Mon, 5 Sep 2016 15:51:16 -0700 Subject: [PATCH 1/8] WIP; TDD --- src/__tests__/handleAction-test.js | 76 ++++++++++++++++++------------ src/handleAction.js | 5 ++ 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/__tests__/handleAction-test.js b/src/__tests__/handleAction-test.js index caa43051..e627272f 100644 --- a/src/__tests__/handleAction-test.js +++ b/src/__tests__/handleAction-test.js @@ -3,18 +3,25 @@ import { handleAction, createAction, createActions, combineActions } from '../'; describe('handleAction()', () => { const type = 'TYPE'; - const prevState = { counter: 3 }; + const defaultState = { counter: 0 }; + const previousState = { counter: 3 }; describe('single handler form', () => { + it('should throw an error if defaultState is not specified', () => { + expect(() => { + handleAction(type, undefined) + }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined') + }); + describe('resulting reducer', () => { it('returns previous state if type does not match', () => { - const reducer = handleAction('NOTTYPE', () => null); - expect(reducer(prevState, { type })).to.equal(prevState); + const reducer = handleAction('NOTTYPE', () => null, defaultState); + expect(reducer(previousState, { type })).to.equal(previousState); }); it('returns default state if type does not match', () => { const reducer = handleAction('NOTTYPE', () => null, { counter: 7 }); - expect(reducer(undefined, { type })) + expect(reducer(undefined, { type }, defaultState)) .to.deep.equal({ counter: 7 }); @@ -23,8 +30,8 @@ describe('handleAction()', () => { it('accepts single function as handler', () => { const reducer = handleAction(type, (state, action) => ({ counter: state.counter + action.payload - })); - expect(reducer(prevState, { type, payload: 7 })) + }), defaultState); + expect(reducer(previousState, { type, payload: 7 })) .to.deep.equal({ counter: 10 }); @@ -34,22 +41,22 @@ describe('handleAction()', () => { const incrementAction = createAction(type); const reducer = handleAction(incrementAction, (state, action) => ({ counter: state.counter + action.payload - })); + }), defaultState); - expect(reducer(prevState, incrementAction(7))) + expect(reducer(previousState, incrementAction(7))) .to.deep.equal({ counter: 10 }); }); - it('accepts single function as handler and a default state', () => { + it('accepts a default state used when the previous state is undefined', () => { const reducer = handleAction(type, (state, action) => ({ counter: state.counter + action.payload - }), { counter: 3 }); + }), { counter: 5 }); expect(reducer(undefined, { type, payload: 7 })) .to.deep.equal({ - counter: 10 + counter: 12 }); }); @@ -58,21 +65,27 @@ describe('handleAction()', () => { const reducer = handleAction(increment, (state, { payload }) => ({ counter: state.counter + payload - }), { counter: 3 }); + }), defaultState); expect(reducer(undefined, increment(7))) .to.deep.equal({ - counter: 10 + counter: 7 }); }); }); }); describe('map of handlers form', () => { + it('should throw an error if defaultState is not specified', () => { + expect(() => { + handleAction(type, { next: () => null }) + }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined') + }); + describe('resulting reducer', () => { it('returns previous state if type does not match', () => { - const reducer = handleAction('NOTTYPE', { next: () => null }); - expect(reducer(prevState, { type })).to.equal(prevState); + const reducer = handleAction('NOTTYPE', { next: () => null }, defaultState); + expect(reducer(previousState, { type })).to.equal(previousState); }); it('uses `next()` if action does not represent an error', () => { @@ -80,8 +93,8 @@ describe('handleAction()', () => { next: (state, action) => ({ counter: state.counter + action.payload }) - }); - expect(reducer(prevState, { type, payload: 7 })) + }, defaultState); + expect(reducer(previousState, { type, payload: 7 })) .to.deep.equal({ counter: 10 }); @@ -92,19 +105,19 @@ describe('handleAction()', () => { throw: (state, action) => ({ counter: state.counter + action.payload }) - }); + }, defaultState); - expect(reducer(prevState, { type, payload: 7, error: true })) + expect(reducer(previousState, { type, payload: 7, error: true })) .to.deep.equal({ counter: 10 }); }); it('returns previous state if matching handler is not function', () => { - const reducer = handleAction(type, { next: null, error: 123 }); - expect(reducer(prevState, { type, payload: 123 })).to.equal(prevState); - expect(reducer(prevState, { type, payload: 123, error: true })) - .to.equal(prevState); + const reducer = handleAction(type, { next: null, error: 123 }, defaultState); + expect(reducer(previousState, { type, payload: 123 })).to.equal(previousState); + expect(reducer(previousState, { type, payload: 123, error: true })) + .to.equal(previousState); }); }); }); @@ -114,7 +127,8 @@ describe('handleAction()', () => { const action1 = createAction('ACTION_1'); const reducer = handleAction( combineActions(action1, 'ACTION_2', 'ACTION_3'), - (state, { payload }) => ({ ...state, number: state.number + payload }) + (state, { payload }) => ({ ...state, number: state.number + payload }), + defaultState ); expect(reducer({ number: 1 }, action1(1))).to.deep.equal({ number: 2 }); @@ -128,7 +142,7 @@ describe('handleAction()', () => { next(state, { payload }) { return { ...state, number: state.number + payload }; } - }); + }, defaultState); expect(reducer({ number: 1 }, action1(1))).to.deep.equal({ number: 2 }); expect(reducer({ number: 1 }, { type: 'ACTION_2', payload: 2 })).to.deep.equal({ number: 3 }); @@ -145,7 +159,7 @@ describe('handleAction()', () => { throw(state) { return { ...state, threw: true }; } - }); + }, defaultState); const error = new Error; expect(reducer({ number: 0 }, action1(error))) @@ -160,6 +174,7 @@ describe('handleAction()', () => { const reducer = handleAction( combineActions('ACTION_1', 'ACTION_2'), (state, { payload }) => ({ ...state, state: state.number + payload }), + defaultState ); const state = { number: 0 }; @@ -171,11 +186,11 @@ describe('handleAction()', () => { const reducer = handleAction( combineActions('INCREMENT', 'DECREMENT'), (state, { payload }) => ({ ...state, counter: state.counter + payload }), - { counter: 10 } + defaultState ); - expect(reducer(undefined, { type: 'INCREMENT', payload: +1 })).to.deep.equal({ counter: 11 }); - expect(reducer(undefined, { type: 'DECREMENT', payload: -1 })).to.deep.equal({ counter: 9 }); + expect(reducer(undefined, { type: 'INCREMENT', payload: +1 })).to.deep.equal({ counter: +1 }); + expect(reducer(undefined, { type: 'DECREMENT', payload: -1 })).to.deep.equal({ counter: -1 }); }); it('should handle combined actions with symbols', () => { @@ -184,7 +199,8 @@ describe('handleAction()', () => { const action3 = createAction(Symbol('ACTION_3')); const reducer = handleAction( combineActions(action1, action2, action3), - (state, { payload }) => ({ ...state, number: state.number + payload }) + (state, { payload }) => ({ ...state, number: state.number + payload }), + defaultState ); expect(reducer({ number: 0 }, action1(1))) diff --git a/src/handleAction.js b/src/handleAction.js index 207f9071..9a33eafa 100644 --- a/src/handleAction.js +++ b/src/handleAction.js @@ -1,12 +1,17 @@ import isFunction from 'lodash/isFunction'; import identity from 'lodash/identity'; import isNil from 'lodash/isNil'; +import isUndefined from 'lodash/isUndefined'; import includes from 'lodash/includes'; import { ACTION_TYPE_DELIMITER } from './combineActions'; export default function handleAction(actionType, reducers, defaultState) { const actionTypes = actionType.toString().split(ACTION_TYPE_DELIMITER); + if (isUndefined(defaultState)) { + throw new Error(`Expected defaultState for reducer handling ${actionTypes} to be defined`) + } + const [nextReducer, throwReducer] = isFunction(reducers) ? [reducers, reducers] : [reducers.next, reducers.throw].map(reducer => (isNil(reducer) ? identity : reducer)); From 60a1a0f823072c6d86d1f1d8328b6b1e8a25ec60 Mon Sep 17 00:00:00 2001 From: VIctor Alvarez Date: Mon, 5 Sep 2016 16:11:47 -0700 Subject: [PATCH 2/8] WIP; handleActions; refactoring --- src/__tests__/handleActions-test.js | 18 +++++++++++++++++- src/assertDefaultState.js | 7 +++++++ src/handleAction.js | 7 ++----- src/handleActions.js | 6 +++++- 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 src/assertDefaultState.js diff --git a/src/__tests__/handleActions-test.js b/src/__tests__/handleActions-test.js index 6335f2d2..84fe2ae5 100644 --- a/src/__tests__/handleActions-test.js +++ b/src/__tests__/handleActions-test.js @@ -2,6 +2,22 @@ import { expect } from 'chai'; import { handleActions, createAction, createActions, combineActions } from '../'; describe('handleActions', () => { + const defaultState = { counter: 0 } + + it('should throw an error when defaultState is not specified', () => { + expect(() => { + handleActions({ + INCREMENT: ({ counter }, { payload: amount }) => ({ + counter: counter + amount + }), + + DECREMENT: ({ counter }, { payload: amount }) => ({ + counter: counter - amount + }) + }); + }).to.throw(Error, 'Expected defaultState for reducer handling INCREMENT, DECREMENT to be defined') + }) + it('create a single handler from a map of multiple action handlers', () => { const reducer = handleActions({ INCREMENT: ({ counter }, { payload: amount }) => ({ @@ -11,7 +27,7 @@ describe('handleActions', () => { DECREMENT: ({ counter }, { payload: amount }) => ({ counter: counter - amount }) - }); + }, defaultState); expect(reducer({ counter: 3 }, { type: 'INCREMENT', payload: 7 })) .to.deep.equal({ diff --git a/src/assertDefaultState.js b/src/assertDefaultState.js new file mode 100644 index 00000000..7ddd702e --- /dev/null +++ b/src/assertDefaultState.js @@ -0,0 +1,7 @@ +import isUndefined from 'lodash/isUndefined'; + +export default function assertDefaultState(defaultState, actionTypes) { + if (isUndefined(defaultState)) { + throw new Error(`Expected defaultState for reducer handling ${actionTypes.join(', ')} to be defined`) + } +} diff --git a/src/handleAction.js b/src/handleAction.js index 9a33eafa..f08a2c86 100644 --- a/src/handleAction.js +++ b/src/handleAction.js @@ -1,17 +1,14 @@ import isFunction from 'lodash/isFunction'; import identity from 'lodash/identity'; import isNil from 'lodash/isNil'; -import isUndefined from 'lodash/isUndefined'; +import assertDefaultState from './assertDefaultState'; import includes from 'lodash/includes'; import { ACTION_TYPE_DELIMITER } from './combineActions'; export default function handleAction(actionType, reducers, defaultState) { const actionTypes = actionType.toString().split(ACTION_TYPE_DELIMITER); + assertDefaultState(defaultState, actionTypes); - if (isUndefined(defaultState)) { - throw new Error(`Expected defaultState for reducer handling ${actionTypes} to be defined`) - } - const [nextReducer, throwReducer] = isFunction(reducers) ? [reducers, reducers] : [reducers.next, reducers.throw].map(reducer => (isNil(reducer) ? identity : reducer)); diff --git a/src/handleActions.js b/src/handleActions.js index 69589d02..76d0d950 100644 --- a/src/handleActions.js +++ b/src/handleActions.js @@ -1,9 +1,13 @@ import handleAction from './handleAction'; import ownKeys from './ownKeys'; +import assertDefaultState from './assertDefaultState'; import reduceReducers from 'reduce-reducers'; export default function handleActions(handlers, defaultState) { - const reducers = ownKeys(handlers).map(type => handleAction(type, handlers[type])); + const actionTypes = ownKeys(handlers); + assertDefaultState(defaultState, actionTypes); + + const reducers = actionTypes.map(type => handleAction(type, handlers[type])); const reducer = reduceReducers(...reducers); return (state = defaultState, action) => reducer(state, action); From e9c4ba2966e99d188d38dbb740e66fd1d982341b Mon Sep 17 00:00:00 2001 From: VIctor Alvarez Date: Mon, 5 Sep 2016 16:19:30 -0700 Subject: [PATCH 3/8] clean --- src/__tests__/handleAction-test.js | 8 ++++---- src/__tests__/handleActions-test.js | 31 ++++++++++++++++------------- src/assertDefaultState.js | 4 +++- src/handleActions.js | 4 ++-- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/__tests__/handleAction-test.js b/src/__tests__/handleAction-test.js index e627272f..b3b221c9 100644 --- a/src/__tests__/handleAction-test.js +++ b/src/__tests__/handleAction-test.js @@ -9,8 +9,8 @@ describe('handleAction()', () => { describe('single handler form', () => { it('should throw an error if defaultState is not specified', () => { expect(() => { - handleAction(type, undefined) - }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined') + handleAction(type, undefined); + }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined'); }); describe('resulting reducer', () => { @@ -78,8 +78,8 @@ describe('handleAction()', () => { describe('map of handlers form', () => { it('should throw an error if defaultState is not specified', () => { expect(() => { - handleAction(type, { next: () => null }) - }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined') + handleAction(type, { next: () => null }); + }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined'); }); describe('resulting reducer', () => { diff --git a/src/__tests__/handleActions-test.js b/src/__tests__/handleActions-test.js index 84fe2ae5..bf13a69b 100644 --- a/src/__tests__/handleActions-test.js +++ b/src/__tests__/handleActions-test.js @@ -2,22 +2,25 @@ import { expect } from 'chai'; import { handleActions, createAction, createActions, combineActions } from '../'; describe('handleActions', () => { - const defaultState = { counter: 0 } - + const defaultState = { counter: 0 }; + it('should throw an error when defaultState is not specified', () => { expect(() => { handleActions({ INCREMENT: ({ counter }, { payload: amount }) => ({ counter: counter + amount }), - + DECREMENT: ({ counter }, { payload: amount }) => ({ counter: counter - amount }) }); - }).to.throw(Error, 'Expected defaultState for reducer handling INCREMENT, DECREMENT to be defined') - }) - + }).to.throw( + Error, + 'Expected defaultState for reducer handling INCREMENT, DECREMENT to be defined' + ); + }); + it('create a single handler from a map of multiple action handlers', () => { const reducer = handleActions({ INCREMENT: ({ counter }, { payload: amount }) => ({ @@ -46,7 +49,7 @@ describe('handleActions', () => { [INCREMENT]: ({ counter }, { payload: amount }) => ({ counter: counter + amount }) - }); + }, defaultState); expect(reducer({ counter: 3 }, { type: INCREMENT, payload: 7 })) .to.deep.equal({ @@ -54,7 +57,7 @@ describe('handleActions', () => { }); }); - it('accepts a default state as the second parameter', () => { + it('accepts a default state used when previous state is undefined', () => { const reducer = handleActions({ INCREMENT: ({ counter }, { payload: amount }) => ({ counter: counter + amount @@ -77,7 +80,7 @@ describe('handleActions', () => { [incrementAction]: ({ counter }, { payload: amount }) => ({ counter: counter + amount }) - }); + }, defaultState); expect(reducer({ counter: 3 }, incrementAction(7))) .to.deep.equal({ @@ -97,12 +100,12 @@ describe('handleActions', () => { [combineActions(increment, decrement)](state, { payload: { amount } }) { return { ...state, counter: state.counter + amount }; } - }, initialState); + }, defaultState); expect(reducer(initialState, increment(5))).to.deep.equal({ counter: 15 }); expect(reducer(initialState, decrement(5))).to.deep.equal({ counter: 5 }); expect(reducer(initialState, { type: 'NOT_TYPE', payload: 1000 })).to.equal(initialState); - expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 15 }); + expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 5 }); }); it('should accept combined actions as action types in the next/throw form', () => { @@ -123,14 +126,14 @@ describe('handleActions', () => { return { ...state, counter: 0 }; } } - }, initialState); + }, defaultState); const error = new Error; // non-errors expect(reducer(initialState, increment(5))).to.deep.equal({ counter: 15 }); expect(reducer(initialState, decrement(5))).to.deep.equal({ counter: 5 }); expect(reducer(initialState, { type: 'NOT_TYPE', payload: 1000 })).to.equal(initialState); - expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 15 }); + expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 5 }); // errors expect( @@ -152,7 +155,7 @@ describe('handleActions', () => { [decrement]: ({ counter }, { payload }) => ({ counter: counter - payload }) - }); + }, defaultState); expect(reducer({ counter: 3 }, increment(2))) .to.deep.equal({ diff --git a/src/assertDefaultState.js b/src/assertDefaultState.js index 7ddd702e..ddea3caf 100644 --- a/src/assertDefaultState.js +++ b/src/assertDefaultState.js @@ -2,6 +2,8 @@ import isUndefined from 'lodash/isUndefined'; export default function assertDefaultState(defaultState, actionTypes) { if (isUndefined(defaultState)) { - throw new Error(`Expected defaultState for reducer handling ${actionTypes.join(', ')} to be defined`) + throw new Error( + `Expected defaultState for reducer handling ${actionTypes.join(', ')} to be defined` + ); } } diff --git a/src/handleActions.js b/src/handleActions.js index 76d0d950..fbed3017 100644 --- a/src/handleActions.js +++ b/src/handleActions.js @@ -7,8 +7,8 @@ export default function handleActions(handlers, defaultState) { const actionTypes = ownKeys(handlers); assertDefaultState(defaultState, actionTypes); - const reducers = actionTypes.map(type => handleAction(type, handlers[type])); + const reducers = actionTypes.map(type => handleAction(type, handlers[type], defaultState)); const reducer = reduceReducers(...reducers); - return (state = defaultState, action) => reducer(state, action); + return (state, action) => reducer(state, action); } From a722b061ceae9d734287c80ae45eda444e54665f Mon Sep 17 00:00:00 2001 From: VIctor Alvarez Date: Mon, 5 Sep 2016 16:27:33 -0700 Subject: [PATCH 4/8] update docs --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c438e7a9..fcdabdc3 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ expect(actionThree(3)).to.deep.equal({ }); ``` -### `handleAction(type, reducer | reducerMap, ?defaultState)` +### `handleAction(type, reducer | reducerMap, defaultState)` ```js import { handleAction } from 'redux-actions'; @@ -147,14 +147,14 @@ Otherwise, you can specify separate reducers for `next()` and `throw()`. This AP handleAction('FETCH_DATA', { next(state, action) {...}, throw(state, action) {...} -}); +}, defaultState); ``` If either `next()` or `throw()` are `undefined` or `null`, then the identity function is used for that reducer. -The optional third parameter specifies a default or initial state, which is used when `undefined` is passed to the reducer. +The third parameter `defaultState` is required, and specifies a default or initial state, which is used when `undefined` is passed to the reducer. -### `handleActions(reducerMap, ?defaultState)` +### `handleActions(reducerMap, defaultState)` ```js import { handleActions } from 'redux-actions'; @@ -162,7 +162,7 @@ import { handleActions } from 'redux-actions'; Creates multiple reducers using `handleAction()` and combines them into a single reducer that handles multiple actions. Accepts a map where the keys are passed as the first parameter to `handleAction()` (the action type), and the values are passed as the second parameter (either a reducer or reducer map). -The optional second parameter specifies a default or initial state, which is used when `undefined` is passed to the reducer. +The second parameter `defaultState` is required, and used when `undefined` is passed to the reducer. (Internally, `handleActions()` works by applying multiple reducers in sequence using [reduce-reducers](https://github.com/acdlite/reduce-reducers).) From b974e428fc89a02216fce136cbeb92e89b34bc68 Mon Sep 17 00:00:00 2001 From: VIctor Alvarez Date: Mon, 5 Sep 2016 16:36:15 -0700 Subject: [PATCH 5/8] english --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fcdabdc3..47870ec1 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ handleAction('FETCH_DATA', { If either `next()` or `throw()` are `undefined` or `null`, then the identity function is used for that reducer. -The third parameter `defaultState` is required, and specifies a default or initial state, which is used when `undefined` is passed to the reducer. +The third parameter `defaultState` is required, and is used when `undefined` is passed to the reducer. ### `handleActions(reducerMap, defaultState)` @@ -162,7 +162,7 @@ import { handleActions } from 'redux-actions'; Creates multiple reducers using `handleAction()` and combines them into a single reducer that handles multiple actions. Accepts a map where the keys are passed as the first parameter to `handleAction()` (the action type), and the values are passed as the second parameter (either a reducer or reducer map). -The second parameter `defaultState` is required, and used when `undefined` is passed to the reducer. +The second parameter `defaultState` is required, and is used when `undefined` is passed to the reducer. (Internally, `handleActions()` works by applying multiple reducers in sequence using [reduce-reducers](https://github.com/acdlite/reduce-reducers).) From 87d393ddbc1e2592d795826db0ca162f8b56cfdf Mon Sep 17 00:00:00 2001 From: VIctor Alvarez Date: Mon, 5 Sep 2016 16:39:43 -0700 Subject: [PATCH 6/8] clean diff --- src/__tests__/handleAction-test.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/__tests__/handleAction-test.js b/src/__tests__/handleAction-test.js index b3b221c9..b4da1e84 100644 --- a/src/__tests__/handleAction-test.js +++ b/src/__tests__/handleAction-test.js @@ -3,8 +3,8 @@ import { handleAction, createAction, createActions, combineActions } from '../'; describe('handleAction()', () => { const type = 'TYPE'; + const prevState = { counter: 3 }; const defaultState = { counter: 0 }; - const previousState = { counter: 3 }; describe('single handler form', () => { it('should throw an error if defaultState is not specified', () => { @@ -16,12 +16,12 @@ describe('handleAction()', () => { describe('resulting reducer', () => { it('returns previous state if type does not match', () => { const reducer = handleAction('NOTTYPE', () => null, defaultState); - expect(reducer(previousState, { type })).to.equal(previousState); + expect(reducer(prevState, { type })).to.equal(prevState); }); it('returns default state if type does not match', () => { const reducer = handleAction('NOTTYPE', () => null, { counter: 7 }); - expect(reducer(undefined, { type }, defaultState)) + expect(reducer(undefined, { type })) .to.deep.equal({ counter: 7 }); @@ -31,7 +31,7 @@ describe('handleAction()', () => { const reducer = handleAction(type, (state, action) => ({ counter: state.counter + action.payload }), defaultState); - expect(reducer(previousState, { type, payload: 7 })) + expect(reducer(prevState, { type, payload: 7 })) .to.deep.equal({ counter: 10 }); @@ -43,7 +43,7 @@ describe('handleAction()', () => { counter: state.counter + action.payload }), defaultState); - expect(reducer(previousState, incrementAction(7))) + expect(reducer(prevState, incrementAction(7))) .to.deep.equal({ counter: 10 }); @@ -85,7 +85,7 @@ describe('handleAction()', () => { describe('resulting reducer', () => { it('returns previous state if type does not match', () => { const reducer = handleAction('NOTTYPE', { next: () => null }, defaultState); - expect(reducer(previousState, { type })).to.equal(previousState); + expect(reducer(prevState, { type })).to.equal(prevState); }); it('uses `next()` if action does not represent an error', () => { @@ -94,7 +94,7 @@ describe('handleAction()', () => { counter: state.counter + action.payload }) }, defaultState); - expect(reducer(previousState, { type, payload: 7 })) + expect(reducer(prevState, { type, payload: 7 })) .to.deep.equal({ counter: 10 }); @@ -107,7 +107,7 @@ describe('handleAction()', () => { }) }, defaultState); - expect(reducer(previousState, { type, payload: 7, error: true })) + expect(reducer(prevState, { type, payload: 7, error: true })) .to.deep.equal({ counter: 10 }); @@ -115,9 +115,9 @@ describe('handleAction()', () => { it('returns previous state if matching handler is not function', () => { const reducer = handleAction(type, { next: null, error: 123 }, defaultState); - expect(reducer(previousState, { type, payload: 123 })).to.equal(previousState); - expect(reducer(previousState, { type, payload: 123, error: true })) - .to.equal(previousState); + expect(reducer(prevState, { type, payload: 123 })).to.equal(prevState); + expect(reducer(prevState, { type, payload: 123, error: true })) + .to.equal(prevState); }); }); }); From e7052449dfccc6d867e5b66b87c50f5ded7e57dc Mon Sep 17 00:00:00 2001 From: VIctor Alvarez Date: Mon, 5 Sep 2016 16:43:49 -0700 Subject: [PATCH 7/8] clean diff --- src/__tests__/handleAction-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/handleAction-test.js b/src/__tests__/handleAction-test.js index b4da1e84..bf6b432a 100644 --- a/src/__tests__/handleAction-test.js +++ b/src/__tests__/handleAction-test.js @@ -52,11 +52,11 @@ describe('handleAction()', () => { it('accepts a default state used when the previous state is undefined', () => { const reducer = handleAction(type, (state, action) => ({ counter: state.counter + action.payload - }), { counter: 5 }); + }), { counter: 3 }); expect(reducer(undefined, { type, payload: 7 })) .to.deep.equal({ - counter: 12 + counter: 10 }); }); From acc781274e924953391be4530898a67841f34f46 Mon Sep 17 00:00:00 2001 From: Victor Alvarez Date: Thu, 27 Oct 2016 21:08:18 -0700 Subject: [PATCH 8/8] clean --- src/__tests__/handleAction-test.js | 17 ++++++++++++----- src/__tests__/handleActions-test.js | 22 ++++++++++++++++++++-- src/assertDefaultState.js | 9 --------- src/handleAction.js | 7 +++++-- src/handleActions.js | 13 +++++++------ 5 files changed, 44 insertions(+), 24 deletions(-) delete mode 100644 src/assertDefaultState.js diff --git a/src/__tests__/handleAction-test.js b/src/__tests__/handleAction-test.js index df6f4f97..a62d2e09 100644 --- a/src/__tests__/handleAction-test.js +++ b/src/__tests__/handleAction-test.js @@ -11,7 +11,10 @@ describe('handleAction()', () => { it('should throw an error if defaultState is not specified', () => { expect(() => { handleAction(type, undefined); - }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined'); + }).to.throw( + Error, + 'defaultState for reducer handling TYPE should be defined' + ); }); describe('resulting reducer', () => { @@ -80,7 +83,11 @@ describe('handleAction()', () => { it('should throw an error if defaultState is not specified', () => { expect(() => { handleAction(type, { next: () => null }); - }).to.throw(Error, 'Expected defaultState for reducer handling TYPE to be defined'); + }) + .to.throw( + Error, + 'defaultState for reducer handling TYPE should be defined' + ); }); describe('resulting reducer', () => { @@ -215,7 +222,7 @@ describe('handleAction()', () => { describe('with invalid actions', () => { it('should throw a descriptive error when the action object is missing', () => { - const reducer = handleAction(createAction('ACTION_1'), identity); + const reducer = handleAction(createAction('ACTION_1'), identity, {}); expect( () => reducer(undefined) ).to.throw( @@ -225,7 +232,7 @@ describe('handleAction()', () => { }); it('should throw a descriptive error when the action type is missing', () => { - const reducer = handleAction(createAction('ACTION_1'), identity); + const reducer = handleAction(createAction('ACTION_1'), identity, {}); expect( () => reducer(undefined, {}) ).to.throw( @@ -235,7 +242,7 @@ describe('handleAction()', () => { }); it('should throw a descriptive error when the action type is not a string or symbol', () => { - const reducer = handleAction(createAction('ACTION_1'), identity); + const reducer = handleAction(createAction('ACTION_1'), identity, {}); expect( () => reducer(undefined, { type: false }) ).to.throw( diff --git a/src/__tests__/handleActions-test.js b/src/__tests__/handleActions-test.js index bf13a69b..3c6a6e08 100644 --- a/src/__tests__/handleActions-test.js +++ b/src/__tests__/handleActions-test.js @@ -4,7 +4,7 @@ import { handleActions, createAction, createActions, combineActions } from '../' describe('handleActions', () => { const defaultState = { counter: 0 }; - it('should throw an error when defaultState is not specified', () => { + it('should throw an error when defaultState is not defined', () => { expect(() => { handleActions({ INCREMENT: ({ counter }, { payload: amount }) => ({ @@ -17,7 +17,25 @@ describe('handleActions', () => { }); }).to.throw( Error, - 'Expected defaultState for reducer handling INCREMENT, DECREMENT to be defined' + 'defaultState for reducer handling INCREMENT should be defined' + ); + }); + + it('should throw an error when defaultState is not defined for combinedActions', () => { + expect(() => { + handleActions({ + [ + combineActions( + 'INCREMENT', + 'DECREMENT' + ) + ]: ({ counter }, { type, payload: amount }) => ({ + counter: counter + (type === 'INCREMENT' ? +1 : -1) * amount + }) + }); + }).to.throw( + Error, + 'defaultState for reducer handling INCREMENT, DECREMENT should be defined' ); }); diff --git a/src/assertDefaultState.js b/src/assertDefaultState.js deleted file mode 100644 index ddea3caf..00000000 --- a/src/assertDefaultState.js +++ /dev/null @@ -1,9 +0,0 @@ -import isUndefined from 'lodash/isUndefined'; - -export default function assertDefaultState(defaultState, actionTypes) { - if (isUndefined(defaultState)) { - throw new Error( - `Expected defaultState for reducer handling ${actionTypes.join(', ')} to be defined` - ); - } -} diff --git a/src/handleAction.js b/src/handleAction.js index cd574200..4c5231ba 100644 --- a/src/handleAction.js +++ b/src/handleAction.js @@ -1,7 +1,7 @@ import isFunction from 'lodash/isFunction'; import identity from 'lodash/identity'; import isNil from 'lodash/isNil'; -import assertDefaultState from './assertDefaultState'; +import isUndefined from 'lodash/isUndefined'; import includes from 'lodash/includes'; import invariant from 'invariant'; import { isFSA } from 'flux-standard-action'; @@ -9,7 +9,10 @@ import { ACTION_TYPE_DELIMITER } from './combineActions'; export default function handleAction(actionType, reducers, defaultState) { const actionTypes = actionType.toString().split(ACTION_TYPE_DELIMITER); - assertDefaultState(defaultState, actionTypes); + invariant( + !isUndefined(defaultState), + `defaultState for reducer handling ${actionTypes.join(', ')} should be defined` + ); const [nextReducer, throwReducer] = isFunction(reducers) ? [reducers, reducers] diff --git a/src/handleActions.js b/src/handleActions.js index fbed3017..69d7a83d 100644 --- a/src/handleActions.js +++ b/src/handleActions.js @@ -1,14 +1,15 @@ import handleAction from './handleAction'; import ownKeys from './ownKeys'; -import assertDefaultState from './assertDefaultState'; import reduceReducers from 'reduce-reducers'; export default function handleActions(handlers, defaultState) { - const actionTypes = ownKeys(handlers); - assertDefaultState(defaultState, actionTypes); - - const reducers = actionTypes.map(type => handleAction(type, handlers[type], defaultState)); + const reducers = ownKeys(handlers).map(type => + handleAction( + type, + handlers[type], + defaultState + ) + ); const reducer = reduceReducers(...reducers); - return (state, action) => reducer(state, action); }