diff --git a/src/store.js b/src/store.js index b5b083c92..22c62a91a 100644 --- a/src/store.js +++ b/src/store.js @@ -237,6 +237,12 @@ function resetStoreVM (store, state, hot) { enumerable: true // for local getters }) }) + // map namespaced getters - computed object needs to be complete + forEachValue(wrappedGetters, (fn, key) => { + if (key.includes('/')) { + mapNamespacedGetter(store.getters, key, computed[key], computed) + } + }) // use a Vue instance to store the state tree // suppress warnings just in case the user has added @@ -452,6 +458,34 @@ function getNestedState (state, path) { : state } +function mapNamespacedGetter (gettersObject, path, getterFunction, computed) { + const sections = path.split('/') + // If there is a getter registered in the root namespace with the name of the module + // don't map the namespaced getters to avoid breaking changes. + // A warning should be thrown in this case. + if (computed[sections[0]]) { + console.warn( + `[vuex] "${sections[0]}" module's getters are not available under the dot-notation because there is a getter in the root namespace with the same name ("${sections[0]}").`, + `If you want to use the dot-notation rename the "${sections[0]}" root getter or the module.` + ) + return + } + sections.forEach((section, index) => { + // if it's the value AKA the last element of the array + if (index === sections.length - 1) { + Object.defineProperty(gettersObject, section, { + get: getterFunction, + enumerable: true + }) + } else { + if (!gettersObject[section]) { + gettersObject[section] = {} + } + gettersObject = gettersObject[section] + } + }) +} + function unifyObjectStyle (type, payload, options) { if (isObject(type) && type.type) { options = payload diff --git a/test/unit/modules.spec.js b/test/unit/modules.spec.js index bc8fa2247..cf4499da9 100644 --- a/test/unit/modules.spec.js +++ b/test/unit/modules.spec.js @@ -547,6 +547,101 @@ describe('Modules', () => { store.dispatch('parent/test') }) + it('module: prefer root getter when there is a module with the same name', () => { + const store = new Vuex.Store({ + state: {}, + getters: { + users () { + return 'root users' + } + }, + modules: { + users: { + namespaced: true + } + } + }) + expect(store.getters.users).toBe('root users') + }) + + it('module: warn when root getter has the same name with a namespaced module', () => { + spyOn(console, 'warn') + new Vuex.Store({ + getters: { + users () { + return 'root users' + } + }, + modules: { + users: { + namespaced: true, + getters: { + users () { + return 'module users' + } + } + } + } + }) + expect(console.warn).toHaveBeenCalledWith( + `[vuex] "users" module's getters are not available under the dot-notation because there is a getter in the root namespace with the same name ("users").`, + `If you want to use the dot-notation rename the "users" root getter or the module.` + ) + }) + + it('module: access namespaced getters with a dot', () => { + const store = new Vuex.Store({ + modules: { + a: { + namespaced: true, + state: () => ({ x: 1 }), + mutations: { + incX: state => state.x++ + }, + getters: { + twiceX: state => state.x * 2 + }, + modules: { + b: { + state: () => ({ y: 10 }), + mutations: { + incY: state => state.y++ + }, + getters: { + twiceY: state => state.y * 2 + } + }, + c: { + namespaced: true, + state: () => ({ z: 100 }), + mutations: { + incZ: state => state.z++ + }, + getters: { + twiceZ: state => state.z * 2 + } + } + } + } + } + }) + + // root > namespaced + expect(store.getters.a.twiceX).toBe(2) + store.commit('a/incX') + expect(store.getters.a.twiceX).toBe(4) + + // root > namespaced > not namespaced + expect(store.getters.a.twiceY).toBe(20) + store.commit('a/incY') + expect(store.getters.a.twiceY).toBe(22) + + // root > namespaced > namespaced + expect(store.getters.a.c.twiceZ).toBe(200) + store.commit('a/c/incZ') + expect(store.getters.a.c.twiceZ).toBe(202) + }) + it('dispatching multiple actions in different modules', done => { const store = new Vuex.Store({ modules: {