diff --git a/src/index.ts b/src/index.ts index 889a9ba..7abbbbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ export interface Options { key?: string; } -export default function(options?: Options) { +export default function (options?: Options) { const tab = new Tab(window); let key: string = 'vuex-multi-tab'; let statesPaths: string[] = []; @@ -18,19 +18,37 @@ export default function(options?: Options) { function filterStates(state: { [key: string]: any }): { [key: string]: any } { const result = {}; - statesPaths.forEach(statePath => { + statesPaths.forEach((statePath) => { set(statePath, pick(statePath, state), result); }); return result; } - function mergeState(oldState: object, newState: object) { + /** + * simple object deep clone method + * @param obj + */ + function cloneObj(obj: any): any { + if (Array.isArray(obj)) { + return obj.map((val) => cloneObj(val)); + } else if (typeof obj === 'object' && obj !== null) { + return Object.keys(obj).reduce((r: any, key) => { + r[key] = cloneObj(obj[key]); + return r; + }, {}); + } + return obj; + } + + function mergeState(oldState: any, newState: object) { // if whole state is to be replaced then do just that if (statesPaths.length === 0) return { ...newState }; - // else take old state - const merged = { ...oldState }; + + // else clone old state + const merged: any = cloneObj(oldState); + // and replace only specified paths - statesPaths.forEach(statePath => { + statesPaths.forEach((statePath) => { const newValue = pick(statePath, newState); // remove value if it doesn't exist, overwrite otherwise if (typeof newValue === 'undefined') remove(statePath, merged); diff --git a/test/test.spec.ts b/test/test.spec.ts index 966677b..0a2a1d3 100644 --- a/test/test.spec.ts +++ b/test/test.spec.ts @@ -33,6 +33,56 @@ describe('vuex-multi-tab-state basic tests', () => { expect(spy).to.have.been.called.with(testState.state); }); + it('should fetch filtered nested modules state from local storage without no-state-mutation errors', () => { + const testState = { + id: 'randomIdHere', + state: { + random: 1, + modA: { + rainbow: [1, 2, 3], + some: { + value1: 3, + value2: 4, + }, + }, + }, + }; + window.localStorage.setItem('vuex-multi-tab', JSON.stringify(testState)); + + const store = new Vuex.Store({ + strict: true, + state: { random: 0 }, + modules: { + modA: { + //namespaced: true, + state: { + rainbow: [], + some: { + value1: 0, + value2: 0, + }, + }, + }, + }, + }); + + const plugin = createMultiTabState({ + statesPaths: ['random', 'modA.rainbow', 'modA.some.value1'], + }); + + plugin(store); + expect(store.state).to.be.eql({ + random: 1, + modA: { + rainbow: [1, 2, 3], + some: { + value1: 3, + value2: 0, // not set during read-from-storage + }, + }, + }); + }); + it('should save only the specified states in local storage', () => { const store = new Vuex.Store({ state: { bar: { random: 0, rainbow: 0 }, foo: 0 },