Skip to content

Commit 13ab742

Browse files
Merge branch 'v1.0.0' into hygiene/check-reducers
2 parents ad7f22c + 999ff0a commit 13ab742

8 files changed

+148
-41
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ expect(actionThree(3)).to.deep.equal({
132132
});
133133
```
134134

135-
### `handleAction(type, reducer | reducerMap = Identity, ?defaultState)`
135+
### `handleAction(type, reducer | reducerMap = Identity, defaultState)`
136136

137137
```js
138138
import { handleAction } from 'redux-actions';
@@ -148,24 +148,24 @@ Otherwise, you can specify separate reducers for `next()` and `throw()` using th
148148
handleAction('FETCH_DATA', {
149149
next(state, action) {...},
150150
throw(state, action) {...}
151-
});
151+
}, defaultState);
152152
```
153153

154154
If either `next()` or `throw()` are `undefined` or `null`, then the identity function is used for that reducer.
155155

156156
If the reducer argument (`reducer | reducerMap`) is `undefined`, then the identity function is used.
157157

158-
The optional third parameter specifies a default or initial state, which is used when `undefined` is passed to the reducer.
158+
The third parameter `defaultState` is required, and is used when `undefined` is passed to the reducer.
159159

160-
### `handleActions(reducerMap, ?defaultState)`
160+
### `handleActions(reducerMap, defaultState)`
161161

162162
```js
163163
import { handleActions } from 'redux-actions';
164164
```
165165

166166
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).
167167

168-
The optional second parameter specifies a default or initial state, which is used when `undefined` is passed to the reducer.
168+
The second parameter `defaultState` is required, and is used when `undefined` is passed to the reducer.
169169

170170
(Internally, `handleActions()` works by applying multiple reducers in sequence using [reduce-reducers](https://github.com/acdlite/reduce-reducers).)
171171

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,13 @@
5050
"eslint-config-airbnb-base": "^1.0.3",
5151
"eslint-plugin-import": "^1.5.0",
5252
"eslint-watch": "^2.1.13",
53-
"flux-standard-action": "^0.6.0",
5453
"mocha": "^2.2.5",
5554
"rimraf": "^2.5.3",
5655
"webpack": "^1.13.1"
5756
},
5857
"dependencies": {
58+
"flux-standard-action": "^1.0.0",
59+
"invariant": "^2.2.1",
5960
"lodash": "^4.13.1",
6061
"reduce-reducers": "^0.1.0"
6162
}

src/__tests__/createAction-test.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,6 @@ describe('createAction()', () => {
9393
type
9494
});
9595

96-
const explictNullAction = createAction(type)(null);
97-
expect(explictNullAction).to.deep.equal({
98-
type
99-
});
100-
10196
const baz = '1';
10297
const actionCreator = createAction(type, null, () => ({ bar: baz }));
10398
expect(actionCreator()).to.deep.equal({

src/__tests__/handleAction-test.js

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { expect } from 'chai';
2+
import identity from 'lodash/identity';
23
import { handleAction, createAction, createActions, combineActions } from '../';
34

45
describe('handleAction()', () => {
56
const type = 'TYPE';
67
const prevState = { counter: 3 };
8+
const defaultState = { counter: 0 };
79

810
it('should throw an error if the reducer is the wrong type', () => {
911
const badReducers = [1, 'string', [], null];
@@ -26,9 +28,18 @@ describe('handleAction()', () => {
2628
});
2729

2830
describe('single handler form', () => {
31+
it('should throw an error if defaultState is not specified', () => {
32+
expect(() => {
33+
handleAction(type, undefined);
34+
}).to.throw(
35+
Error,
36+
'defaultState for reducer handling TYPE should be defined'
37+
);
38+
});
39+
2940
describe('resulting reducer', () => {
3041
it('returns previous state if type does not match', () => {
31-
const reducer = handleAction('NOTTYPE', () => null);
42+
const reducer = handleAction('NOTTYPE', () => null, defaultState);
3243
expect(reducer(prevState, { type })).to.equal(prevState);
3344
});
3445

@@ -43,7 +54,7 @@ describe('handleAction()', () => {
4354
it('accepts single function as handler', () => {
4455
const reducer = handleAction(type, (state, action) => ({
4556
counter: state.counter + action.payload
46-
}));
57+
}), defaultState);
4758
expect(reducer(prevState, { type, payload: 7 }))
4859
.to.deep.equal({
4960
counter: 10
@@ -54,15 +65,15 @@ describe('handleAction()', () => {
5465
const incrementAction = createAction(type);
5566
const reducer = handleAction(incrementAction, (state, action) => ({
5667
counter: state.counter + action.payload
57-
}));
68+
}), defaultState);
5869

5970
expect(reducer(prevState, incrementAction(7)))
6071
.to.deep.equal({
6172
counter: 10
6273
});
6374
});
6475

65-
it('accepts single function as handler and a default state', () => {
76+
it('accepts a default state used when the previous state is undefined', () => {
6677
const reducer = handleAction(type, (state, action) => ({
6778
counter: state.counter + action.payload
6879
}), { counter: 3 });
@@ -78,20 +89,30 @@ describe('handleAction()', () => {
7889

7990
const reducer = handleAction(increment, (state, { payload }) => ({
8091
counter: state.counter + payload
81-
}), { counter: 3 });
92+
}), defaultState);
8293

8394
expect(reducer(undefined, increment(7)))
8495
.to.deep.equal({
85-
counter: 10
96+
counter: 7
8697
});
8798
});
8899
});
89100
});
90101

91102
describe('map of handlers form', () => {
103+
it('should throw an error if defaultState is not specified', () => {
104+
expect(() => {
105+
handleAction(type, { next: () => null });
106+
})
107+
.to.throw(
108+
Error,
109+
'defaultState for reducer handling TYPE should be defined'
110+
);
111+
});
112+
92113
describe('resulting reducer', () => {
93114
it('returns previous state if type does not match', () => {
94-
const reducer = handleAction('NOTTYPE', { next: () => null });
115+
const reducer = handleAction('NOTTYPE', { next: () => null }, defaultState);
95116
expect(reducer(prevState, { type })).to.equal(prevState);
96117
});
97118

@@ -100,7 +121,7 @@ describe('handleAction()', () => {
100121
next: (state, action) => ({
101122
counter: state.counter + action.payload
102123
})
103-
});
124+
}, defaultState);
104125
expect(reducer(prevState, { type, payload: 7 }))
105126
.to.deep.equal({
106127
counter: 10
@@ -112,7 +133,7 @@ describe('handleAction()', () => {
112133
throw: (state, action) => ({
113134
counter: state.counter + action.payload
114135
})
115-
});
136+
}, defaultState);
116137

117138
expect(reducer(prevState, { type, payload: 7, error: true }))
118139
.to.deep.equal({
@@ -121,7 +142,7 @@ describe('handleAction()', () => {
121142
});
122143

123144
it('returns previous state if matching handler is not function', () => {
124-
const reducer = handleAction(type, { next: null, error: 123 });
145+
const reducer = handleAction(type, { next: null, error: 123 }, defaultState);
125146
expect(reducer(prevState, { type, payload: 123 })).to.equal(prevState);
126147
expect(reducer(prevState, { type, payload: 123, error: true }))
127148
.to.equal(prevState);
@@ -134,7 +155,8 @@ describe('handleAction()', () => {
134155
const action1 = createAction('ACTION_1');
135156
const reducer = handleAction(
136157
combineActions(action1, 'ACTION_2', 'ACTION_3'),
137-
(state, { payload }) => ({ ...state, number: state.number + payload })
158+
(state, { payload }) => ({ ...state, number: state.number + payload }),
159+
defaultState
138160
);
139161

140162
expect(reducer({ number: 1 }, action1(1))).to.deep.equal({ number: 2 });
@@ -148,7 +170,7 @@ describe('handleAction()', () => {
148170
next(state, { payload }) {
149171
return { ...state, number: state.number + payload };
150172
}
151-
});
173+
}, defaultState);
152174

153175
expect(reducer({ number: 1 }, action1(1))).to.deep.equal({ number: 2 });
154176
expect(reducer({ number: 1 }, { type: 'ACTION_2', payload: 2 })).to.deep.equal({ number: 3 });
@@ -165,7 +187,7 @@ describe('handleAction()', () => {
165187
throw(state) {
166188
return { ...state, threw: true };
167189
}
168-
});
190+
}, defaultState);
169191
const error = new Error;
170192

171193
expect(reducer({ number: 0 }, action1(error)))
@@ -180,6 +202,7 @@ describe('handleAction()', () => {
180202
const reducer = handleAction(
181203
combineActions('ACTION_1', 'ACTION_2'),
182204
(state, { payload }) => ({ ...state, state: state.number + payload }),
205+
defaultState
183206
);
184207

185208
const state = { number: 0 };
@@ -191,11 +214,11 @@ describe('handleAction()', () => {
191214
const reducer = handleAction(
192215
combineActions('INCREMENT', 'DECREMENT'),
193216
(state, { payload }) => ({ ...state, counter: state.counter + payload }),
194-
{ counter: 10 }
217+
defaultState
195218
);
196219

197-
expect(reducer(undefined, { type: 'INCREMENT', payload: +1 })).to.deep.equal({ counter: 11 });
198-
expect(reducer(undefined, { type: 'DECREMENT', payload: -1 })).to.deep.equal({ counter: 9 });
220+
expect(reducer(undefined, { type: 'INCREMENT', payload: +1 })).to.deep.equal({ counter: +1 });
221+
expect(reducer(undefined, { type: 'DECREMENT', payload: -1 })).to.deep.equal({ counter: -1 });
199222
});
200223

201224
it('should handle combined actions with symbols', () => {
@@ -204,7 +227,8 @@ describe('handleAction()', () => {
204227
const action3 = createAction(Symbol('ACTION_3'));
205228
const reducer = handleAction(
206229
combineActions(action1, action2, action3),
207-
(state, { payload }) => ({ ...state, number: state.number + payload })
230+
(state, { payload }) => ({ ...state, number: state.number + payload }),
231+
defaultState
208232
);
209233

210234
expect(reducer({ number: 0 }, action1(1)))
@@ -215,4 +239,36 @@ describe('handleAction()', () => {
215239
.to.deep.equal({ number: 3 });
216240
});
217241
});
242+
243+
describe('with invalid actions', () => {
244+
it('should throw a descriptive error when the action object is missing', () => {
245+
const reducer = handleAction(createAction('ACTION_1'), identity, {});
246+
expect(
247+
() => reducer(undefined)
248+
).to.throw(
249+
Error,
250+
'The FSA spec mandates an action object with a type. Try using the createAction(s) method.'
251+
);
252+
});
253+
254+
it('should throw a descriptive error when the action type is missing', () => {
255+
const reducer = handleAction(createAction('ACTION_1'), identity, {});
256+
expect(
257+
() => reducer(undefined, {})
258+
).to.throw(
259+
Error,
260+
'The FSA spec mandates an action object with a type. Try using the createAction(s) method.'
261+
);
262+
});
263+
264+
it('should throw a descriptive error when the action type is not a string or symbol', () => {
265+
const reducer = handleAction(createAction('ACTION_1'), identity, {});
266+
expect(
267+
() => reducer(undefined, { type: false })
268+
).to.throw(
269+
Error,
270+
'The FSA spec mandates an action object with a type. Try using the createAction(s) method.'
271+
);
272+
});
273+
});
218274
});

0 commit comments

Comments
 (0)