From ef559a484828c03335402ad89721cd6ea812ca95 Mon Sep 17 00:00:00 2001 From: Nikolaus Graf Date: Sun, 20 Sep 2015 11:34:44 +0200 Subject: [PATCH 1/7] fix(LogMonitor): add theme to propTypes --- src/react/LogMonitor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/react/LogMonitor.js b/src/react/LogMonitor.js index d960f460..4177b5d5 100644 --- a/src/react/LogMonitor.js +++ b/src/react/LogMonitor.js @@ -54,7 +54,8 @@ export default class LogMonitor extends Component { jumpToState: PropTypes.func.isRequired, setMonitorState: PropTypes.func.isRequired, select: PropTypes.func.isRequired, - visibleOnLoad: PropTypes.bool + visibleOnLoad: PropTypes.bool, + theme: PropTypes.object }; static defaultProps = { From c5165b1bce3f45e205598eb014c55925ea5a9dd0 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Sun, 20 Sep 2015 18:25:22 +0200 Subject: [PATCH 2/7] fix(LogMonitor): allow a string for the theme prop --- src/react/LogMonitor.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/react/LogMonitor.js b/src/react/LogMonitor.js index 4177b5d5..c9839de5 100644 --- a/src/react/LogMonitor.js +++ b/src/react/LogMonitor.js @@ -55,7 +55,10 @@ export default class LogMonitor extends Component { setMonitorState: PropTypes.func.isRequired, select: PropTypes.func.isRequired, visibleOnLoad: PropTypes.bool, - theme: PropTypes.object + theme: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string + ]) }; static defaultProps = { From 54146ad2a9609f730f9ff72b3f62e9524da9363c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 24 Sep 2015 17:58:42 +0300 Subject: [PATCH 3/7] Add badges --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 01791d4c..18942ba4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ Redux DevTools A live-editing time travel environment for [Redux](https://github.com/rackt/redux). **[See Dan's React Europe talk demoing it!](http://youtube.com/watch?v=xsSnOQynTHs)** +[![build status](https://img.shields.io/travis/gaearon/redux-devtools/master.svg?style=flat-square)](https://travis-ci.org/gaearon/redux-devtools) +[![npm version](https://img.shields.io/npm/v/redux-devtools.svg?style=flat-square)](https://www.npmjs.com/package/redux-devtools) +[![npm downloads](https://img.shields.io/npm/dm/redux-devtools.svg?style=flat-square)](https://www.npmjs.com/package/redux-devtools) +[![redux channel on slack](https://img.shields.io/badge/slack-redux@reactiflux-61DAFB.svg?style=flat-square)](http://www.reactiflux.com) + + ![](http://i.imgur.com/J4GeW0M.gif) ### Features From 7ef89c29e9a053cd82e3a6c7405a181e516fa46f Mon Sep 17 00:00:00 2001 From: Lee Bannard Date: Thu, 24 Sep 2015 18:11:11 +0100 Subject: [PATCH 4/7] rename sweep disabled action test --- test/devTools.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/devTools.spec.js b/test/devTools.spec.js index 234ef10f..8e336e67 100644 --- a/test/devTools.spec.js +++ b/test/devTools.spec.js @@ -99,7 +99,7 @@ describe('devTools', () => { expect(store.getState()).toBe(1); }); - it('sweep disabled actions', () => { + it('should sweep disabled actions', () => { // stateIndex 0 = @@INIT store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'DECREMENT' }); From d157963cebc00492a3abfc38dc0de7b70296730c Mon Sep 17 00:00:00 2001 From: Lee Bannard Date: Thu, 24 Sep 2015 18:12:24 +0100 Subject: [PATCH 5/7] clarify recompute test --- test/devTools.spec.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/devTools.spec.js b/test/devTools.spec.js index 8e336e67..dbfe0c58 100644 --- a/test/devTools.spec.js +++ b/test/devTools.spec.js @@ -153,13 +153,14 @@ describe('devTools', () => { store.dispatch({ type: 'INCREMENT' }); expect(store.getState()).toBe(1); - let devStoreState = store.devToolsStore.getState(); - devStoreState.committedState = 10; - devStoreState.stagedActions[2] = { type: 'INCREMENT' }; + let stagedActions = [...store.devToolsStore.getState().stagedActions]; + // replace DECREMENT with INCREMENT (stagedAction[0] is @@INIT) + stagedActions[2] = { type: 'INCREMENT' }; + const committedState = 10; devToolsStore.dispatch(ActionCreators.recomputeStates( - devStoreState.committedState, - devStoreState.stagedActions + committedState, + stagedActions )); expect(store.getState()).toBe(13); From eb9ecb1e6598c2033ebbe3500a4d132b23c0079c Mon Sep 17 00:00:00 2001 From: Lee Bannard Date: Thu, 24 Sep 2015 19:18:58 +0100 Subject: [PATCH 6/7] improve code coverage on devTools.js --- test/devTools.spec.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/devTools.spec.js b/test/devTools.spec.js index dbfe0c58..672fb8b9 100644 --- a/test/devTools.spec.js +++ b/test/devTools.spec.js @@ -14,6 +14,7 @@ function counterWithBug(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return mistake - 1; // eslint-disable-line no-undef + case 'SET_UNDEFINED': return undefined; default: return state; } } @@ -202,18 +203,13 @@ describe('devTools', () => { spy.restore(); }); - it('returns the last non-undefined state from getState', () => { - let spy = spyOn(console, 'error'); + it('should return the last non-undefined state from getState', () => { + let storeWithBug = devTools()(createStore)(counterWithBug); + storeWithBug.dispatch({ type: 'INCREMENT' }); + storeWithBug.dispatch({ type: 'INCREMENT' }); + expect(storeWithBug.getState()).toBe(2); - store.dispatch({ type: 'INCREMENT' }); - store.dispatch({ type: 'DECREMENT' }); - store.dispatch({ type: 'INCREMENT' }); - store.dispatch({ type: 'INCREMENT' }); - expect(store.getState()).toBe(2); - - store.replaceReducer(counterWithBug); - expect(store.getState()).toBe(1); - - spy.restore(); + storeWithBug.dispatch({ type: 'SET_UNDEFINED' }); + expect(storeWithBug.getState()).toBe(2); }); }); From 95a2636370153c966d92fe8f0bc5ac4c7d5e6a70 Mon Sep 17 00:00:00 2001 From: Lee Bannard Date: Thu, 24 Sep 2015 19:20:02 +0100 Subject: [PATCH 7/7] add tests for persistState.js --- src/persistState.js | 2 +- test/persistState.spec.js | 123 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 test/persistState.spec.js diff --git a/src/persistState.js b/src/persistState.js index 976a3b05..c12e49ce 100644 --- a/src/persistState.js +++ b/src/persistState.js @@ -65,7 +65,7 @@ export default function persistState(sessionId, stateDeserializer = null, action try { localStorage.setItem(key, JSON.stringify(store.getState())); } catch (e) { - console.warn('Could not write debug session from localStorage:', e); + console.warn('Could not write debug session to localStorage:', e); } return action; diff --git a/test/persistState.spec.js b/test/persistState.spec.js new file mode 100644 index 00000000..4cafe1c5 --- /dev/null +++ b/test/persistState.spec.js @@ -0,0 +1,123 @@ +import expect from 'expect'; +import devTools from '../src/devTools'; +import persistState from '../src/persistState'; +import { compose, createStore } from 'redux'; + +describe('persistState', () => { + let savedLocalStorage = global.localStorage; + + beforeEach(() => { + global.localStorage = { + store: {}, + getItem(key) { + return this.store[key] || null; + }, + setItem(key, value) { + this.store[key] = value; + }, + removeItem(key) { + delete this.store[key]; + }, + clear() { + this.store = {}; + } + }; + }); + + after(() => { + global.localStorage = savedLocalStorage; + }); + + const reducer = (state = 0, action) => { + switch (action.type) { + case 'INCREMENT': + return state + 1; + case 'DECREMENT': + return state - 1; + default: + return state; + } + }; + + it('should persist state', () => { + const finalCreateStore = compose(devTools(), persistState('id'))(createStore); + const store = finalCreateStore(reducer); + expect(store.getState()).toBe(0); + + store.dispatch({ type: 'INCREMENT' }); + store.dispatch({ type: 'INCREMENT' }); + expect(store.getState()).toBe(2); + + const store2 = finalCreateStore(reducer); + expect(store2.getState()).toBe(2); + }); + + it('should not persist state if no session id', () => { + const finalCreateStore = compose(devTools(), persistState())(createStore); + const store = finalCreateStore(reducer); + expect(store.getState()).toBe(0); + + store.dispatch({ type: 'INCREMENT' }); + store.dispatch({ type: 'INCREMENT' }); + expect(store.getState()).toBe(2); + + const store2 = finalCreateStore(reducer); + expect(store2.getState()).toBe(0); + }); + + it('should run with a custom state deserializer', () => { + const oneLess = state => state === undefined ? -1 : state - 1; + const finalCreateStore = compose(devTools(), persistState('id', oneLess))(createStore); + const store = finalCreateStore(reducer); + expect(store.getState()).toBe(0); + + store.dispatch({ type: 'INCREMENT' }); + store.dispatch({ type: 'INCREMENT' }); + expect(store.getState()).toBe(2); + + const store2 = finalCreateStore(reducer); + expect(store2.getState()).toBe(1); + }); + + it('should run with a custom action deserializer', () => { + const incToDec = action => action.type === 'INCREMENT' ? { type: 'DECREMENT' } : action; + const finalCreateStore = compose(devTools(), persistState('id', null, incToDec))(createStore); + const store = finalCreateStore(reducer); + expect(store.getState()).toBe(0); + + store.dispatch({ type: 'INCREMENT' }); + store.dispatch({ type: 'INCREMENT' }); + expect(store.getState()).toBe(2); + + const store2 = finalCreateStore(reducer); + expect(store2.getState()).toBe(-2); + }); + + it('should warn if read from localStorage fails', () => { + const spy = expect.spyOn(console, 'warn'); + const finalCreateStore = compose(devTools(), persistState('id'))(createStore); + delete global.localStorage.getItem; + finalCreateStore(reducer); + + expect(spy.calls).toContain( + /Could not read debug session from localStorage/, + (call, errMsg) => call.arguments[0].match(errMsg) + ); + + spy.restore(); + }); + it('should warn if write to localStorage fails', () => { + const spy = expect.spyOn(console, 'warn'); + const finalCreateStore = compose(devTools(), persistState('id'))(createStore); + delete global.localStorage.setItem; + const store = finalCreateStore(reducer); + + store.dispatch({ type: 'INCREMENT' }); + expect(spy.calls).toContain( + /Could not write debug session to localStorage/, + (call, errMsg) => call.arguments[0].match(errMsg) + ); + + spy.restore(); + }); +});