diff --git a/src/proxy.ts b/src/proxy.ts index e9d65be..6fbedc0 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -2,7 +2,17 @@ import { extractVuexModule, getNamespacedPath } from "./module"; import { VuexModuleConstructor, Map, VuexModule, ProxyWatchers } from "./interfaces"; import { getClassPath, toCamelCase, refineNamespacedPath } from "./utils"; -export function clearProxyCache( cls :T ) {} +export function clearProxyCache( cls :T ) { + //@ts-ignore + const VuexClass = cls as VuexModuleConstructor + delete VuexClass.prototype.__vuex_module_cache__ + delete VuexClass.prototype.__vuex_proxy_cache__ + delete VuexClass.prototype.__store_cache__ + delete VuexClass.prototype.__vuex_local_proxy_cache__ + for (const submodule of Object.values(VuexClass.prototype.__submodules_cache__)) { + clearProxyCache(submodule) + } +} export function createProxy( $store :any, cls :T ) :ProxyWatchers & InstanceType { //@ts-ignore @@ -277,7 +287,7 @@ function createSubModuleProxy( $store :Map, cls:VuexModuleConstructor, proxy :Ma } -function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, namespacedPath = "", currentField = "", maxDepth = 1 }: { cls: VuexModuleConstructor, proxy: Map; state: Map; $store: any; namespacedPath?: string; currentField?: string; maxDepth ?:number}) { +function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, namespacedPath = "", currentField = "", maxDepth = 12 }: { cls: VuexModuleConstructor, proxy: Map, state: Map, $store: any, namespacedPath?: string, currentField?: string, maxDepth ?:number}) { /** * 1. Go through all fields in the object and check the values of those fields. * @@ -303,20 +313,27 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam if (currentField.length && !currentField.endsWith(".")) currentField += "."; const path = currentField + field; - if ( maxDepth === 0 || typeof value !== "object" || (typeof value === 'object' && !fieldIsSubmodule) ) { + if ( maxDepth === 0 || typeof value !== 'object' || value === null || Array.isArray(value) ) { + + const getter = () => { + // When creating local proxies getters doesn't exist on that context, so we have to account + // for that. + let getters = namespacedPath ? $store.rootGetters : $store.getters + if (getters === undefined) { + if ($store.getters === undefined) { + namespacedPath = "" + getters = $store + } else { + getters = $store.getters + } + } + return getters[ `${namespacedPath}__${className}_internal_getter__` ]( path ) + } if( !strict || fieldIsSubmodule ) { Object.defineProperty(proxy, field, { - get: () => { - // When creating local proxies getters doesn't exist on that context, so we have to account - // for that. - const getters = cls.prototype.__namespacedPath__ ? $store.rootGetters : $store.getters; - if( getters ) { - const getterPath = refineNamespacedPath(cls.prototype.__namespacedPath__) + `__${className}_internal_getter__`; - return getters[ getterPath ]( path ) - }else return $store[ `__${className}_internal_getter__` ]( path ) - }, + get: getter, set: payload => { const commit = $store.commit || cls.prototype.__store_cache__.commit; if( commit ) commit( refineNamespacedPath( cls.prototype.__namespacedPath__ ) + `__${className}_internal_mutator__`, { field: path, payload }, { root: true }); @@ -333,13 +350,7 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam else { Object.defineProperty(proxy, field, { - get: () => { - // When creating local proxies getters doesn't exist on that context, so we have to account - // for that. - if( $store.getters ) { - return $store.getters[ namespacedPath + `__${className}_internal_getter__` ]( path ) - }else return $store[ `__${className}_internal_getter__` ]( path ) - }, + get: getter, }) } @@ -463,15 +474,26 @@ function createGettersAndGetterMutationsProxy({ cls, getters, mutations, proxy, if( $store === undefined || proxy[ field ] ) continue; + const getter = () => { + // When creating local proxies getters doesn't exist on that context, so we have to account + // for that. + let getters = namespacedPath ? $store.rootGetters : $store.getters + if (getters === undefined) { + if ($store.getters === undefined) { + namespacedPath = "" + getters = $store + } else { + getters = $store.getters + } + } + return getters[ `${namespacedPath}${field}` ] + } + const fieldHasGetterAndMutation = getterMutations.indexOf( field ) > -1; if( fieldHasGetterAndMutation ) { Object.defineProperty( proxy, field, { - get: () => { - const storeGetters = namespacedPath ? $store.rootGetters : $store.getters; - if( storeGetters ) return storeGetters[ namespacedPath + field ] - else return $store[ namespacedPath + field ]; - }, + get: getter, set: ( payload :any ) => $store.commit( namespacedPath + field, payload, { root: !!namespacedPath } ), }) @@ -482,13 +504,7 @@ function createGettersAndGetterMutationsProxy({ cls, getters, mutations, proxy, if( Object.prototype.hasOwnProperty.call(proxy, field) ) continue; Object.defineProperty( proxy, field, { - get: () => { - const storeGetters = namespacedPath ? $store.rootGetters : $store.getters; - if (storeGetters) - return storeGetters[ namespacedPath + field ]; - else - return $store[ namespacedPath + field ]; - } + get: getter }) } diff --git a/test/create-proxy.spec.ts b/test/create-proxy.spec.ts index ee023fe..e173bf9 100644 --- a/test/create-proxy.spec.ts +++ b/test/create-proxy.spec.ts @@ -2,7 +2,7 @@ import Vuex, {Store} from 'vuex' // @ts-ignore import { createLocalVue } from '@vue/test-utils' -import { Module, VuexModule, getter, mutation, action, getRawActionContext } from '../src' +import { getter, mutation, action, getRawActionContext, createModule, createProxy, clearProxyCache, createSubModule, extractVuexModule } from '../src' interface Name { @@ -10,8 +10,7 @@ interface Name { lastname:string } -@Module({ namespacedPath: 'user/settings/' }) -class UserSettings extends VuexModule { +class UserSettings extends createModule({ namespaced: 'user/settings/' }) { @getter cookieConsent = false @mutation changeConsent(consent: boolean) { @@ -19,13 +18,18 @@ class UserSettings extends VuexModule { } } -@Module({ namespacedPath: 'user/something/'}) -class Something extends VuexModule { +class Something extends createModule({ namespaced: 'user/something/' }) { something = 'nothing' + nested = { + test: "test", + deep: { + test: "deep test", + valid: true + } + } } -@Module({ namespacedPath: 'books/' }) -class Books extends VuexModule{ +class Books extends createModule({ namespaced: 'books/' }) { books: string[] = [] @mutation addBook(book: string) { @@ -33,14 +37,23 @@ class Books extends VuexModule{ } } -@Module({ namespacedPath: 'user/' }) -class UserStore extends VuexModule { +class UserStore extends createModule({ namespaced: 'user/', strict: false }) { - settings = UserSettings.CreateSubModule(UserSettings) - something = Something.CreateSubModule(Something) + settings = createSubModule(UserSettings) + something = createSubModule(Something) firstname = 'Michael' lastname = 'Olofinjana' + nullField: string | null = null + description = { + fingers: 10, + arms: 2, + hungry: true, + head: { + eyes: 2, + hairs: "brown" + } + } @getter specialty = 'JavaScript' // The @getter decorator automatically exposes a defined state as a getter. @getter occupation = 'Developer' @@ -67,7 +80,7 @@ class UserStore extends VuexModule { } @action async addBook(book: string) { - const booksProxy = Books.CreateProxy(this.$store, Books) + const booksProxy = createProxy(this.$store, Books) booksProxy.addBook(book) } @@ -95,18 +108,19 @@ describe('CreateProxy', () => { localVue = createLocalVue() localVue.use(Vuex) store = new Store({ + strict: true, modules: { - user: UserStore.ExtractVuexModule(UserStore) + ...extractVuexModule(UserStore) } }) }) afterEach(() => { - UserStore.ClearProxyCache(UserStore) + clearProxyCache(UserStore) }) it('should proxy getters', () => { - const user = UserStore.CreateProxy(store, UserStore); + const user = createProxy(store, UserStore); expect(user.fullName).toEqual('Michael Olofinjana') expect(user.specialty).toEqual('JavaScript') @@ -114,15 +128,16 @@ describe('CreateProxy', () => { }) it('should proxy state', () => { - const user = UserStore.CreateProxy(store, UserStore) + const user = createProxy(store, UserStore) expect(user.firstname).toEqual('Michael') expect(user.lastname).toEqual('Olofinjana') + expect(user.nullField).toEqual(null) }) it('should proxy actions', async () => { - const user = UserStore.CreateProxy(store, UserStore) + const user = createProxy(store, UserStore) await user.doAnotherAsyncStuff('Something') @@ -141,7 +156,7 @@ describe('CreateProxy', () => { }) it('should proxy mutations', async () => { - const user = UserStore.CreateProxy(store, UserStore) + const user = createProxy(store, UserStore) await user.changeName({ firstname: 'Ola', lastname: 'Nordmann' }) @@ -151,4 +166,59 @@ describe('CreateProxy', () => { expect(user.lastname).toEqual('Nordmann') }) -}) + it('should proxy non-strict setter in strict mode', () => { + const user = createProxy(store, UserStore) + + expect(user.firstname).toEqual('Michael') + expect(user.lastname).toEqual('Olofinjana') + expect(user.nullField).toEqual(null) + + user.firstname = 'Ola' + user.lastname = 'Nordmann' + user.nullField = 'not null' + expect(user.firstname).toEqual('Ola') + expect(user.lastname).toEqual('Nordmann') + expect(user.nullField).toEqual('not null') + }) + + it('should proxy objects recursively', () => { + const user = createProxy(store, UserStore) + + expect(user.description.arms).toEqual(2) + expect(user.description.fingers).toEqual(10) + expect(user.description.hungry).toEqual(true) + expect(user.description.head.eyes).toEqual(2) + expect(user.description.head.hairs).toEqual("brown") + + user.description.hungry = false + expect(user.description.hungry).toEqual(false) + + user.description.head.hairs = "blond" + expect(user.description.head.hairs).toEqual("blond") + }) + + it('should proxy submodule', () => { + const user = createProxy(store, UserStore) + + expect(user.settings.cookieConsent).toEqual(false) + expect(user.something.something).toEqual("nothing") + expect(user.something.nested.test).toEqual("test") + expect(user.something.nested.deep.test).toEqual("deep test") + expect(user.something.nested.deep.valid).toEqual(true) + + user.settings.changeConsent(true) + expect(user.settings.cookieConsent).toEqual(true) + + user.something.something = "more than nothing" + expect(user.something.something).toEqual("more than nothing") + + user.something.nested.test = "nested change" + expect(user.something.nested.test).toEqual("nested change") + + user.something.nested.deep.test = "nested deep change" + user.something.nested.deep.valid = false + expect(user.something.nested.deep.test).toEqual("nested deep change") + expect(user.something.nested.deep.valid).toEqual(false) + }) + +}) \ No newline at end of file