Tweak tests to use new API and add another warning

This commit is contained in:
Dan Abramov 2016-02-02 17:52:24 +00:00
parent f1ecc9d812
commit 8316de0970
4 changed files with 59 additions and 37 deletions

View File

@ -63,6 +63,6 @@
"dependencies": { "dependencies": {
"lodash": "^3.10.1", "lodash": "^3.10.1",
"react-redux": "^4.0.0", "react-redux": "^4.0.0",
"redux": "^3.0.0" "redux": "^3.2.1"
} }
} }

View File

@ -341,18 +341,30 @@ function unliftStore(liftedStore, liftReducer) {
*/ */
export default function instrument(monitorReducer = () => null) { export default function instrument(monitorReducer = () => null) {
return createStore => (reducer, initialState, enhancer) => { return createStore => (reducer, initialState, enhancer) => {
function liftReducer(r) { function liftReducer(r) {
if (typeof r !== 'function') { if (typeof r !== 'function') {
throw new Error('Expected the nextReducer to be a function.'); if (r && typeof r['default'] === 'function') {
throw new Error(
'Expected the reducer to be a function. ' +
'Instead got an object with a "default" field. ' +
'Did you pass a module instead of the default export? ' +
'Try passing require(...).default instead.'
);
}
throw new Error('Expected the reducer to be a function.');
} }
return liftReducerWith(r, initialState, monitorReducer); return liftReducerWith(r, initialState, monitorReducer);
} }
const liftedStore = createStore(liftReducer(reducer), undefined, enhancer); const liftedStore = createStore(liftReducer(reducer), enhancer);
if (liftedStore.liftedStore) { if (liftedStore.liftedStore) {
throw new Error('DevTools instrument shouldn\'t be included more than once. ' + throw new Error(
'Check your store configuration.'); 'DevTools instrumentation should not be applied more than once. ' +
'Check your store configuration.'
);
} }
return unliftStore(liftedStore, liftReducer); return unliftStore(liftedStore, liftReducer);
}; };
} }

View File

@ -32,7 +32,7 @@ describe('instrument', () => {
let liftedStore; let liftedStore;
beforeEach(() => { beforeEach(() => {
store = instrument()(createStore)(counter); store = createStore(counter, instrument());
liftedStore = store.liftedStore; liftedStore = store.liftedStore;
}); });
@ -156,7 +156,7 @@ describe('instrument', () => {
it('should catch and record errors', () => { it('should catch and record errors', () => {
let spy = spyOn(console, 'error'); let spy = spyOn(console, 'error');
let storeWithBug = instrument()(createStore)(counterWithBug); let storeWithBug = createStore(counterWithBug, instrument());
storeWithBug.dispatch({ type: 'INCREMENT' }); storeWithBug.dispatch({ type: 'INCREMENT' });
storeWithBug.dispatch({ type: 'DECREMENT' }); storeWithBug.dispatch({ type: 'DECREMENT' });
@ -180,12 +180,13 @@ describe('instrument', () => {
expect(() => { expect(() => {
store.dispatch({ type: undefined }); store.dispatch({ type: undefined });
}).toThrow( }).toThrow(
/Actions may not have an undefined/ 'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
); );
}); });
it('should return the last non-undefined state from getState', () => { it('should return the last non-undefined state from getState', () => {
let storeWithBug = instrument()(createStore)(counterWithBug); let storeWithBug = createStore(counterWithBug, instrument());
storeWithBug.dispatch({ type: 'INCREMENT' }); storeWithBug.dispatch({ type: 'INCREMENT' });
storeWithBug.dispatch({ type: 'INCREMENT' }); storeWithBug.dispatch({ type: 'INCREMENT' });
expect(storeWithBug.getState()).toBe(2); expect(storeWithBug.getState()).toBe(2);
@ -196,7 +197,7 @@ describe('instrument', () => {
it('should not recompute states on every action', () => { it('should not recompute states on every action', () => {
let reducerCalls = 0; let reducerCalls = 0;
let monitoredStore = instrument()(createStore)(() => reducerCalls++); let monitoredStore = createStore(() => reducerCalls++, instrument());
expect(reducerCalls).toBe(1); expect(reducerCalls).toBe(1);
monitoredStore.dispatch({ type: 'INCREMENT' }); monitoredStore.dispatch({ type: 'INCREMENT' });
monitoredStore.dispatch({ type: 'INCREMENT' }); monitoredStore.dispatch({ type: 'INCREMENT' });
@ -206,7 +207,7 @@ describe('instrument', () => {
it('should not recompute old states when toggling an action', () => { it('should not recompute old states when toggling an action', () => {
let reducerCalls = 0; let reducerCalls = 0;
let monitoredStore = instrument()(createStore)(() => reducerCalls++); let monitoredStore = createStore(() => reducerCalls++, instrument());
let monitoredLiftedStore = monitoredStore.liftedStore; let monitoredLiftedStore = monitoredStore.liftedStore;
expect(reducerCalls).toBe(1); expect(reducerCalls).toBe(1);
@ -249,7 +250,7 @@ describe('instrument', () => {
it('should not recompute states when jumping to state', () => { it('should not recompute states when jumping to state', () => {
let reducerCalls = 0; let reducerCalls = 0;
let monitoredStore = instrument()(createStore)(() => reducerCalls++); let monitoredStore = createStore(() => reducerCalls++, instrument());
let monitoredLiftedStore = monitoredStore.liftedStore; let monitoredLiftedStore = monitoredStore.liftedStore;
expect(reducerCalls).toBe(1); expect(reducerCalls).toBe(1);
@ -272,10 +273,9 @@ describe('instrument', () => {
expect(monitoredLiftedStore.getState().computedStates).toBe(savedComputedStates); expect(monitoredLiftedStore.getState().computedStates).toBe(savedComputedStates);
}); });
it('should not recompute states on monitor actions', () => { it('should not recompute states on monitor actions', () => {
let reducerCalls = 0; let reducerCalls = 0;
let monitoredStore = instrument()(createStore)(() => reducerCalls++); let monitoredStore = createStore(() => reducerCalls++, instrument());
let monitoredLiftedStore = monitoredStore.liftedStore; let monitoredLiftedStore = monitoredStore.liftedStore;
expect(reducerCalls).toBe(1); expect(reducerCalls).toBe(1);
@ -301,7 +301,7 @@ describe('instrument', () => {
let exportedState; let exportedState;
beforeEach(() => { beforeEach(() => {
monitoredStore = instrument()(createStore)(counter); monitoredStore = createStore(counter, instrument());
monitoredLiftedStore = monitoredStore.liftedStore; monitoredLiftedStore = monitoredStore.liftedStore;
// Set up state to export // Set up state to export
monitoredStore.dispatch({ type: 'INCREMENT' }); monitoredStore.dispatch({ type: 'INCREMENT' });
@ -312,7 +312,7 @@ describe('instrument', () => {
}); });
it('should replay all the steps when a state is imported', () => { it('should replay all the steps when a state is imported', () => {
let importMonitoredStore = instrument()(createStore)(counter); let importMonitoredStore = createStore(counter, instrument());
let importMonitoredLiftedStore = importMonitoredStore.liftedStore; let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
importMonitoredLiftedStore.dispatch(ActionCreators.importState(exportedState)); importMonitoredLiftedStore.dispatch(ActionCreators.importState(exportedState));
@ -320,7 +320,7 @@ describe('instrument', () => {
}); });
it('should replace the existing action log with the one imported', () => { it('should replace the existing action log with the one imported', () => {
let importMonitoredStore = instrument()(createStore)(counter); let importMonitoredStore = createStore(counter, instrument());
let importMonitoredLiftedStore = importMonitoredStore.liftedStore; let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
importMonitoredStore.dispatch({ type: 'DECREMENT' }); importMonitoredStore.dispatch({ type: 'DECREMENT' });
@ -333,12 +333,27 @@ describe('instrument', () => {
it('throws if reducer is not a function', () => { it('throws if reducer is not a function', () => {
expect(() => expect(() =>
instrument()(createStore)() createStore(undefined, instrument())
).toThrow('Expected the nextReducer to be a function.'); ).toThrow('Expected the reducer to be a function.');
}); });
it('warns if the reducer is not a function but has a default field that is', () => {
expect(() =>
createStore(({ default: () => {} }), instrument())
).toThrow(
'Expected the reducer to be a function. ' +
'Instead got an object with a "default" field. ' +
'Did you pass a module instead of the default export? ' +
'Try passing require(...).default instead.'
);
});
it('throws if there are more than one instrument enhancer included', () => { it('throws if there are more than one instrument enhancer included', () => {
expect(() => { expect(() => {
store = createStore(counter, undefined, compose(instrument(), instrument())); createStore(counter, compose(instrument(), instrument()))
}).toThrow('DevTools instrument shouldn\'t be included more than once. Check your store configuration.'); }).toThrow(
'DevTools instrumentation should not be applied more than once. ' +
'Check your store configuration.'
);
}); });
}); });

View File

@ -39,64 +39,59 @@ describe('persistState', () => {
}; };
it('should persist state', () => { it('should persist state', () => {
const finalCreateStore = compose(instrument(), persistState('id'))(createStore); const store = createStore(reducer, compose(instrument(), persistState('id')));
const store = finalCreateStore(reducer);
expect(store.getState()).toBe(0); expect(store.getState()).toBe(0);
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
expect(store.getState()).toBe(2); expect(store.getState()).toBe(2);
const store2 = finalCreateStore(reducer); const store2 = createStore(reducer, compose(instrument(), persistState('id')));
expect(store2.getState()).toBe(2); expect(store2.getState()).toBe(2);
}); });
it('should not persist state if no session id', () => { it('should not persist state if no session id', () => {
const finalCreateStore = compose(instrument(), persistState())(createStore); const store = createStore(reducer, compose(instrument(), persistState()));
const store = finalCreateStore(reducer);
expect(store.getState()).toBe(0); expect(store.getState()).toBe(0);
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
expect(store.getState()).toBe(2); expect(store.getState()).toBe(2);
const store2 = finalCreateStore(reducer); const store2 = createStore(reducer, compose(instrument(), persistState()));
expect(store2.getState()).toBe(0); expect(store2.getState()).toBe(0);
}); });
it('should run with a custom state deserializer', () => { it('should run with a custom state deserializer', () => {
const oneLess = state => state === undefined ? -1 : state - 1; const oneLess = state => state === undefined ? -1 : state - 1;
const finalCreateStore = compose(instrument(), persistState('id', oneLess))(createStore); const store = createStore(reducer, compose(instrument(), persistState('id', oneLess)));
const store = finalCreateStore(reducer);
expect(store.getState()).toBe(0); expect(store.getState()).toBe(0);
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
expect(store.getState()).toBe(2); expect(store.getState()).toBe(2);
const store2 = finalCreateStore(reducer); const store2 = createStore(reducer, compose(instrument(), persistState('id', oneLess)));
expect(store2.getState()).toBe(1); expect(store2.getState()).toBe(1);
}); });
it('should run with a custom action deserializer', () => { it('should run with a custom action deserializer', () => {
const incToDec = action => action.type === 'INCREMENT' ? { type: 'DECREMENT' } : action; const incToDec = action => action.type === 'INCREMENT' ? { type: 'DECREMENT' } : action;
const finalCreateStore = compose(instrument(), persistState('id', undefined, incToDec))(createStore); const store = createStore(reducer, compose(instrument(), persistState('id', undefined, incToDec)));
const store = finalCreateStore(reducer);
expect(store.getState()).toBe(0); expect(store.getState()).toBe(0);
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
expect(store.getState()).toBe(2); expect(store.getState()).toBe(2);
const store2 = finalCreateStore(reducer); const store2 = createStore(reducer, compose(instrument(), persistState('id', undefined, incToDec)));
expect(store2.getState()).toBe(-2); expect(store2.getState()).toBe(-2);
}); });
it('should warn if read from localStorage fails', () => { it('should warn if read from localStorage fails', () => {
const spy = expect.spyOn(console, 'warn'); const spy = expect.spyOn(console, 'warn');
const finalCreateStore = compose(instrument(), persistState('id'))(createStore);
delete global.localStorage.getItem; delete global.localStorage.getItem;
finalCreateStore(reducer); const store = createStore(reducer, compose(instrument(), persistState('id')));
expect(spy.calls).toContain( expect(spy.calls).toContain(
/Could not read debug session from localStorage/, /Could not read debug session from localStorage/,
@ -105,11 +100,11 @@ describe('persistState', () => {
spy.restore(); spy.restore();
}); });
it('should warn if write to localStorage fails', () => { it('should warn if write to localStorage fails', () => {
const spy = expect.spyOn(console, 'warn'); const spy = expect.spyOn(console, 'warn');
const finalCreateStore = compose(instrument(), persistState('id'))(createStore);
delete global.localStorage.setItem; delete global.localStorage.setItem;
const store = finalCreateStore(reducer); const store = createStore(reducer, compose(instrument(), persistState('id')));
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' });
expect(spy.calls).toContain( expect(spy.calls).toContain(