From da36dc6b62a2c342dc9704124592d4fb24177c45 Mon Sep 17 00:00:00 2001 From: ixnv Date: Tue, 30 Apr 2019 03:51:09 +0300 Subject: [PATCH 1/4] Save user preferences to localStorage --- __tests__/store/store.test.js | 57 +++++++++++++++++++++++++++++++++++ src/helpers/localStorage.js | 18 +++++++++++ src/store/index.js | 27 +++++++++++++++-- 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 __tests__/store/store.test.js create mode 100644 src/helpers/localStorage.js 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; From 883609773ef36c7baddd7c98519d99e62c7d2f48 Mon Sep 17 00:00:00 2001 From: ixnv Date: Tue, 30 Apr 2019 05:11:42 +0300 Subject: [PATCH 2/4] Fix coverage --- __tests__/localStorage.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 __tests__/localStorage.js diff --git a/__tests__/localStorage.js b/__tests__/localStorage.js new file mode 100644 index 0000000..8ea0db8 --- /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', () => { + delete window.localStorage; + + expect(saveState(data)).toBeUndefined(); + expect(loadState()).toBeUndefined(); + }); +}); From 127851137cb862314120b43740d712e6e263f1a6 Mon Sep 17 00:00:00 2001 From: ixnv Date: Tue, 30 Apr 2019 05:17:59 +0300 Subject: [PATCH 3/4] Use Reflect.deleteProperty instead of delete keyword --- __tests__/localStorage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/localStorage.js b/__tests__/localStorage.js index 8ea0db8..ff70bcf 100644 --- a/__tests__/localStorage.js +++ b/__tests__/localStorage.js @@ -20,7 +20,7 @@ describe('LocalStorage utilities', () => { }); it('should not throw exceptions if localStorage is undefined', () => { - delete window.localStorage; + Reflect.deleteProperty(window, 'localStorage'); expect(saveState(data)).toBeUndefined(); expect(loadState()).toBeUndefined(); From 6d061a1b10445bd4159347c358988d862e1df3e1 Mon Sep 17 00:00:00 2001 From: ixnv Date: Tue, 30 Apr 2019 05:37:01 +0300 Subject: [PATCH 4/4] Add es6 to eslint env --- .eslintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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": {