From eaf93c45feb08b2f11c2514e972d49e89f6bbbec Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 26 Jun 2020 17:24:24 +0200 Subject: [PATCH 1/6] convert test to use new API --- test/create-proxy.spec.ts | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/test/create-proxy.spec.ts b/test/create-proxy.spec.ts index ee023fe..f2fa77f 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,11 @@ class UserSettings extends VuexModule { } } -@Module({ namespacedPath: 'user/something/'}) -class Something extends VuexModule { +class Something extends createModule({ namespaced: 'user/something/' }) { something = 'nothing' } -@Module({ namespacedPath: 'books/' }) -class Books extends VuexModule{ +class Books extends createModule({ namespaced: 'books/' }) { books: string[] = [] @mutation addBook(book: string) { @@ -33,11 +30,10 @@ class Books extends VuexModule{ } } -@Module({ namespacedPath: 'user/' }) -class UserStore extends VuexModule { +class UserStore extends createModule({ namespaced: 'user/' }) { - settings = UserSettings.CreateSubModule(UserSettings) - something = Something.CreateSubModule(Something) + settings = createSubModule(UserSettings) + something = createSubModule(Something) firstname = 'Michael' lastname = 'Olofinjana' @@ -67,7 +63,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) } @@ -96,17 +92,18 @@ describe('CreateProxy', () => { localVue.use(Vuex) store = new Store({ modules: { - user: UserStore.ExtractVuexModule(UserStore) + ...extractVuexModule(UserStore) } }) + // console.log('New store with UserStore', store.getters) }) 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,7 +111,7 @@ 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') @@ -122,7 +119,7 @@ describe('CreateProxy', () => { it('should proxy actions', async () => { - const user = UserStore.CreateProxy(store, UserStore) + const user = createProxy(store, UserStore) await user.doAnotherAsyncStuff('Something') @@ -141,7 +138,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 +148,4 @@ describe('CreateProxy', () => { expect(user.lastname).toEqual('Nordmann') }) -}) +}) \ No newline at end of file From 15d0a1cc98845a9be6ea1511f81cffe194230b2e Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 26 Jun 2020 17:25:13 +0200 Subject: [PATCH 2/6] fix getters to work with unit tests --- src/proxy.ts | 62 ++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/proxy.ts b/src/proxy.ts index e9d65be..04f10a4 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -305,18 +305,25 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam if ( maxDepth === 0 || typeof value !== "object" || (typeof value === 'object' && !fieldIsSubmodule) ) { + 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 +340,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 +464,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 +494,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 }) } From 777de282ddc15ec9c7349f20ae6df5fa920ccebb Mon Sep 17 00:00:00 2001 From: Glandos Date: Mon, 29 Jun 2020 15:50:53 +0200 Subject: [PATCH 3/6] add test for auto setters in strict mode --- test/create-proxy.spec.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/create-proxy.spec.ts b/test/create-proxy.spec.ts index f2fa77f..bd1b6a4 100644 --- a/test/create-proxy.spec.ts +++ b/test/create-proxy.spec.ts @@ -30,7 +30,7 @@ class Books extends createModule({ namespaced: 'books/' }) { } } -class UserStore extends createModule({ namespaced: 'user/' }) { +class UserStore extends createModule({ namespaced: 'user/', strict: false }) { settings = createSubModule(UserSettings) something = createSubModule(Something) @@ -91,11 +91,11 @@ describe('CreateProxy', () => { localVue = createLocalVue() localVue.use(Vuex) store = new Store({ + strict: true, modules: { ...extractVuexModule(UserStore) } }) - // console.log('New store with UserStore', store.getters) }) afterEach(() => { @@ -148,4 +148,15 @@ 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') + user.firstname = 'Ola' + user.lastname = 'Nordmann' + expect(user.firstname).toEqual('Ola') + expect(user.lastname).toEqual('Nordmann') + }) + }) \ No newline at end of file From 88acfef6199192d36adfbe1bcc315e6d33c8795e Mon Sep 17 00:00:00 2001 From: Glandos Date: Mon, 29 Jun 2020 15:52:03 +0200 Subject: [PATCH 4/6] clean everything in proxy cache to ensure module and proxy is regenerated this is especially useful for tests --- src/proxy.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/proxy.ts b/src/proxy.ts index 04f10a4..a58de71 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 From a83e8a29e196b712cb019b3c30551b57f6bb5865 Mon Sep 17 00:00:00 2001 From: Glandos Date: Mon, 29 Jun 2020 17:18:58 +0200 Subject: [PATCH 5/6] re-enable nested objects automatic setter This partly reverts #40, and add a test for null object that can be set. Now nested object can be set without explicit mutation, even in submodule. --- src/proxy.ts | 4 +-- test/create-proxy.spec.ts | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/proxy.ts b/src/proxy.ts index a58de71..15f76ef 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -287,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. * @@ -313,7 +313,7 @@ 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 ) { const getter = () => { // When creating local proxies getters doesn't exist on that context, so we have to account diff --git a/test/create-proxy.spec.ts b/test/create-proxy.spec.ts index bd1b6a4..e173bf9 100644 --- a/test/create-proxy.spec.ts +++ b/test/create-proxy.spec.ts @@ -20,6 +20,13 @@ class UserSettings extends createModule({ namespaced: 'user/settings/' }) { class Something extends createModule({ namespaced: 'user/something/' }) { something = 'nothing' + nested = { + test: "test", + deep: { + test: "deep test", + valid: true + } + } } class Books extends createModule({ namespaced: 'books/' }) { @@ -37,6 +44,16 @@ class UserStore extends createModule({ namespaced: 'user/', strict: false }) { 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' @@ -115,6 +132,7 @@ describe('CreateProxy', () => { expect(user.firstname).toEqual('Michael') expect(user.lastname).toEqual('Olofinjana') + expect(user.nullField).toEqual(null) }) it('should proxy actions', async () => { @@ -153,10 +171,54 @@ describe('CreateProxy', () => { 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 From fb6acfd65c26b3c99699f94cc9c207cd5aa51261 Mon Sep 17 00:00:00 2001 From: Glandos Date: Wed, 1 Jul 2020 17:15:27 +0200 Subject: [PATCH 6/6] include arrays as automatic getters --- src/proxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy.ts b/src/proxy.ts index 15f76ef..6fbedc0 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -313,7 +313,7 @@ function createGettersAndMutationProxyFromState({ cls, proxy, state, $store, nam if (currentField.length && !currentField.endsWith(".")) currentField += "."; const path = currentField + field; - if ( maxDepth === 0 || typeof value !== 'object' || value === null ) { + 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