Skip to content

Commit b6caa02

Browse files
committed
Merge pull request #203 from optimizely/jordan/add-options-tests
Add tests for all reactor options
2 parents b9e202b + 3d0e8ab commit b6caa02

File tree

5 files changed

+268
-19
lines changed

5 files changed

+268
-19
lines changed

TODO.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
TODO for `1.3.0`
22
===
33

4-
- [ ] add documentation for all new reactor options
4+
- [x] add documentation for all new reactor options
5+
- [x] add tests for all new reactor options
56
- [ ] link the nuclear-js package from the hot reloadable example
67
- [ ] link `0.3.0` of `nuclear-js-react-addons` in hot reloadable example
78
- [ ] add `nuclear-js-react-addons` link in example and documentation

docs/src/docs/07-api.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ If `config.debug` is true then all of the options below will be enabled.
3131

3232
`logDirtyStores` (default=`false`) console.logs what stores have changed after each dispatched action.
3333

34-
`throwOnUndefinedDispatch` (default=`false`) if true, throws an Error if a store ever returns undefined.
34+
`throwOnUndefinedActionType` (default=`false`) if true, throws an Error when dispatch is called with an undefined action type.
35+
36+
`throwOnUndefinedStoreReturnValue` (default=`false`) if true, throws an Error if a store handler or `getInitialState()` ever returns `undefined`.
3537

3638
`throwOnNonImmutableStore` (default=`false`) if true, throws an Error if a store returns a non-immutable value. Javascript primitive such as `String`, `Boolean` and `Number` count as immutable.
3739

38-
`throwOnDispatchInDispatch` (default=`true`) if true, throws an Error if a dispatch occurs in a change observer.
40+
`throwOnDispatchInDispatch` (default=`false`) if true, throws an Error if a dispatch occurs in a change observer.
3941

4042
**Example**
4143

src/reactor/fns.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export function registerStores(reactorState, stores) {
3434

3535
const initialState = store.getInitialState()
3636

37+
if (initialState === undefined && getOption(reactorState, 'throwOnUndefinedStoreReturnValue')) {
38+
throw new Error('Store getInitialState() must return a value, did you forget a return statement')
39+
}
3740
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(initialState)) {
3841
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
3942
}
@@ -58,14 +61,7 @@ export function registerStores(reactorState, stores) {
5861
export function replaceStores(reactorState, stores) {
5962
return reactorState.withMutations((reactorState) => {
6063
each(stores, (store, id) => {
61-
const initialState = store.getInitialState()
62-
63-
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(initialState)) {
64-
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
65-
}
66-
67-
reactorState
68-
.update('stores', stores => stores.set(id, store))
64+
reactorState.update('stores', stores => stores.set(id, store))
6965
})
7066
})
7167
}
@@ -77,6 +73,10 @@ export function replaceStores(reactorState, stores) {
7773
* @return {ReactorState}
7874
*/
7975
export function dispatch(reactorState, actionType, payload) {
76+
if (actionType === undefined && getOption(reactorState, 'throwOnUndefinedActionType')) {
77+
throw new Error('`dispatch` cannot be called with an `undefined` action type.');
78+
}
79+
8080
const currState = reactorState.get('state')
8181
let dirtyStores = reactorState.get('dirtyStores')
8282

@@ -96,7 +96,7 @@ export function dispatch(reactorState, actionType, payload) {
9696
throw e
9797
}
9898

99-
if (getOption(reactorState, 'throwOnUndefinedDispatch') && newState === undefined) {
99+
if (newState === undefined && getOption(reactorState, 'throwOnUndefinedStoreReturnValue')) {
100100
const errorMsg = 'Store handler must return a value, did you forget a return statement'
101101
logging.dispatchError(reactorState, errorMsg)
102102
throw new Error(errorMsg)
@@ -297,7 +297,7 @@ export function reset(reactorState) {
297297
storeMap.forEach((store, id) => {
298298
const storeState = prevState.get(id)
299299
const resetStoreState = store.handleReset(storeState)
300-
if (getOption(reactorState, 'throwOnUndefinedDispatch') && resetStoreState === undefined) {
300+
if (resetStoreState === undefined && getOption(reactorState, 'throwOnUndefinedStoreReturnValue')) {
301301
throw new Error('Store handleReset() must return a value, did you forget a return statement')
302302
}
303303
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(resetStoreState)) {

src/reactor/records.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ export const PROD_OPTIONS = Map({
77
logAppState: false,
88
// logs what stores changed after a dispatch
99
logDirtyStores: false,
10+
// if true, throws an error when dispatching an `undefined` actionType
11+
throwOnUndefinedActionType: false,
1012
// if true, throws an error if a store returns undefined
11-
throwOnUndefinedDispatch: false,
12-
// if true, throws an error if a store returns undefined
13+
throwOnUndefinedStoreReturnValue: false,
14+
// if true, throws an error if a store.getInitialState() returns a non immutable value
1315
throwOnNonImmutableStore: false,
1416
// if true, throws when dispatching in dispatch
1517
throwOnDispatchInDispatch: false,
@@ -22,9 +24,11 @@ export const DEBUG_OPTIONS = Map({
2224
logAppState: true,
2325
// logs what stores changed after a dispatch
2426
logDirtyStores: true,
27+
// if true, throws an error when dispatching an `undefined` actionType
28+
throwOnUndefinedActionType: true,
2529
// if true, throws an error if a store returns undefined
26-
throwOnUndefinedDispatch: true,
27-
// if true, throws an error if a store returns undefined
30+
throwOnUndefinedStoreReturnValue: true,
31+
// if true, throws an error if a store.getInitialState() returns a non immutable value
2832
throwOnNonImmutableStore: true,
2933
// if true, throws when dispatching in dispatch
3034
throwOnDispatchInDispatch: true,

tests/reactor-tests.js

+244-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ describe('Reactor', () => {
3333
expect(getOption(reactor.reactorState, 'logDispatches')).toBe(true)
3434
expect(getOption(reactor.reactorState, 'logAppState')).toBe(false)
3535
expect(getOption(reactor.reactorState, 'logDirtyStores')).toBe(false)
36-
expect(getOption(reactor.reactorState, 'throwOnUndefinedDispatch')).toBe(false)
36+
expect(getOption(reactor.reactorState, 'throwOnUndefinedActionType')).toBe(false)
37+
expect(getOption(reactor.reactorState, 'throwOnUndefinedStoreReturnValue')).toBe(false)
3738
expect(getOption(reactor.reactorState, 'throwOnNonImmutableStore')).toBe(false)
3839
expect(getOption(reactor.reactorState, 'throwOnDispatchInDispatch')).toBe(false)
3940
})
@@ -48,12 +49,253 @@ describe('Reactor', () => {
4849
expect(getOption(reactor.reactorState, 'logDispatches')).toBe(false)
4950
expect(getOption(reactor.reactorState, 'logAppState')).toBe(true)
5051
expect(getOption(reactor.reactorState, 'logDirtyStores')).toBe(true)
51-
expect(getOption(reactor.reactorState, 'throwOnUndefinedDispatch')).toBe(true)
52+
expect(getOption(reactor.reactorState, 'throwOnUndefinedActionType')).toBe(true)
53+
expect(getOption(reactor.reactorState, 'throwOnUndefinedStoreReturnValue')).toBe(true)
5254
expect(getOption(reactor.reactorState, 'throwOnNonImmutableStore')).toBe(true)
5355
expect(getOption(reactor.reactorState, 'throwOnDispatchInDispatch')).toBe(false)
5456
})
5557
})
5658

59+
describe('options', () => {
60+
describe('throwOnUndefinedActionType', () => {
61+
it('should NOT throw when `false`', () => {
62+
var reactor = new Reactor({
63+
options: {
64+
throwOnUndefinedActionType: false,
65+
},
66+
})
67+
68+
expect(() => {
69+
reactor.dispatch(undefined)
70+
}).not.toThrow()
71+
})
72+
73+
it('should throw when `true`', () => {
74+
var reactor = new Reactor({
75+
options: {
76+
throwOnUndefinedActionType: true,
77+
},
78+
})
79+
80+
expect(() => {
81+
reactor.dispatch(undefined)
82+
}).toThrow()
83+
})
84+
})
85+
86+
describe('throwOnUndefinedStoreReturnValue', () => {
87+
it('should NOT throw during `registerStores`, `dispatch` or `reset` when `false`', () => {
88+
var reactor = new Reactor({
89+
options: {
90+
throwOnUndefinedStoreReturnValue: false,
91+
},
92+
})
93+
94+
expect(() => {
95+
reactor.registerStores({
96+
store: Store({
97+
getInitialState() {
98+
return undefined
99+
},
100+
initialize() {
101+
this.on('action', () => undefined)
102+
},
103+
})
104+
})
105+
reactor.dispatch('action')
106+
reactor.reset()
107+
}).not.toThrow()
108+
})
109+
110+
it('should throw during `registerStores` when `true`', () => {
111+
var reactor = new Reactor({
112+
options: {
113+
throwOnUndefinedStoreReturnValue: true,
114+
},
115+
})
116+
117+
expect(() => {
118+
reactor.registerStores({
119+
store: Store({
120+
getInitialState() {
121+
return undefined
122+
},
123+
initialize() {
124+
this.on('action', () => undefined)
125+
},
126+
})
127+
})
128+
}).toThrow()
129+
})
130+
131+
it('should throw during `dispatch` when `true`', () => {
132+
var reactor = new Reactor({
133+
options: {
134+
throwOnUndefinedStoreReturnValue: true,
135+
},
136+
})
137+
138+
expect(() => {
139+
reactor.registerStores({
140+
store: Store({
141+
getInitialState() {
142+
return undefined
143+
},
144+
initialize() {
145+
this.on('action', () => undefined)
146+
},
147+
})
148+
})
149+
}).toThrow()
150+
})
151+
152+
it('should throw during `reset` when `true`', () => {
153+
var reactor = new Reactor({
154+
options: {
155+
throwOnUndefinedStoreReturnValue: true,
156+
},
157+
})
158+
159+
expect(() => {
160+
reactor.registerStores({
161+
store: Store({
162+
getInitialState() {
163+
return 1
164+
},
165+
handleReset() {
166+
return undefined
167+
}
168+
})
169+
})
170+
reactor.reset()
171+
}).toThrow()
172+
})
173+
})
174+
175+
describe('throwOnNonImmutableStore', () => {
176+
it('should NOT throw during `registerStores` or `reset` when `false`', () => {
177+
var reactor = new Reactor({
178+
options: {
179+
throwOnNonImmutableStore: false,
180+
},
181+
})
182+
183+
expect(() => {
184+
reactor.registerStores({
185+
store: Store({
186+
getInitialState() {
187+
return { foo: 'bar' }
188+
},
189+
handleReset() {
190+
return { foo: 'baz' }
191+
},
192+
})
193+
})
194+
reactor.reset()
195+
}).not.toThrow()
196+
})
197+
198+
it('should throw during `registerStores` when `true`', () => {
199+
var reactor = new Reactor({
200+
options: {
201+
throwOnNonImmutableStore: true,
202+
},
203+
})
204+
205+
expect(() => {
206+
reactor.registerStores({
207+
store: Store({
208+
getInitialState() {
209+
return { foo: 'bar' }
210+
},
211+
})
212+
})
213+
}).toThrow()
214+
})
215+
216+
it('should throw during `reset` when `true`', () => {
217+
var reactor = new Reactor({
218+
options: {
219+
throwOnNonImmutableStore: true,
220+
},
221+
})
222+
223+
expect(() => {
224+
reactor.registerStores({
225+
store: Store({
226+
getInitialState() {
227+
return 123
228+
},
229+
handleReset() {
230+
return { foo: 'baz' }
231+
},
232+
})
233+
})
234+
reactor.reset()
235+
}).toThrow()
236+
})
237+
})
238+
239+
describe('throwOnDispatchInDispatch', () => {
240+
it('should NOT throw when `false`', () => {
241+
var reactor = new Reactor({
242+
options: {
243+
throwOnDispatchInDispatch: false,
244+
},
245+
})
246+
247+
expect(() => {
248+
reactor.registerStores({
249+
count: Store({
250+
getInitialState() {
251+
return 1
252+
},
253+
initialize() {
254+
this.on('increment', curr => curr + 1)
255+
}
256+
})
257+
})
258+
259+
reactor.observe(['count'], (val) => {
260+
if (val % 2 === 0) {
261+
reactor.dispatch('increment')
262+
}
263+
})
264+
reactor.dispatch('increment')
265+
expect(reactor.evaluate(['count'])).toBe(3)
266+
}).not.toThrow()
267+
})
268+
269+
it('should throw when `true`', () => {
270+
var reactor = new Reactor({
271+
options: {
272+
throwOnDispatchInDispatch: true,
273+
},
274+
})
275+
276+
expect(() => {
277+
reactor.registerStores({
278+
count: Store({
279+
getInitialState() {
280+
return 1
281+
},
282+
initialize() {
283+
this.on('increment', curr => curr + 1)
284+
}
285+
})
286+
})
287+
288+
reactor.observe(['count'], (val) => {
289+
if (val % 2 === 0) {
290+
reactor.dispatch('increment')
291+
}
292+
})
293+
reactor.dispatch('increment')
294+
}).toThrow()
295+
})
296+
})
297+
})
298+
57299
describe('Reactor with no initial state', () => {
58300
var checkoutActions
59301
var reactor

0 commit comments

Comments
 (0)