Skip to content

Commit

Permalink
Merge pull request #203 from optimizely/jordan/add-options-tests
Browse files Browse the repository at this point in the history
Add tests for all reactor options
  • Loading branch information
jordangarcia committed Dec 30, 2015
2 parents b9e202b + 3d0e8ab commit b6caa02
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 19 deletions.
3 changes: 2 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
TODO for `1.3.0`
===

- [ ] add documentation for all new reactor options
- [x] add documentation for all new reactor options
- [x] add tests for all new reactor options
- [ ] link the nuclear-js package from the hot reloadable example
- [ ] link `0.3.0` of `nuclear-js-react-addons` in hot reloadable example
- [ ] add `nuclear-js-react-addons` link in example and documentation
Expand Down
6 changes: 4 additions & 2 deletions docs/src/docs/07-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ If `config.debug` is true then all of the options below will be enabled.

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

`throwOnUndefinedDispatch` (default=`false`) if true, throws an Error if a store ever returns undefined.
`throwOnUndefinedActionType` (default=`false`) if true, throws an Error when dispatch is called with an undefined action type.

`throwOnUndefinedStoreReturnValue` (default=`false`) if true, throws an Error if a store handler or `getInitialState()` ever returns `undefined`.

`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.

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

**Example**

Expand Down
20 changes: 10 additions & 10 deletions src/reactor/fns.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export function registerStores(reactorState, stores) {

const initialState = store.getInitialState()

if (initialState === undefined && getOption(reactorState, 'throwOnUndefinedStoreReturnValue')) {
throw new Error('Store getInitialState() must return a value, did you forget a return statement')
}
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(initialState)) {
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
}
Expand All @@ -58,14 +61,7 @@ export function registerStores(reactorState, stores) {
export function replaceStores(reactorState, stores) {
return reactorState.withMutations((reactorState) => {
each(stores, (store, id) => {
const initialState = store.getInitialState()

if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(initialState)) {
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
}

reactorState
.update('stores', stores => stores.set(id, store))
reactorState.update('stores', stores => stores.set(id, store))
})
})
}
Expand All @@ -77,6 +73,10 @@ export function replaceStores(reactorState, stores) {
* @return {ReactorState}
*/
export function dispatch(reactorState, actionType, payload) {
if (actionType === undefined && getOption(reactorState, 'throwOnUndefinedActionType')) {
throw new Error('`dispatch` cannot be called with an `undefined` action type.');
}

const currState = reactorState.get('state')
let dirtyStores = reactorState.get('dirtyStores')

Expand All @@ -96,7 +96,7 @@ export function dispatch(reactorState, actionType, payload) {
throw e
}

if (getOption(reactorState, 'throwOnUndefinedDispatch') && newState === undefined) {
if (newState === undefined && getOption(reactorState, 'throwOnUndefinedStoreReturnValue')) {
const errorMsg = 'Store handler must return a value, did you forget a return statement'
logging.dispatchError(reactorState, errorMsg)
throw new Error(errorMsg)
Expand Down Expand Up @@ -297,7 +297,7 @@ export function reset(reactorState) {
storeMap.forEach((store, id) => {
const storeState = prevState.get(id)
const resetStoreState = store.handleReset(storeState)
if (getOption(reactorState, 'throwOnUndefinedDispatch') && resetStoreState === undefined) {
if (resetStoreState === undefined && getOption(reactorState, 'throwOnUndefinedStoreReturnValue')) {
throw new Error('Store handleReset() must return a value, did you forget a return statement')
}
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(resetStoreState)) {
Expand Down
12 changes: 8 additions & 4 deletions src/reactor/records.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ export const PROD_OPTIONS = Map({
logAppState: false,
// logs what stores changed after a dispatch
logDirtyStores: false,
// if true, throws an error when dispatching an `undefined` actionType
throwOnUndefinedActionType: false,
// if true, throws an error if a store returns undefined
throwOnUndefinedDispatch: false,
// if true, throws an error if a store returns undefined
throwOnUndefinedStoreReturnValue: false,
// if true, throws an error if a store.getInitialState() returns a non immutable value
throwOnNonImmutableStore: false,
// if true, throws when dispatching in dispatch
throwOnDispatchInDispatch: false,
Expand All @@ -22,9 +24,11 @@ export const DEBUG_OPTIONS = Map({
logAppState: true,
// logs what stores changed after a dispatch
logDirtyStores: true,
// if true, throws an error when dispatching an `undefined` actionType
throwOnUndefinedActionType: true,
// if true, throws an error if a store returns undefined
throwOnUndefinedDispatch: true,
// if true, throws an error if a store returns undefined
throwOnUndefinedStoreReturnValue: true,
// if true, throws an error if a store.getInitialState() returns a non immutable value
throwOnNonImmutableStore: true,
// if true, throws when dispatching in dispatch
throwOnDispatchInDispatch: true,
Expand Down
246 changes: 244 additions & 2 deletions tests/reactor-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ describe('Reactor', () => {
expect(getOption(reactor.reactorState, 'logDispatches')).toBe(true)
expect(getOption(reactor.reactorState, 'logAppState')).toBe(false)
expect(getOption(reactor.reactorState, 'logDirtyStores')).toBe(false)
expect(getOption(reactor.reactorState, 'throwOnUndefinedDispatch')).toBe(false)
expect(getOption(reactor.reactorState, 'throwOnUndefinedActionType')).toBe(false)
expect(getOption(reactor.reactorState, 'throwOnUndefinedStoreReturnValue')).toBe(false)
expect(getOption(reactor.reactorState, 'throwOnNonImmutableStore')).toBe(false)
expect(getOption(reactor.reactorState, 'throwOnDispatchInDispatch')).toBe(false)
})
Expand All @@ -48,12 +49,253 @@ describe('Reactor', () => {
expect(getOption(reactor.reactorState, 'logDispatches')).toBe(false)
expect(getOption(reactor.reactorState, 'logAppState')).toBe(true)
expect(getOption(reactor.reactorState, 'logDirtyStores')).toBe(true)
expect(getOption(reactor.reactorState, 'throwOnUndefinedDispatch')).toBe(true)
expect(getOption(reactor.reactorState, 'throwOnUndefinedActionType')).toBe(true)
expect(getOption(reactor.reactorState, 'throwOnUndefinedStoreReturnValue')).toBe(true)
expect(getOption(reactor.reactorState, 'throwOnNonImmutableStore')).toBe(true)
expect(getOption(reactor.reactorState, 'throwOnDispatchInDispatch')).toBe(false)
})
})

describe('options', () => {
describe('throwOnUndefinedActionType', () => {
it('should NOT throw when `false`', () => {
var reactor = new Reactor({
options: {
throwOnUndefinedActionType: false,
},
})

expect(() => {
reactor.dispatch(undefined)
}).not.toThrow()
})

it('should throw when `true`', () => {
var reactor = new Reactor({
options: {
throwOnUndefinedActionType: true,
},
})

expect(() => {
reactor.dispatch(undefined)
}).toThrow()
})
})

describe('throwOnUndefinedStoreReturnValue', () => {
it('should NOT throw during `registerStores`, `dispatch` or `reset` when `false`', () => {
var reactor = new Reactor({
options: {
throwOnUndefinedStoreReturnValue: false,
},
})

expect(() => {
reactor.registerStores({
store: Store({
getInitialState() {
return undefined
},
initialize() {
this.on('action', () => undefined)
},
})
})
reactor.dispatch('action')
reactor.reset()
}).not.toThrow()
})

it('should throw during `registerStores` when `true`', () => {
var reactor = new Reactor({
options: {
throwOnUndefinedStoreReturnValue: true,
},
})

expect(() => {
reactor.registerStores({
store: Store({
getInitialState() {
return undefined
},
initialize() {
this.on('action', () => undefined)
},
})
})
}).toThrow()
})

it('should throw during `dispatch` when `true`', () => {
var reactor = new Reactor({
options: {
throwOnUndefinedStoreReturnValue: true,
},
})

expect(() => {
reactor.registerStores({
store: Store({
getInitialState() {
return undefined
},
initialize() {
this.on('action', () => undefined)
},
})
})
}).toThrow()
})

it('should throw during `reset` when `true`', () => {
var reactor = new Reactor({
options: {
throwOnUndefinedStoreReturnValue: true,
},
})

expect(() => {
reactor.registerStores({
store: Store({
getInitialState() {
return 1
},
handleReset() {
return undefined
}
})
})
reactor.reset()
}).toThrow()
})
})

describe('throwOnNonImmutableStore', () => {
it('should NOT throw during `registerStores` or `reset` when `false`', () => {
var reactor = new Reactor({
options: {
throwOnNonImmutableStore: false,
},
})

expect(() => {
reactor.registerStores({
store: Store({
getInitialState() {
return { foo: 'bar' }
},
handleReset() {
return { foo: 'baz' }
},
})
})
reactor.reset()
}).not.toThrow()
})

it('should throw during `registerStores` when `true`', () => {
var reactor = new Reactor({
options: {
throwOnNonImmutableStore: true,
},
})

expect(() => {
reactor.registerStores({
store: Store({
getInitialState() {
return { foo: 'bar' }
},
})
})
}).toThrow()
})

it('should throw during `reset` when `true`', () => {
var reactor = new Reactor({
options: {
throwOnNonImmutableStore: true,
},
})

expect(() => {
reactor.registerStores({
store: Store({
getInitialState() {
return 123
},
handleReset() {
return { foo: 'baz' }
},
})
})
reactor.reset()
}).toThrow()
})
})

describe('throwOnDispatchInDispatch', () => {
it('should NOT throw when `false`', () => {
var reactor = new Reactor({
options: {
throwOnDispatchInDispatch: false,
},
})

expect(() => {
reactor.registerStores({
count: Store({
getInitialState() {
return 1
},
initialize() {
this.on('increment', curr => curr + 1)
}
})
})

reactor.observe(['count'], (val) => {
if (val % 2 === 0) {
reactor.dispatch('increment')
}
})
reactor.dispatch('increment')
expect(reactor.evaluate(['count'])).toBe(3)
}).not.toThrow()
})

it('should throw when `true`', () => {
var reactor = new Reactor({
options: {
throwOnDispatchInDispatch: true,
},
})

expect(() => {
reactor.registerStores({
count: Store({
getInitialState() {
return 1
},
initialize() {
this.on('increment', curr => curr + 1)
}
})
})

reactor.observe(['count'], (val) => {
if (val % 2 === 0) {
reactor.dispatch('increment')
}
})
reactor.dispatch('increment')
}).toThrow()
})
})
})

describe('Reactor with no initial state', () => {
var checkoutActions
var reactor
Expand Down

0 comments on commit b6caa02

Please sign in to comment.