From 06f900a9b669fc2ba9bb153c92937abfa8c79a0f Mon Sep 17 00:00:00 2001 From: ktsn Date: Thu, 11 Jan 2018 23:21:13 +0900 Subject: [PATCH 01/10] refactor: rename bindings.ts to decorators.ts --- src/{bindings.ts => decorators.ts} | 0 src/index.ts | 2 +- test/{bindings.ts => decorators.ts} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{bindings.ts => decorators.ts} (100%) rename test/{bindings.ts => decorators.ts} (99%) diff --git a/src/bindings.ts b/src/decorators.ts similarity index 100% rename from src/bindings.ts rename to src/decorators.ts diff --git a/src/index.ts b/src/index.ts index 410c20e..b10b0c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,4 +4,4 @@ export { Action, Mutation, namespace -} from './bindings' +} from './decorators' diff --git a/test/bindings.ts b/test/decorators.ts similarity index 99% rename from test/bindings.ts rename to test/decorators.ts index f5f507f..5794111 100644 --- a/test/bindings.ts +++ b/test/decorators.ts @@ -9,7 +9,7 @@ import { Action, Mutation, namespace -} from '../src/bindings' +} from '../src/decorators' describe('binding helpers', () => { Vue.use(Vuex) From 3dfd71108224908fc1a7e3bde2af22b29debfa90 Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 12 Jan 2018 01:08:36 +0900 Subject: [PATCH 02/10] chore: bump vue related packages --- package-lock.json | 22 ++++++++++------------ package.json | 6 +++--- tsconfig.base.json | 6 +----- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4d0afb..f125756 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4824,9 +4824,9 @@ "dev": true }, "typescript": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz", - "integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", + "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", "dev": true }, "uglify-js": { @@ -4996,21 +4996,19 @@ } }, "vue": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.2.tgz", - "integrity": "sha512-Au9rf8fPkBulFHfZ406UaQDd1jH9fqGRIM+0IHilrXnJ/0TeeMH4SBkNxWf2dGevl2S3aVeu0E/WklEv0/msag==", + "version": "2.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.13.tgz", + "integrity": "sha512-3D+lY7HTkKbtswDM4BBHgqyq+qo8IAEE8lz8va1dz3LLmttjgo0FxairO4r1iN2OBqk8o1FyL4hvzzTFEdQSEw==", "dev": true }, "vue-class-component": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-6.0.0.tgz", - "integrity": "sha512-3XS48fRq8NoTg/SgGOoHc50xiwgIkaee3/eyFcHl5BlzU5EW4phN3q5yh8aLdJ3vzcW1jxdiEyI6davLq+VJ0w==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-6.1.2.tgz", + "integrity": "sha512-DF0PIhpBDiQdr+Rofd3HQ79N722hMVPQ8PDMt9vCD4Q7vCnOE3Dgn75ZuvRPYrNvkJtn26HWgwgR83XcJGmxGA==", "dev": true }, "vuex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.0.0.tgz", - "integrity": "sha512-/QPzbRtnj39bUYTRWfFScZ7RWXBxlaQxqfAWuHTKxJoxrBHPANaQNUhY4aUjBlLbAfOOJTOgx17gCcyX8h2AhQ==", + "version": "github:ktsn/vuex#cfb60429ddfe20ca01286e4c41ff887631f9ef24", "dev": true }, "watchpack": { diff --git a/package.json b/package.json index f42e58a..513fa33 100644 --- a/package.json +++ b/package.json @@ -52,9 +52,9 @@ "tslint-config-ktsn": "^2.1.0", "typescript": "^2.5.3", "uglify-js": "^3.1.4", - "vue": "^2.5.2", - "vue-class-component": "^6.0.0", - "vuex": "^3.0.0", + "vue": "^2.5.13", + "vue-class-component": "^6.1.2", + "vuex": "github:ktsn/vuex#cfb6042", "webpack": "^3.8.1", "webpack-espower-loader": "^1.0.2" }, diff --git a/tsconfig.base.json b/tsconfig.base.json index 846fc1d..7dd3f22 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -8,10 +8,6 @@ "dom", "es2015" ], - "allowSyntheticDefaultImports": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "strictNullChecks": true + "strict": true } } From 0f2a51e3ab99584afd2832982085f7e1680a0d2d Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 12 Jan 2018 01:08:57 +0900 Subject: [PATCH 03/10] chore: add setup script for test --- scripts/webpack.config.test.js | 3 ++- test/{decorators.ts => decorators.spec.ts} | 2 -- test/setup.ts | 7 +++++++ 3 files changed, 9 insertions(+), 3 deletions(-) rename test/{decorators.ts => decorators.spec.ts} (99%) create mode 100644 test/setup.ts diff --git a/scripts/webpack.config.test.js b/scripts/webpack.config.test.js index f574af4..f3d4bff 100644 --- a/scripts/webpack.config.test.js +++ b/scripts/webpack.config.test.js @@ -2,7 +2,8 @@ const path = require('path') const glob = require('glob') module.exports = { - entry: ['es6-promise/auto'].concat(glob.sync(path.resolve(__dirname, '../test/**/*.ts'))), + entry: ['es6-promise/auto', path.resolve(__dirname, '../test/setup.ts')] + .concat(glob.sync(path.resolve(__dirname, '../test/**/*.spec.ts'))), output: { path: path.resolve(__dirname, '../.tmp'), filename: 'test.js' diff --git a/test/decorators.ts b/test/decorators.spec.ts similarity index 99% rename from test/decorators.ts rename to test/decorators.spec.ts index 5794111..28f7a14 100644 --- a/test/decorators.ts +++ b/test/decorators.spec.ts @@ -12,8 +12,6 @@ import { } from '../src/decorators' describe('binding helpers', () => { - Vue.use(Vuex) - it('State: type', () => { const store = new Vuex.Store({ state: { value: 1 } diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 0000000..bc78d14 --- /dev/null +++ b/test/setup.ts @@ -0,0 +1,7 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +Vue.config.productionTip = false +Vue.config.devtools = false + +Vue.use(Vuex) From 41a3b05be60a7c363a5e10100202e578d2886438 Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 12 Jan 2018 01:09:24 +0900 Subject: [PATCH 04/10] feat: allow to bind state to components with type safety --- src/bind-store.ts | 47 +++++++++++++++++++++++++ src/decorators.ts | 17 +++------ src/utils.ts | 20 +++++++++++ test/bind-store.spec.ts | 78 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 src/bind-store.ts create mode 100644 src/utils.ts create mode 100644 test/bind-store.spec.ts diff --git a/src/bind-store.ts b/src/bind-store.ts new file mode 100644 index 0000000..9788194 --- /dev/null +++ b/src/bind-store.ts @@ -0,0 +1,47 @@ +import Vue, { VueConstructor, ComponentOptions } from 'vue' +import { mapValues } from './utils' + +interface Class { + new (...args: any[]): Instance +} + +type MutationMethod

= (payload: P) => void +type ActionMethod

= (payload: P) => Promise + +interface BoundClass extends VueConstructor { + state(map: Key[]): this & Class<{ [K in Key]: State[K] }> + state>(map: Map): this & Class<{ [K in keyof Map]: State[Map[K]] }> +} + +export function bindStore(namespace?: string): BoundClass { + const BoundClass: BoundClass & { options: ComponentOptions } = Vue.extend() as any + + BoundClass.state = function state(map: string[] | Record) { + BoundClass.options.computed = mapPoly(map, value => { + return makeComputed(value, 'state') + }) + return BoundClass + } + + return BoundClass +} + +function mapPoly( + map: string[] | Record, + fn: (value: string, key: string) => R +): Record { + if (Array.isArray(map)) { + map = map.reduce>((acc, value) => { + acc[value] = value + return acc + }, {}) + } + + return mapValues(map, fn) +} + +function makeComputed(key: string, type: 'state' | 'getters'): () => any { + return function boundComputed (this: Vue): any { + return this.$store[type][key] + } +} diff --git a/src/decorators.ts b/src/decorators.ts index ac6b75f..533a13d 100644 --- a/src/decorators.ts +++ b/src/decorators.ts @@ -6,13 +6,16 @@ import { mapActions, mapMutations } from 'vuex' +import { merge } from './utils' export type VuexDecorator = (proto: V, key: string) => void export type StateTransformer = (state: any, getters: any) => any -export type MapHelper = typeof mapState | typeof mapGetters - | typeof mapActions | typeof mapMutations +export interface MapHelper { + (map: string[] | Record): Record + (namespace: string, map: string[] | Record): Record +} export interface BindingOptions { namespace?: string @@ -105,13 +108,3 @@ function extractNamespace (options: BindingOptions | undefined): string | undefi return n } - -function merge (a: T, b: U): T & U { - const res: any = {} - ;[a, b].forEach((obj: any) => { - Object.keys(obj).forEach(key => { - res[key] = obj[key] - }) - }) - return res -} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..59b022a --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,20 @@ +export function merge (a: T, b: U): T & U { + const res: any = {} + ;[a, b].forEach((obj: any) => { + Object.keys(obj).forEach(key => { + res[key] = obj[key] + }) + }) + return res +} + +export function mapValues( + obj: Record, + fn: (value: T, key: string) => R +): Record { + const res: Record = {} + Object.keys(obj).forEach(key => { + res[key] = fn(obj[key], key) + }) + return res +} diff --git a/test/bind-store.spec.ts b/test/bind-store.spec.ts new file mode 100644 index 0000000..b16eda0 --- /dev/null +++ b/test/bind-store.spec.ts @@ -0,0 +1,78 @@ +import * as assert from 'power-assert' +import Component from 'vue-class-component' +import { Store, DefineModule } from 'vuex' +import { bindStore } from '../src/bind-store' + +interface State { + count: number +} + +interface Getters { + double: number +} + +interface Mutations { + increment: number +} + +interface Actions { + incrementAsync: { + delay: number + amount: number + } +} + +const counter: DefineModule = { + state: () => ({ + count: 0 + }), + getters: { + double: state => state.count * 2 + }, + mutations: { + increment (state, n) { + state.count += n + } + }, + actions: { + incrementAsync ({ commit }, { delay, amount }) { + setTimeout(() => { + commit('increment', amount) + }, delay) + } + } +} + +describe('bindStore', () => { + let store: Store + beforeEach(() => { + // @ts-ignore + store = new Store(counter) + }) + + it('binds states', () => { + const Super = bindStore().state(['count']) + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + assert(vm.count === 0) + store.state.count++ + assert(vm.count === 1) + }) + + it('binds state by using object mapper', () => { + const Super = bindStore().state({ + value: 'count' + }) + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + assert(vm.value === 0) + store.state.count++ + assert(vm.value === 1) + }) +}) From 045ea2e4f476fa2dd4d2f7426453c0f78f70b3d4 Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 12 Jan 2018 01:20:26 +0900 Subject: [PATCH 05/10] fix: expose required types for d.ts --- src/bind-store.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bind-store.ts b/src/bind-store.ts index 9788194..50d6f77 100644 --- a/src/bind-store.ts +++ b/src/bind-store.ts @@ -1,14 +1,14 @@ import Vue, { VueConstructor, ComponentOptions } from 'vue' import { mapValues } from './utils' -interface Class { +export interface Class { new (...args: any[]): Instance } -type MutationMethod

= (payload: P) => void -type ActionMethod

= (payload: P) => Promise +export type MutationMethod

= (payload: P) => void +export type ActionMethod

= (payload: P) => Promise -interface BoundClass extends VueConstructor { +export interface BoundClass extends VueConstructor { state(map: Key[]): this & Class<{ [K in Key]: State[K] }> state>(map: Map): this & Class<{ [K in keyof Map]: State[Map[K]] }> } From 5c67a93d7d1cc51384d75c09959ac117f856b5cd Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 19 Jan 2018 01:03:22 +0900 Subject: [PATCH 06/10] fix: change BoundStore to StoreBinder --- package-lock.json | 2 +- package.json | 2 +- src/bind-store.ts | 28 +++++++++++++++++----------- test/bind-store.spec.ts | 12 ++++++++---- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index f125756..fa8e972 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5008,7 +5008,7 @@ "dev": true }, "vuex": { - "version": "github:ktsn/vuex#cfb60429ddfe20ca01286e4c41ff887631f9ef24", + "version": "github:ktsn/vuex#8b6a6f919a553f2342d6f839491c0e26be7449d8", "dev": true }, "watchpack": { diff --git a/package.json b/package.json index 513fa33..7fd56b8 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "uglify-js": "^3.1.4", "vue": "^2.5.13", "vue-class-component": "^6.1.2", - "vuex": "github:ktsn/vuex#cfb6042", + "vuex": "github:ktsn/vuex#8b6a6f9", "webpack": "^3.8.1", "webpack-espower-loader": "^1.0.2" }, diff --git a/src/bind-store.ts b/src/bind-store.ts index 50d6f77..2954075 100644 --- a/src/bind-store.ts +++ b/src/bind-store.ts @@ -8,22 +8,28 @@ export interface Class { export type MutationMethod

= (payload: P) => void export type ActionMethod

= (payload: P) => Promise -export interface BoundClass extends VueConstructor { - state(map: Key[]): this & Class<{ [K in Key]: State[K] }> - state>(map: Map): this & Class<{ [K in keyof Map]: State[Map[K]] }> +export interface StoreBinder { + create(): Class & typeof Vue + + state(map: Key[]): StoreBinder + state>(map: Map): StoreBinder } -export function bindStore(namespace?: string): BoundClass { - const BoundClass: BoundClass & { options: ComponentOptions } = Vue.extend() as any +export function bindStore(namespace?: string): StoreBinder { + const options: ComponentOptions = {} + + const binder: StoreBinder = { + state(map: string[] | Record) { + options.computed = mapPoly(map, value => makeComputed(value, 'state')) + return binder + }, - BoundClass.state = function state(map: string[] | Record) { - BoundClass.options.computed = mapPoly(map, value => { - return makeComputed(value, 'state') - }) - return BoundClass + create() { + return Vue.extend(options) + } } - return BoundClass + return binder } function mapPoly( diff --git a/test/bind-store.spec.ts b/test/bind-store.spec.ts index b16eda0..79365da 100644 --- a/test/bind-store.spec.ts +++ b/test/bind-store.spec.ts @@ -51,7 +51,9 @@ describe('bindStore', () => { }) it('binds states', () => { - const Super = bindStore().state(['count']) + const Super = bindStore() + .state(['count']) + .create() @Component class Test extends Super {} @@ -63,9 +65,11 @@ describe('bindStore', () => { }) it('binds state by using object mapper', () => { - const Super = bindStore().state({ - value: 'count' - }) + const Super = bindStore() + .state({ + value: 'count' + }) + .create() @Component class Test extends Super {} From 9643a05e9e7a1cfc28f54590900d86eb8d846d8b Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 19 Jan 2018 01:10:01 +0900 Subject: [PATCH 07/10] feat: allow to bind getters --- src/bind-store.ts | 8 ++++++++ test/bind-store.spec.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/bind-store.ts b/src/bind-store.ts index 2954075..f3a926c 100644 --- a/src/bind-store.ts +++ b/src/bind-store.ts @@ -13,6 +13,9 @@ export interface StoreBinder(map: Key[]): StoreBinder state>(map: Map): StoreBinder + + getters(map: Key[]): StoreBinder + getters>(map: Map): StoreBinder } export function bindStore(namespace?: string): StoreBinder { @@ -24,6 +27,11 @@ export function bindStore(namespace?: string return binder }, + getters(map: string[] | Record) { + options.computed = mapPoly(map, value => makeComputed(value, 'getters')) + return binder + }, + create() { return Vue.extend(options) } diff --git a/test/bind-store.spec.ts b/test/bind-store.spec.ts index 79365da..4460660 100644 --- a/test/bind-store.spec.ts +++ b/test/bind-store.spec.ts @@ -79,4 +79,34 @@ describe('bindStore', () => { store.state.count++ assert(vm.value === 1) }) + + it('binds getters', () => { + const Super = bindStore() + .getters(['double']) + .create() + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + assert(vm.double === 0) + store.state.count++ + assert(vm.double === 2) + }) + + it('binds getters by using object mapper', () => { + const Super = bindStore() + .getters({ + multiply2: 'double' + }) + .create() + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + assert(vm.multiply2 === 0) + store.state.count++ + assert(vm.multiply2 === 2) + }) }) From 224d049b0cae2e94bdcfe800955aaec236eff1bd Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 19 Jan 2018 01:14:05 +0900 Subject: [PATCH 08/10] fix: merge mapped computed with existing one --- src/bind-store.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/bind-store.ts b/src/bind-store.ts index f3a926c..1aa4e4d 100644 --- a/src/bind-store.ts +++ b/src/bind-store.ts @@ -1,5 +1,5 @@ import Vue, { VueConstructor, ComponentOptions } from 'vue' -import { mapValues } from './utils' +import { merge, mapValues } from './utils' export interface Class { new (...args: any[]): Instance @@ -23,12 +23,18 @@ export function bindStore(namespace?: string const binder: StoreBinder = { state(map: string[] | Record) { - options.computed = mapPoly(map, value => makeComputed(value, 'state')) + options.computed = merge( + options.computed || {}, + mapPoly(map, value => makeComputed(value, 'state')) + ) return binder }, getters(map: string[] | Record) { - options.computed = mapPoly(map, value => makeComputed(value, 'getters')) + options.computed = merge( + options.computed || {}, + mapPoly(map, value => makeComputed(value, 'getters')) + ) return binder }, From 035113dfcc2f62f884b26d15370f6904f24dc9fa Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 19 Jan 2018 01:25:18 +0900 Subject: [PATCH 09/10] fix: make binder immutable --- src/bind-store.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/bind-store.ts b/src/bind-store.ts index 1aa4e4d..4603b70 100644 --- a/src/bind-store.ts +++ b/src/bind-store.ts @@ -19,31 +19,37 @@ export interface StoreBinder(namespace?: string): StoreBinder { - const options: ComponentOptions = {} + return createBinder({}) +} - const binder: StoreBinder = { +function createBinder(options: ComponentOptions): StoreBinder { + return { state(map: string[] | Record) { - options.computed = merge( + const computed = merge( options.computed || {}, mapPoly(map, value => makeComputed(value, 'state')) ) - return binder + + const newOptions = merge(options, { computed }) + + return createBinder(newOptions) }, getters(map: string[] | Record) { - options.computed = merge( + const computed = merge( options.computed || {}, mapPoly(map, value => makeComputed(value, 'getters')) ) - return binder + + const newOptions = merge(options, { computed }) + + return createBinder(newOptions) }, create() { return Vue.extend(options) } } - - return binder } function mapPoly( From e464875374c6ecd148b1ddc69586ea3527635d82 Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 19 Jan 2018 01:45:46 +0900 Subject: [PATCH 10/10] feat: allow to bind actions/mutations --- package-lock.json | 55 +++++++++++++++++++++++++++ package.json | 1 + src/bind-store.ts | 34 +++++++++++++++++ test/bind-store.spec.ts | 83 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 165 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa8e972..0083175 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2881,6 +2881,12 @@ "kind-of": "3.2.2" } }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, "is-posix-bracket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", @@ -2893,6 +2899,12 @@ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "dev": true }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -3991,6 +4003,27 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "quibble": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/quibble/-/quibble-0.5.3.tgz", + "integrity": "sha512-HL+gtKkDOo1HlxDpWaBd2xbkVg3sQeP0mS39kdF1CzkdNcY0bOVxzjGOs35oEjbDTbL8DtgP24UGgrd0cr9x8w==", + "dev": true, + "requires": { + "lodash": "4.17.4", + "resolve": "1.5.0" + }, + "dependencies": { + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + } + } + }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", @@ -4593,6 +4626,16 @@ "type-name": "2.0.2" } }, + "stringify-object-es5": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stringify-object-es5/-/stringify-object-es5-2.5.0.tgz", + "integrity": "sha1-BXw8mpChJzObudFwSikLt70KHsU=", + "dev": true, + "requires": { + "is-plain-obj": "1.1.0", + "is-regexp": "1.0.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -4646,6 +4689,18 @@ "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", "dev": true }, + "testdouble": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/testdouble/-/testdouble-3.3.2.tgz", + "integrity": "sha512-RUsGZt/tu8A+OkZJa23qnmaEcbAKfSkV0/YVDAg+rEIJg03QUZcVBhUER0JOoHHB/y8S5o3wmieQh8e6bAJk+w==", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "lodash": "4.17.4", + "quibble": "0.5.3", + "stringify-object-es5": "2.5.0" + } + }, "testem": { "version": "1.18.4", "resolved": "https://registry.npmjs.org/testem/-/testem-1.18.4.tgz", diff --git a/package.json b/package.json index 7fd56b8..723dd61 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "rollup": "^0.50.0", "rollup-plugin-replace": "^2.0.0", "sinon": "^4.0.1", + "testdouble": "^3.3.2", "testem": "^1.18.4", "ts-loader": "^3.0.2", "tslint": "^5.7.0", diff --git a/src/bind-store.ts b/src/bind-store.ts index 4603b70..479e647 100644 --- a/src/bind-store.ts +++ b/src/bind-store.ts @@ -16,6 +16,12 @@ export interface StoreBinder(map: Key[]): StoreBinder getters>(map: Map): StoreBinder + + mutations(map: Key[]): StoreBinder }, State, Getters, Mutations, Actions> + mutations>(map: Map): StoreBinder }, State, Getters, Mutations, Actions> + + actions(map: Key[]): StoreBinder }, State, Getters, Mutations, Actions> + actions>(map: Map): StoreBinder }, State, Getters, Mutations, Actions> } export function bindStore(namespace?: string): StoreBinder { @@ -46,6 +52,28 @@ function createBinder(options: ComponentOptions): StoreBinder) { + const methods = merge( + options.methods || {}, + mapPoly(map, value => makeMethod(value, 'commit')) + ) + + const newOptions = merge(options, { methods }) + + return createBinder(newOptions) + }, + + actions(map: string[] | Record) { + const methods = merge( + options.methods || {}, + mapPoly(map, value => makeMethod(value, 'dispatch')) + ) + + const newOptions = merge(options, { methods }) + + return createBinder(newOptions) + }, + create() { return Vue.extend(options) } @@ -71,3 +99,9 @@ function makeComputed(key: string, type: 'state' | 'getters'): () => any { return this.$store[type][key] } } + +function makeMethod(key: string, type: 'dispatch' | 'commit'): (payload: any) => any { + return function boundMethod (this: Vue, payload: any): any { + return (this.$store[type] as Function)(key, payload) + } +} diff --git a/test/bind-store.spec.ts b/test/bind-store.spec.ts index 4460660..3ed7a2c 100644 --- a/test/bind-store.spec.ts +++ b/test/bind-store.spec.ts @@ -1,4 +1,5 @@ import * as assert from 'power-assert' +import * as td from 'testdouble' import Component from 'vue-class-component' import { Store, DefineModule } from 'vuex' import { bindStore } from '../src/bind-store' @@ -30,16 +31,10 @@ const counter: DefineModule = { double: state => state.count * 2 }, mutations: { - increment (state, n) { - state.count += n - } + increment: td.function() as any }, actions: { - incrementAsync ({ commit }, { delay, amount }) { - setTimeout(() => { - commit('increment', amount) - }, delay) - } + incrementAsync: td.function() as any } } @@ -109,4 +104,76 @@ describe('bindStore', () => { store.state.count++ assert(vm.multiply2 === 2) }) + + it('binds mutations', () => { + const Super = bindStore() + .mutations(['increment']) + .create() + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + vm.increment(123) + td.verify(counter.mutations!.increment( + td.matchers.anything(), + 123 + )) + }) + + it('binds mutations by using object mapper', () => { + const Super = bindStore() + .mutations({ + plus: 'increment' + }) + .create() + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + vm.plus(123) + td.verify(counter.mutations!.increment( + td.matchers.anything(), + 123 + )) + }) + + it('binds actions', () => { + const Super = bindStore() + .actions(['incrementAsync']) + .create() + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + vm.incrementAsync({ delay: 100, amount: 42 }) + td.verify(counter.actions!.incrementAsync( + td.matchers.anything(), + { delay: 100, amount: 42 } + ), { + ignoreExtraArgs: true + }) + }) + + it('binds actions by using object mapper', () => { + const Super = bindStore() + .actions({ + delayedPlus: 'incrementAsync' + }) + .create() + + @Component + class Test extends Super {} + + const vm = new Test({ store }) + vm.delayedPlus({ delay: 100, amount: 42 }) + td.verify(counter.actions!.incrementAsync( + td.matchers.anything(), + { delay: 100, amount: 42 } + ), { + ignoreExtraArgs: true + }) + }) })