diff --git a/.eslintrc b/.eslintrc index 49077db..ed291ea 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,8 @@ "env": { "browser": true, "node": true, - "jest": true + "jest": true, + "es6": true }, "settings": { "react": { diff --git a/__tests__/localStorage.js b/__tests__/localStorage.js new file mode 100644 index 0000000..ff70bcf --- /dev/null +++ b/__tests__/localStorage.js @@ -0,0 +1,28 @@ +import { + loadState, + saveState +} from '../src/helpers/localStorage'; + +describe('LocalStorage utilities', () => { + beforeEach(() => { + window.localStorage.clear(); + }); + + const data = { + foo: 1 + }; + + it('should save and load state', () => { + saveState(data); + + const state = loadState(); + expect(state).toMatchObject(data); + }); + + it('should not throw exceptions if localStorage is undefined', () => { + Reflect.deleteProperty(window, 'localStorage'); + + expect(saveState(data)).toBeUndefined(); + expect(loadState()).toBeUndefined(); + }); +}); diff --git a/__tests__/store/store.test.js b/__tests__/store/store.test.js new file mode 100644 index 0000000..dd1562d --- /dev/null +++ b/__tests__/store/store.test.js @@ -0,0 +1,57 @@ +import { + TOGGLE_JS, + TOGGLE_MODE +} from '../../src/static/constants/actions'; +import { loadState, saveState } from '../../src/helpers/localStorage'; + +describe('Store', () => { + beforeEach(() => { + jest.resetModules(); + window.localStorage.clear(); + }); + + it('should work without saved state', () => { + const { + default: store, + initialState + } = require('../../src/store'); + + expect(store.getState()).toMatchObject(initialState); + }); + + it('should load saved state from localStorage', () => { + const savedState = { + mode: 'light', + js: 'es6' + }; + saveState(savedState); + + const state = require('../../src/store').default.getState(); + expect(state.mode).toBe('light'); + expect(state.js).toBe('es6'); + }); + + it('should save state to localStorage', () => { + const toggleJSAction = { + type: TOGGLE_JS, + payload: 'es6' + }; + + const toggleModeAction = { + type: TOGGLE_MODE, + payload: 'light' + }; + + const { + default: store + } = require('../../src/store'); + + store.dispatch(toggleJSAction); + store.dispatch(toggleModeAction); + + expect(loadState()).toMatchObject({ + mode: 'light', + js: 'es6' + }); + }); +}); diff --git a/src/helpers/localStorage.js b/src/helpers/localStorage.js new file mode 100644 index 0000000..da7ea2b --- /dev/null +++ b/src/helpers/localStorage.js @@ -0,0 +1,18 @@ +export const loadState = () => { + try { + const serializedState = JSON.parse(localStorage.getItem('state')); + + return serializedState === null ? undefined : serializedState; + } catch (e) { + return undefined; + } +}; + +export const saveState = (state) => { + try { + const serializedState = JSON.stringify(state); + localStorage.setItem('state', serializedState); + } catch (e) { + return undefined; + } +}; diff --git a/src/store/index.js b/src/store/index.js index ef6999b..1b40e46 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,8 +1,16 @@ -import { createStore, applyMiddleware, compose } from 'redux'; +import { + createStore, + applyMiddleware, + compose +} from 'redux'; const uuid = require('uuid/v4'); import reducer from '../reducers/index'; import patterns from '../static/patterns'; import middleware from '../middleware'; +import { + loadState, + saveState +} from '../helpers/localStorage'; export const answers = patterns.map(pattern => ({ ...pattern, @@ -18,7 +26,7 @@ export const initialProgress = { current: answers[0] }; -const initialState = { +export const initialState = { js: 'es5', mode: 'dark', intro: true, @@ -26,8 +34,21 @@ const initialState = { progress: initialProgress }; +const state = { + ...initialState, + ...loadState() +}; + const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; -const store = createStore(reducer, initialState, composeEnhancers(applyMiddleware(...middleware))); +const store = createStore(reducer, state, composeEnhancers(applyMiddleware(...middleware))); + +store.subscribe(() => { + const currentState = store.getState(); + saveState({ + mode: currentState.mode, + js: currentState.js + }); +}); export default store;