From 5e0ebe54b1fb91275d332461ff542387543c4586 Mon Sep 17 00:00:00 2001 From: Hoa Nguyen Van Date: Sun, 11 Dec 2016 12:32:34 +0700 Subject: [PATCH 1/2] add WatchSub --- README.md | 32 +++++++++++++++++++++++++++- index.ts | 1 + src/watch_sub.ts | 31 +++++++++++++++++++++++++++ test/decorators.ts | 52 +++++++++++++++++++++++++++++++++++----------- 4 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 src/watch_sub.ts diff --git a/README.md b/README.md index cbe087d..f8ecfa8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ npm run dev 4. `render` and life cycle handler like `created` is declared by decorated methods. They are declared in class body because these handlers use `this`. But you cannot invoke them on the instance itself. So they are decorated to remind users. When declaring custom methods, you should avoid these reserved names. -5. `watch` handlers are declared by `@Watch(propName)` decorator, handler type is checked by `keyof` lookup type. +5. `watch` handlers are declared by `@Watch(propName)` decorator, handler type is checked by `keyof` lookup type. Non-strict watches must use `@WatchSub(propPath)` decorator. 6. All other options are considered as component's meta info. So users should declare them in the `@Component` decorator function. @@ -266,6 +266,36 @@ watch: { } ``` +### `WatchSub` +----- + +Same as `@Watch`, `@WatchSub` is applied to a **watched handler**. +`Watch` takes expression name as the first argument, and an optional config object as the second one. + + +```typescript +// watch handler is declared by decorator +properyBeingWatched = { key1: 0 } +@Watch('properyBeingWatched.key1', {deep: true}) +handler(newVal, oldVal) { + console.log('the delta is ' + (newVal - oldVal)) +}) +// .... +``` + +is equivalent to + +```typescript +watch: { + 'properyBeingWatched.key1' { + handler: function(newVal, oldVal) { + console.log('the delta is ' + (newVal - oldVal)) + }, + deep: true + } +} +``` + ### `Lifecycle` and `Render` ----- diff --git a/index.ts b/index.ts index fc07388..df44f51 100644 --- a/index.ts +++ b/index.ts @@ -6,6 +6,7 @@ export {Prop, p} from './src/prop' export {Render} from './src/render' export {Transition} from './src/transition' export {Watch} from './src/watch' +export {WatchSub} from './src/watch_sub' export {Data} from './src/data' export * from './src/functions' diff --git a/src/watch_sub.ts b/src/watch_sub.ts new file mode 100644 index 0000000..7c9d42d --- /dev/null +++ b/src/watch_sub.ts @@ -0,0 +1,31 @@ +import Vue = require('vue') +import {$$Prop} from './interface' +import {Component} from './core' +import {createMap} from './util' +import {WatchOptions} from 'vue/types/options' + +export type VuePropDecorator = (target: Vue, key: string) => void + +const WATCH_PROP = '$$Watch' as $$Prop + + +export type WatchDecoratorSub = + (target: any, key: string, prop: any) => void + +export function WatchSub(keyPath: K, opt: WatchOptions = {}): WatchDecoratorSub { + return function(target: any, method: string) { + let watchedProps = target[WATCH_PROP] = target[WATCH_PROP] || createMap() + opt['handler'] = target[method] + opt['originalMethod'] = method + watchedProps[keyPath] = opt as any + } +} + +Component.register(WATCH_PROP, function(target, instance, optionsToWrite) { + let watchedProps = target[WATCH_PROP] + const watch = optionsToWrite.watch + for (let key in watchedProps) { + watch![key] = watchedProps[key] + delete target[watchedProps[key]['originalMethod']] + } +}) diff --git a/test/decorators.ts b/test/decorators.ts index e109272..5b47301 100644 --- a/test/decorators.ts +++ b/test/decorators.ts @@ -2,7 +2,7 @@ import {MyComponent} from './spec' import {expect} from 'chai' import { Component, Data, Vue, Prop, p, - Watch + Watch, WatchSub } from '../index' @@ -23,14 +23,25 @@ class TestData extends Vue { c = 456 + d : {a: any, b: number} = {a: 0, b: 0}; + @Watch('c', {deep: true}) increaseCounter() { globalCounter++ } + @WatchSub('d.a', {deep: true}) + increaseCounter2() { + globalCounter++ + } + @Data data() { return { - c: this.a + c: this.a, + d: { + a: 1, + b: 2, + } } } } @@ -80,6 +91,10 @@ describe('various decorators', () => { expect(opt.watch).to.haveOwnProperty('c') expect(opt.watch.c).to.haveOwnProperty('deep') expect(opt.watch.c.deep).to.equal(true) + + expect(opt.watch).to.haveOwnProperty('d.a') + expect(opt.watch['d.a']).to.haveOwnProperty('deep') + expect(opt.watch['d.a'].deep).to.equal(true) }) it('should handle various data initilization', () => { @@ -117,15 +132,28 @@ describe('various decorators', () => { propsData: {b: 'test', a: 123} }) expect(globalCounter).to.equal(0) - instance.c = 111 - instance.$nextTick(() => { - expect(globalCounter).to.equal(1) - instance.a = 321 - instance.$nextTick(() => { - expect(globalCounter).to.equal(2) - done() - }) - }) - }) + const testModifications = [ + { handle: () => {instance.c = 111}, inc: true }, + { handle: () => {instance.a = 321}, inc: true }, + { handle: () => {instance.d.a++}, inc: true }, + { handle: () => {instance.d.a = {x: 0}}, inc: true }, + { handle: () => {instance.d.a.x++}, inc: true }, + ]; + + function test(currentTest: number, currentGlobalCouter: number){ + if (currentTest == testModifications.length) { + done(); + } else { + testModifications[currentTest].handle(); + if(testModifications[currentTest].inc) currentGlobalCouter++; + instance.$nextTick(() => { + expect(globalCounter).to.equal(currentGlobalCouter); + test(currentTest+1, currentGlobalCouter); + }); + } + }; + + test(0,0); + }) }) From 3155b0fdcbcb51b9b8b5570d6b8e2feb956ac622 Mon Sep 17 00:00:00 2001 From: Hoa Nguyen Van Date: Sun, 11 Dec 2016 15:41:35 +0700 Subject: [PATCH 2/2] add dist --- .gitignore | 1 - dist/index.d.ts | 12 +++ dist/index.js | 24 +++++ dist/src/core.d.ts | 22 +++++ dist/src/core.js | 175 +++++++++++++++++++++++++++++++++ dist/src/data.d.ts | 5 + dist/src/data.js | 14 +++ dist/src/functions.d.ts | 4 + dist/src/functions.js | 11 +++ dist/src/interface.d.ts | 29 ++++++ dist/src/interface.js | 1 + dist/src/lifecycle.d.ts | 3 + dist/src/lifecycle.js | 18 ++++ dist/src/prop.d.ts | 30 ++++++ dist/src/prop.js | 44 +++++++++ dist/src/render.d.ts | 4 + dist/src/render.js | 14 +++ dist/src/transition.d.ts | 2 + dist/src/transition.js | 5 + dist/src/util.d.ts | 7 ++ dist/src/util.js | 21 ++++ dist/src/watch.d.ts | 8 ++ dist/src/watch.js | 22 +++++ dist/src/watch_sub.d.ts | 5 + dist/src/watch_sub.js | 22 +++++ dist/test/decorators.d.ts | 0 dist/test/decorators.js | 199 ++++++++++++++++++++++++++++++++++++++ dist/test/functions.d.ts | 0 dist/test/functions.js | 115 ++++++++++++++++++++++ dist/test/index.d.ts | 0 dist/test/index.js | 111 +++++++++++++++++++++ dist/test/prop.d.ts | 0 dist/test/prop.js | 54 +++++++++++ dist/test/spec.d.ts | 30 ++++++ dist/test/spec.js | 136 ++++++++++++++++++++++++++ 35 files changed, 1147 insertions(+), 1 deletion(-) create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/src/core.d.ts create mode 100644 dist/src/core.js create mode 100644 dist/src/data.d.ts create mode 100644 dist/src/data.js create mode 100644 dist/src/functions.d.ts create mode 100644 dist/src/functions.js create mode 100644 dist/src/interface.d.ts create mode 100644 dist/src/interface.js create mode 100644 dist/src/lifecycle.d.ts create mode 100644 dist/src/lifecycle.js create mode 100644 dist/src/prop.d.ts create mode 100644 dist/src/prop.js create mode 100644 dist/src/render.d.ts create mode 100644 dist/src/render.js create mode 100644 dist/src/transition.d.ts create mode 100644 dist/src/transition.js create mode 100644 dist/src/util.d.ts create mode 100644 dist/src/util.js create mode 100644 dist/src/watch.d.ts create mode 100644 dist/src/watch.js create mode 100644 dist/src/watch_sub.d.ts create mode 100644 dist/src/watch_sub.js create mode 100644 dist/test/decorators.d.ts create mode 100644 dist/test/decorators.js create mode 100644 dist/test/functions.d.ts create mode 100644 dist/test/functions.js create mode 100644 dist/test/index.d.ts create mode 100644 dist/test/index.js create mode 100644 dist/test/prop.d.ts create mode 100644 dist/test/prop.js create mode 100644 dist/test/spec.d.ts create mode 100644 dist/test/spec.js diff --git a/.gitignore b/.gitignore index f06235c..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ node_modules -dist diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..21d308f --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,12 @@ +import Vue = require('vue'); +export { Component } from './src/core'; +export { Lifecycle } from './src/lifecycle'; +export { Prop, p } from './src/prop'; +export { Render } from './src/render'; +export { Transition } from './src/transition'; +export { Watch } from './src/watch'; +export { WatchSub } from './src/watch_sub'; +export { Data } from './src/data'; +export * from './src/functions'; +export declare type CreateElement = typeof Vue.prototype.$createElement; +export { Vue }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..5a7fcc2 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,24 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +var Vue = require("vue"); +exports.Vue = Vue; +var core_1 = require("./src/core"); +exports.Component = core_1.Component; +var lifecycle_1 = require("./src/lifecycle"); +exports.Lifecycle = lifecycle_1.Lifecycle; +var prop_1 = require("./src/prop"); +exports.Prop = prop_1.Prop; +exports.p = prop_1.p; +var render_1 = require("./src/render"); +exports.Render = render_1.Render; +var transition_1 = require("./src/transition"); +exports.Transition = transition_1.Transition; +var watch_1 = require("./src/watch"); +exports.Watch = watch_1.Watch; +var watch_sub_1 = require("./src/watch_sub"); +exports.WatchSub = watch_sub_1.WatchSub; +var data_1 = require("./src/data"); +exports.Data = data_1.Data; +__export(require("./src/functions")); diff --git a/dist/src/core.d.ts b/dist/src/core.d.ts new file mode 100644 index 0000000..5b8e018 --- /dev/null +++ b/dist/src/core.d.ts @@ -0,0 +1,22 @@ +/** + * The basic idea behind Component is marking on prototype + * and then process these marks to collect options and modify class/instance. + * + * A decorator will mark `internalKey` on prototypes, storgin meta information + * Then register `DecoratorProcessor` on Component, which will be called in `Component` decorator + * `DecoratorProcessor` can execute custom logic based on meta information stored before + * + * For non-annotated fields, `Component` will treat them as `methods` and `computed` in `option` + * instance variable is treated as the return value of `data()` in `option` + * + * So a `DecoratorProcessor` may delete fields on prototype and instance, + * preventing meta properties like lifecycle and prop to pollute `method` and `data` + */ +import Vue = require('vue'); +import { VClass, DecoratorProcessor, ComponentOptions, $$Prop } from './interface'; +export declare function Component>(ctor: T): T; +export declare function Component(config?: ComponentOptions): >(ctor: T) => T; +export declare namespace Component { + function register(key: $$Prop, logic: DecoratorProcessor): void; + let inDefinition: boolean; +} diff --git a/dist/src/core.js b/dist/src/core.js new file mode 100644 index 0000000..a8ec3a0 --- /dev/null +++ b/dist/src/core.js @@ -0,0 +1,175 @@ +/** + * The basic idea behind Component is marking on prototype + * and then process these marks to collect options and modify class/instance. + * + * A decorator will mark `internalKey` on prototypes, storgin meta information + * Then register `DecoratorProcessor` on Component, which will be called in `Component` decorator + * `DecoratorProcessor` can execute custom logic based on meta information stored before + * + * For non-annotated fields, `Component` will treat them as `methods` and `computed` in `option` + * instance variable is treated as the return value of `data()` in `option` + * + * So a `DecoratorProcessor` may delete fields on prototype and instance, + * preventing meta properties like lifecycle and prop to pollute `method` and `data` + */ +"use strict"; +var Vue = require("vue"); +var util_1 = require("./util"); +// option is a full-blown Vue compatible option +// meta is vue.ts specific type for annotation, a subset of option +function makeOptionsFromMeta(meta, name) { + meta.name = name; + for (var _i = 0, _a = ['props', 'computed', 'watch', 'methods']; _i < _a.length; _i++) { + var key = _a[_i]; + if (!util_1.hasOwn(meta, key)) { + meta[key] = {}; + } + } + return meta; +} +// given a vue class' prototype, return its internalKeys and normalKeys +// internalKeys are for decorators' use, like $$Prop, $$Lifecycle +// normalKeys are for methods / computed property +function getKeys(proto) { + var protoKeys = Object.getOwnPropertyNames(proto); + var internalKeys = []; + var normalKeys = []; + for (var _i = 0, protoKeys_1 = protoKeys; _i < protoKeys_1.length; _i++) { + var key = protoKeys_1[_i]; + if (key === 'constructor') { + continue; + } + else if (key.substr(0, 2) === '$$') { + internalKeys.push(key); + } + else { + normalKeys.push(key); + } + } + return { + internalKeys: internalKeys, normalKeys: normalKeys + }; +} +var registeredProcessors = util_1.createMap(); +// delegate to processor +function collectInternalProp(propKey, proto, instance, optionsToWrite) { + var processor = registeredProcessors[propKey]; + if (!processor) { + return; + } + processor(proto, instance, optionsToWrite); +} +// un-annotated and undeleted methods/getters are handled as `methods` and `computed` +function collectMethodsAndComputed(propKey, proto, optionsToWrite) { + var descriptor = Object.getOwnPropertyDescriptor(proto, propKey); + if (!descriptor) { + return; + } + if (typeof descriptor.value === 'function') { + optionsToWrite.methods[propKey] = descriptor.value; + } + else if (descriptor.get || descriptor.set) { + optionsToWrite.computed[propKey] = { + get: descriptor.get, + set: descriptor.set, + }; + } +} +var VUE_KEYS = Object.keys(new Vue); +// find all undeleted instance property as the return value of data() +// need to remove Vue keys to avoid cyclic references +function collectData(cls, keys, optionsToWrite) { + // already implemented by @Data + if (optionsToWrite.data) + return; + // what a closure! :( + optionsToWrite.data = function () { + var selfData = {}; + var vm = this; + // _init is the only method required for `cls` call + // for not data property, set as a readonly prop + // so @Prop does not rewrite it to undefined + cls.prototype._init = function () { + var _loop_1 = function (key) { + if (keys.indexOf(key) >= 0) + return "continue"; + Object.defineProperty(this_1, key, { + get: function () { return vm[key]; }, + set: util_1.NOOP + }); + }; + var this_1 = this; + for (var _i = 0, _a = Object.keys(vm); _i < _a.length; _i++) { + var key = _a[_i]; + _loop_1(key); + } + }; + var proxy = new cls(); + for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + var key = keys_1[_i]; + if (VUE_KEYS.indexOf(key) === -1) { + selfData[key] = proxy[key]; + } + } + return selfData; + }; +} +// find proto's superclass' constructor to correctly extend +function findSuper(proto) { + // prototype: {} -> VueInst -> ParentInst, aka. proto + // constructor: Vue -> Parent -> Child + var superProto = Object.getPrototypeOf(proto); + var Super = superProto instanceof Vue + ? superProto.constructor // TS does not setup constructor :( + : Vue; + return Super; +} +function Component_(meta) { + if (meta === void 0) { meta = {}; } + function decorate(cls) { + Component.inDefinition = true; + // let instance = Object.create(cls.prototype) + // Object.defineProperty(instance, '_init', { + // value: NOOP, enumerable: false + // }) + cls.prototype._init = util_1.NOOP; + var instance = null; + try { + instance = new cls(); + } + finally { + Component.inDefinition = false; + } + delete cls.prototype._init; + var proto = cls.prototype; + var options = makeOptionsFromMeta(meta, cls['name']); + var _a = getKeys(proto), internalKeys = _a.internalKeys, normalKeys = _a.normalKeys; + for (var _i = 0, internalKeys_1 = internalKeys; _i < internalKeys_1.length; _i++) { + var protoKey = internalKeys_1[_i]; + collectInternalProp(protoKey, proto, instance, options); + } + for (var _b = 0, normalKeys_1 = normalKeys; _b < normalKeys_1.length; _b++) { + var protoKey = normalKeys_1[_b]; + collectMethodsAndComputed(protoKey, proto, options); + } + // everything on instance is packed into data + collectData(cls, Object.keys(instance), options); + var Super = findSuper(proto); + return Super.extend(options); + } + return decorate; +} +function Component(target) { + if (typeof target === 'function') { + return Component_()(target); + } + return Component_(target); +} +exports.Component = Component; +(function (Component) { + function register(key, logic) { + registeredProcessors[key] = logic; + } + Component.register = register; + Component.inDefinition = false; +})(Component = exports.Component || (exports.Component = {})); diff --git a/dist/src/data.d.ts b/dist/src/data.d.ts new file mode 100644 index 0000000..d8ffc38 --- /dev/null +++ b/dist/src/data.d.ts @@ -0,0 +1,5 @@ +import Vue = require('vue'); +export declare type Dict = { + [k: string]: any; +}; +export declare function Data(target: Vue, key: 'data', _: TypedPropertyDescriptor<() => Dict>): void; diff --git a/dist/src/data.js b/dist/src/data.js new file mode 100644 index 0000000..f2000a5 --- /dev/null +++ b/dist/src/data.js @@ -0,0 +1,14 @@ +"use strict"; +var core_1 = require("./core"); +var DATA_KEY = '$$data'; +function Data(target, key, _) { + target[DATA_KEY] = target[key]; +} +exports.Data = Data; +core_1.Component.register(DATA_KEY, function (proto, instance, options) { + var dataFunc = proto['data']; + options.data = function () { + return dataFunc.call(this); + }; + delete proto['data']; +}); diff --git a/dist/src/functions.d.ts b/dist/src/functions.d.ts new file mode 100644 index 0000000..9484a7c --- /dev/null +++ b/dist/src/functions.d.ts @@ -0,0 +1,4 @@ +import { VClass } from './interface'; +import Vue = require('vue'); +export declare function Mixin(parent: typeof Vue, ...traits: (typeof Vue)[]): VClass; +export { Component as Trait } from './core'; diff --git a/dist/src/functions.js b/dist/src/functions.js new file mode 100644 index 0000000..73e64b7 --- /dev/null +++ b/dist/src/functions.js @@ -0,0 +1,11 @@ +"use strict"; +function Mixin(parent) { + var traits = []; + for (var _i = 1; _i < arguments.length; _i++) { + traits[_i - 1] = arguments[_i]; + } + return parent.extend({ mixins: traits }); +} +exports.Mixin = Mixin; +var core_1 = require("./core"); +exports.Trait = core_1.Component; diff --git a/dist/src/interface.d.ts b/dist/src/interface.d.ts new file mode 100644 index 0000000..8013d07 --- /dev/null +++ b/dist/src/interface.d.ts @@ -0,0 +1,29 @@ +import { Vue } from 'vue/types/vue'; +import { VNode, VNodeData } from 'vue/types/vnode'; +export { VNode } from 'vue/types/vnode'; +export { PropOptions } from 'vue/types/options'; +import { ComponentOptions, FunctionalComponentOptions } from 'vue/types/options'; +export { ComponentOptions } from 'vue/types/options'; +export declare type Hash = { + [k: string]: V; +}; +export declare type VClass = { + new (): T; + extend(option: ComponentOptions | FunctionalComponentOptions): typeof Vue; +}; +export interface DecoratorProcessor { + (proto: Vue, instance: Vue, options: ComponentOptions): void; +} +export declare type $$Prop = string & { + '$$Prop Brand': never; +}; +export interface ContextObject { + readonly props: T; + readonly children: VNode[]; + readonly slots: Hash; + readonly data: VNodeData; + readonly parent: VNode; +} +export declare type Class = { + new (...args: {}[]): {}; +}; diff --git a/dist/src/interface.js b/dist/src/interface.js new file mode 100644 index 0000000..3918c74 --- /dev/null +++ b/dist/src/interface.js @@ -0,0 +1 @@ +"use strict"; diff --git a/dist/src/lifecycle.d.ts b/dist/src/lifecycle.d.ts new file mode 100644 index 0000000..6025c65 --- /dev/null +++ b/dist/src/lifecycle.d.ts @@ -0,0 +1,3 @@ +import Vue = require('vue'); +export declare type Lifecycles = 'beforeCreate' | 'created' | 'beforeDestroy' | 'destroyed' | 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated' | 'activated' | 'deactivated'; +export declare function Lifecycle(target: Vue, life: Lifecycles, _: TypedPropertyDescriptor<() => void>): void; diff --git a/dist/src/lifecycle.js b/dist/src/lifecycle.js new file mode 100644 index 0000000..7333640 --- /dev/null +++ b/dist/src/lifecycle.js @@ -0,0 +1,18 @@ +"use strict"; +var core_1 = require("./core"); +var util_1 = require("./util"); +var LIFECYCLE_KEY = '$$Lifecycle'; +function Lifecycle(target, life, _) { + var lifecycles = target[LIFECYCLE_KEY] = target[LIFECYCLE_KEY] || util_1.createMap(); + lifecycles[life] = true; +} +exports.Lifecycle = Lifecycle; +core_1.Component.register(LIFECYCLE_KEY, function (proto, instance, options) { + var lifecycles = proto[LIFECYCLE_KEY]; + for (var lifecycle in lifecycles) { + // lifecycles must be on proto because internalKeys is processed before method + var handler = proto[lifecycle]; + delete proto[lifecycle]; + options[lifecycle] = handler; + } +}); diff --git a/dist/src/prop.d.ts b/dist/src/prop.d.ts new file mode 100644 index 0000000..0bf6a2b --- /dev/null +++ b/dist/src/prop.d.ts @@ -0,0 +1,30 @@ +import Vue = require('vue'); +export declare function Prop(target: Vue, key: string): void; +export declare type Class = { + new (...args: {}[]): T; +}; +export interface PlainProp { + type?: Class; + validator?(value: T): boolean; + required?: boolean; +} +export interface DefaultProp extends PlainProp { + default: T | (() => T); +} +export interface RequiredProp extends PlainProp { + required: true; + default?: T | (() => T); +} +export interface FuncProp { + type?: FunctionConstructor; + defaultFunc?: T; + required?: boolean; +} +export declare function p(tpe: NumberConstructor): number | undefined; +export declare function p(tpe: StringConstructor): string | undefined; +export declare function p(tpe: BooleanConstructor): boolean | undefined; +export declare function p(tpe: Class): T | undefined; +export declare function p(conf: RequiredProp): T; +export declare function p(conf: DefaultProp): T; +export declare function p(conf: PlainProp): T | undefined; +export declare function p(conf: FuncProp): T; diff --git a/dist/src/prop.js b/dist/src/prop.js new file mode 100644 index 0000000..18d3c26 --- /dev/null +++ b/dist/src/prop.js @@ -0,0 +1,44 @@ +"use strict"; +var core_1 = require("./core"); +var util_1 = require("./util"); +var PROP_KEY = '$$Prop'; +function Prop(target, key) { + var propKeys = target[PROP_KEY] = target[PROP_KEY] || []; + propKeys.push(key); +} +exports.Prop = Prop; +core_1.Component.register(PROP_KEY, function (proto, instance, options) { + var propKeys = proto[PROP_KEY]; + var props = options.props = options.props || util_1.createMap(); + for (var _i = 0, propKeys_1 = propKeys; _i < propKeys_1.length; _i++) { + var key = propKeys_1[_i]; + var prop = {}; + if (instance[key] != null) { + prop = instance[key]; + delete instance[key]; + } + // refill type if not existing, do we need this? + if (!prop.type) { + prop.type = util_1.getReflectType(proto, key); + } + props[key] = prop; + } + options.props = props; +}); +function p(confOrType) { + if (!core_1.Component.inDefinition) { + return undefined; + } + if (typeof confOrType === 'function') { + var tpe = confOrType; + return { type: tpe }; + } + var conf = confOrType; + if (conf.type === Function) { + conf.default = conf.defaultFunc; + // TODO: evaluate copying a config rather than delete prop + delete conf.defaultFunc; + } + return conf; +} +exports.p = p; diff --git a/dist/src/render.d.ts b/dist/src/render.d.ts new file mode 100644 index 0000000..7ae6ffd --- /dev/null +++ b/dist/src/render.d.ts @@ -0,0 +1,4 @@ +import Vue = require('vue'); +import { VNode } from './interface'; +export declare type RenderFunc = (createElement: typeof Vue.prototype.$createElement) => VNode; +export declare function Render(target: Vue, key: 'render', _: TypedPropertyDescriptor): void; diff --git a/dist/src/render.js b/dist/src/render.js new file mode 100644 index 0000000..01544de --- /dev/null +++ b/dist/src/render.js @@ -0,0 +1,14 @@ +"use strict"; +var core_1 = require("./core"); +var RENDER_KEY = '$$Render'; +var RENDER = 'render'; +function Render(target, key, _) { + target[RENDER_KEY] = true; +} +exports.Render = Render; +core_1.Component.register(RENDER_KEY, function (proto, instance, options) { + if (proto[RENDER_KEY]) { + options[RENDER] = proto[RENDER]; + delete proto[RENDER]; + } +}); diff --git a/dist/src/transition.d.ts b/dist/src/transition.d.ts new file mode 100644 index 0000000..1eb3610 --- /dev/null +++ b/dist/src/transition.d.ts @@ -0,0 +1,2 @@ +import Vue = require('vue'); +export declare function Transition(target: Vue, key: string, _: TypedPropertyDescriptor<(e: HTMLElement, done?: Function) => void>): void; diff --git a/dist/src/transition.js b/dist/src/transition.js new file mode 100644 index 0000000..edc590a --- /dev/null +++ b/dist/src/transition.js @@ -0,0 +1,5 @@ +"use strict"; +// for type checking only +function Transition(target, key, _) { +} +exports.Transition = Transition; diff --git a/dist/src/util.d.ts b/dist/src/util.d.ts new file mode 100644 index 0000000..5d4f77b --- /dev/null +++ b/dist/src/util.d.ts @@ -0,0 +1,7 @@ +export declare function NOOP(): void; +export declare function getReflectType(target: Object, key: string): any; +export interface Map { + [k: string]: T; +} +export declare function createMap(): Map; +export declare function hasOwn(obj: Object, key: string): any; diff --git a/dist/src/util.js b/dist/src/util.js new file mode 100644 index 0000000..592c419 --- /dev/null +++ b/dist/src/util.js @@ -0,0 +1,21 @@ +"use strict"; +function NOOP() { } +exports.NOOP = NOOP; +function getReflectType(target, key) { + if (typeof Reflect === "object" && typeof Reflect.getMetadata === "function") { + return Reflect.getMetadata('design:type', target, key); + } + return null; +} +exports.getReflectType = getReflectType; +function createMap() { + var ret = Object.create(null); + ret["__"] = undefined; + delete ret["__"]; + return ret; +} +exports.createMap = createMap; +function hasOwn(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} +exports.hasOwn = hasOwn; diff --git a/dist/src/watch.d.ts b/dist/src/watch.d.ts new file mode 100644 index 0000000..4ff84ce --- /dev/null +++ b/dist/src/watch.d.ts @@ -0,0 +1,8 @@ +import Vue = require('vue'); +import { WatchOptions } from 'vue/types/options'; +export declare type VuePropDecorator = (target: Vue, key: string) => void; +export declare type WatchHandler = (val: T, oldVal: T) => void; +export declare type WatchDecorator = (target: { + [k in K]: T; +}, key: string, prop: TypedPropertyDescriptor>) => void; +export declare function Watch(key: K, opt?: WatchOptions): WatchDecorator; diff --git a/dist/src/watch.js b/dist/src/watch.js new file mode 100644 index 0000000..b8dba81 --- /dev/null +++ b/dist/src/watch.js @@ -0,0 +1,22 @@ +"use strict"; +var core_1 = require("./core"); +var util_1 = require("./util"); +var WATCH_PROP = '$$Watch'; +function Watch(key, opt) { + if (opt === void 0) { opt = {}; } + return function (target, method) { + var watchedProps = target[WATCH_PROP] = target[WATCH_PROP] || util_1.createMap(); + opt['handler'] = target[method]; + opt['originalMethod'] = method; + watchedProps[key] = opt; + }; +} +exports.Watch = Watch; +core_1.Component.register(WATCH_PROP, function (target, instance, optionsToWrite) { + var watchedProps = target[WATCH_PROP]; + var watch = optionsToWrite.watch; + for (var key in watchedProps) { + watch[key] = watchedProps[key]; + delete target[watchedProps[key]['originalMethod']]; + } +}); diff --git a/dist/src/watch_sub.d.ts b/dist/src/watch_sub.d.ts new file mode 100644 index 0000000..17dbaca --- /dev/null +++ b/dist/src/watch_sub.d.ts @@ -0,0 +1,5 @@ +import Vue = require('vue'); +import { WatchOptions } from 'vue/types/options'; +export declare type VuePropDecorator = (target: Vue, key: string) => void; +export declare type WatchDecoratorSub = (target: any, key: string, prop: any) => void; +export declare function WatchSub(keyPath: K, opt?: WatchOptions): WatchDecoratorSub; diff --git a/dist/src/watch_sub.js b/dist/src/watch_sub.js new file mode 100644 index 0000000..96b2cf9 --- /dev/null +++ b/dist/src/watch_sub.js @@ -0,0 +1,22 @@ +"use strict"; +var core_1 = require("./core"); +var util_1 = require("./util"); +var WATCH_PROP = '$$Watch'; +function WatchSub(keyPath, opt) { + if (opt === void 0) { opt = {}; } + return function (target, method) { + var watchedProps = target[WATCH_PROP] = target[WATCH_PROP] || util_1.createMap(); + opt['handler'] = target[method]; + opt['originalMethod'] = method; + watchedProps[keyPath] = opt; + }; +} +exports.WatchSub = WatchSub; +core_1.Component.register(WATCH_PROP, function (target, instance, optionsToWrite) { + var watchedProps = target[WATCH_PROP]; + var watch = optionsToWrite.watch; + for (var key in watchedProps) { + watch[key] = watchedProps[key]; + delete target[watchedProps[key]['originalMethod']]; + } +}); diff --git a/dist/test/decorators.d.ts b/dist/test/decorators.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/dist/test/decorators.js b/dist/test/decorators.js new file mode 100644 index 0000000..5e12a0b --- /dev/null +++ b/dist/test/decorators.js @@ -0,0 +1,199 @@ +"use strict"; +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var spec_1 = require("./spec"); +var chai_1 = require("chai"); +var index_1 = require("../index"); +var globalCounter = 0; +var TestData = (function (_super) { + __extends(TestData, _super); + function TestData() { + var _this = _super.apply(this, arguments) || this; + _this.a = index_1.p(Number); + _this.c = 456; + _this.d = { a: 0, b: 0 }; + return _this; + } + TestData.prototype.increaseCounter = function () { + globalCounter++; + }; + TestData.prototype.increaseCounter2 = function () { + globalCounter++; + }; + TestData.prototype.data = function () { + return { + c: this.a, + d: { + a: 1, + b: 2, + } + }; + }; + return TestData; +}(index_1.Vue)); +__decorate([ + index_1.Prop, + __metadata("design:type", Object) +], TestData.prototype, "a", void 0); +__decorate([ + index_1.Watch('c', { deep: true }), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], TestData.prototype, "increaseCounter", null); +__decorate([ + index_1.WatchSub('d.a', { deep: true }), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], TestData.prototype, "increaseCounter2", null); +__decorate([ + index_1.Data, + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], TestData.prototype, "data", null); +TestData = __decorate([ + index_1.Component({ + props: { + b: String + }, + watch: { + a: function () { + globalCounter++; + } + } + }), + __metadata("design:paramtypes", []) +], TestData); +describe('various decorators', function () { + it('should handle lifecycle', function () { + var opt = spec_1.MyComponent['options']; + chai_1.expect(opt.beforeCreate).to.be.a('array'); + chai_1.expect(opt).to.not.have.property('created'); + chai_1.expect(opt.methods['created']).to.be.a('function'); + }); + it('should handle render', function () { + var opt = spec_1.MyComponent['options']; + chai_1.expect(opt).to.have.ownProperty('render'); + chai_1.expect(opt.render).to.be.a('function'); + }); + it('should handle watch', function () { + var opt = spec_1.MyComponent['options']; + chai_1.expect(opt.watch).to.have.ownProperty('myWatchee'); + }); + it('should use @Data', function () { + var opt = TestData['options']; + chai_1.expect(opt).to.have.property('data'); + chai_1.expect(opt.data).to.be.a('function'); + chai_1.expect(opt.data.call({ a: 123 }).c).to.equal(123); + chai_1.expect(opt.methods.data).to.equal(undefined); + var instance = new TestData({ propsData: { a: 777 } }); + chai_1.expect(instance).to.have.property('a'); + chai_1.expect(instance.a).to.be.equal(777); + chai_1.expect(instance.data).to.equal(undefined); + }); + it('should merge options', function () { + var opt = TestData['options']; + chai_1.expect(opt).to.have.property('props'); + chai_1.expect(opt.props).to.haveOwnProperty('a'); + chai_1.expect(opt.props).to.haveOwnProperty('b'); + }); + it('should merge watch', function () { + var opt = TestData['options']; + chai_1.expect(opt).to.have.property('watch'); + chai_1.expect(opt.watch).to.haveOwnProperty('a'); + chai_1.expect(opt.watch).to.haveOwnProperty('c'); + chai_1.expect(opt.watch.c).to.haveOwnProperty('deep'); + chai_1.expect(opt.watch.c.deep).to.equal(true); + chai_1.expect(opt.watch).to.haveOwnProperty('d.a'); + chai_1.expect(opt.watch['d.a']).to.haveOwnProperty('deep'); + chai_1.expect(opt.watch['d.a'].deep).to.equal(true); + }); + it('should handle various data initilization', function () { + var Test = (function () { + function Test() { + } + return Test; + }()); + var sharedObject = {}; + var counter = 0; + function cp(t) { + if (index_1.Component.inDefinition) { + counter++; + return t; + } + return undefined; + } + var TestComponent = (function (_super) { + __extends(TestComponent, _super); + function TestComponent() { + var _this = _super.apply(this, arguments) || this; + _this.propValue = cp(Number); + _this.normal = 'normal'; + _this.test = new Test; + _this.own = _this.propValue + 1; + _this.shared = sharedObject; + return _this; + } + return TestComponent; + }(index_1.Vue)); + __decorate([ + index_1.Prop, + __metadata("design:type", Object) + ], TestComponent.prototype, "propValue", void 0); + TestComponent = __decorate([ + index_1.Component, + __metadata("design:paramtypes", []) + ], TestComponent); + var instance = new TestComponent({ + propsData: { propValue: 123 } + }); + chai_1.expect(instance.normal).to.equal('normal'); + chai_1.expect(instance.test).to.be.instanceOf(Test); + chai_1.expect(instance.own).to.equal(124); + chai_1.expect(instance.shared).to.equal(sharedObject); + chai_1.expect(counter).to.equal(1); + }); + it('should make watch run', function (done) { + var instance = new TestData({ + propsData: { b: 'test', a: 123 } + }); + chai_1.expect(globalCounter).to.equal(0); + var testModifications = [ + { handle: function () { instance.c = 111; }, inc: true }, + { handle: function () { instance.a = 321; }, inc: true }, + { handle: function () { instance.d.a++; }, inc: true }, + { handle: function () { instance.d.a = { x: 0 }; }, inc: true }, + { handle: function () { instance.d.a.x++; }, inc: true }, + ]; + function test(currentTest, currentGlobalCouter) { + if (currentTest == testModifications.length) { + done(); + } + else { + testModifications[currentTest].handle(); + if (testModifications[currentTest].inc) + currentGlobalCouter++; + instance.$nextTick(function () { + chai_1.expect(globalCounter).to.equal(currentGlobalCouter); + test(currentTest + 1, currentGlobalCouter); + }); + } + } + ; + test(0, 0); + }); +}); diff --git a/dist/test/functions.d.ts b/dist/test/functions.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/dist/test/functions.js b/dist/test/functions.js new file mode 100644 index 0000000..4bfc324 --- /dev/null +++ b/dist/test/functions.js @@ -0,0 +1,115 @@ +"use strict"; +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var chai_1 = require("chai"); +var index_1 = require("../index"); +describe('util functions', function () { + it('Trait should work like component', function () { + chai_1.expect(index_1.Trait).to.be.equal(index_1.Component); + }); + it('should mixin', function () { + var MA = (function (_super) { + __extends(MA, _super); + function MA() { + var _this = _super.apply(this, arguments) || this; + _this.data1 = 123; + return _this; + } + MA.prototype.myMethod = function () { }; + return MA; + }(index_1.Vue)); + MA = __decorate([ + index_1.Trait, + __metadata("design:paramtypes", []) + ], MA); + var MB = (function (_super) { + __extends(MB, _super); + function MB() { + var _this = _super.apply(this, arguments) || this; + _this.data2 = 456; + return _this; + } + MB.prototype.myMethod2 = function () { }; + return MB; + }(index_1.Vue)); + MB = __decorate([ + index_1.Trait, + __metadata("design:paramtypes", []) + ], MB); + var Mixed = index_1.Mixin(MA, MB); + chai_1.expect(Mixed).to.be.a('function'); + var options = Mixed['options']; + chai_1.expect(options).to.haveOwnProperty('mixins'); + chai_1.expect(options.mixins).to.have.length(1); + var instance = new Mixed; + chai_1.expect(instance).to.haveOwnProperty('data1'); + chai_1.expect(instance.data1).to.equal(123); + chai_1.expect(instance.data2).to.equal(456); + chai_1.expect(instance.myMethod).to.be.a('function'); + chai_1.expect(instance.myMethod2).to.be.a('function'); + }); + it('should mixin instance', function () { + var MA = (function (_super) { + __extends(MA, _super); + function MA() { + var _this = _super.apply(this, arguments) || this; + _this.data1 = 123; + return _this; + } + MA.prototype.myMethod = function () { }; + return MA; + }(index_1.Vue)); + MA = __decorate([ + index_1.Trait, + __metadata("design:paramtypes", []) + ], MA); + var MB = (function (_super) { + __extends(MB, _super); + function MB() { + var _this = _super.apply(this, arguments) || this; + _this.data2 = 456; + return _this; + } + MB.prototype.myMethod2 = function () { }; + return MB; + }(index_1.Vue)); + MB = __decorate([ + index_1.Trait, + __metadata("design:paramtypes", []) + ], MB); + var Mixed = (function (_super) { + __extends(Mixed, _super); + function Mixed() { + var _this = _super.apply(this, arguments) || this; + _this.data3 = 222; + return _this; + } + Mixed.prototype.m = function () { + this.myMethod(); + }; + return Mixed; + }(index_1.Mixin(MA, MB))); + Mixed = __decorate([ + index_1.Component, + __metadata("design:paramtypes", []) + ], Mixed); + var instance = new Mixed; + chai_1.expect(instance).to.haveOwnProperty('data1'); + chai_1.expect(instance.data1).to.equal(123); + chai_1.expect(instance.data2).to.equal(456); + chai_1.expect(instance.myMethod).to.be.a('function'); + chai_1.expect(instance.myMethod2).to.be.a('function'); + }); +}); diff --git a/dist/test/index.d.ts b/dist/test/index.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/dist/test/index.js b/dist/test/index.js new file mode 100644 index 0000000..7062e98 --- /dev/null +++ b/dist/test/index.js @@ -0,0 +1,111 @@ +"use strict"; +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var spec_1 = require("./spec"); +var chai_1 = require("chai"); +var index_1 = require("../index"); +describe('vue component', function () { + it('should return a vue constructor', function () { + chai_1.expect(spec_1.MyComponent).to.haveOwnProperty('options'); + chai_1.expect(spec_1.MyComponent['options']).to.be.a('object'); + }); + it('should new to a vue instance', function () { + var a = new spec_1.MyComponent({ + propsData: { + complex: { test: 123 }, + required: 456, + } + }); + chai_1.expect(a).to.be.instanceOf(index_1.Vue); + }); + it('should have data function in options', function () { + var options = spec_1.MyComponent['options']; + chai_1.expect(options).to.haveOwnProperty('data'); + chai_1.expect(options.data).to.be.a('function'); + var data = options.data(); + chai_1.expect(data).to.be.a('object'); + chai_1.expect(Object.keys(data)).to.be.eql(['myData', 'funcData', 'myWatchee']); + chai_1.expect(data['myData']).to.equal('123'); + chai_1.expect(data['funcData']).to.be.a('function'); + }); + it('should have method in options', function () { + var options = spec_1.MyComponent['options']; + chai_1.expect(options).to.haveOwnProperty('methods'); + chai_1.expect(options.methods).to.have.ownProperty('myMethod'); + chai_1.expect(options.methods['myMethod']).to.be.a('function'); + chai_1.expect(Object.keys(options.methods)).to.be.eql(['myMethod', 'created']); + }); + it('should not have function data in methods', function () { + var options = spec_1.MyComponent['options']; + chai_1.expect(options).to.haveOwnProperty('methods'); + chai_1.expect(options.methods).to.not.have.property('funcData'); + }); + it('should have computed in options', function () { + var options = spec_1.MyComponent['options']; + chai_1.expect(options).to.haveOwnProperty('computed'); + chai_1.expect(options.computed).to.haveOwnProperty('myGetter'); + var myGetter = options.computed['myGetter']; + chai_1.expect(myGetter).to.be.a('object'); + chai_1.expect(myGetter).to.haveOwnProperty('get'); + chai_1.expect(myGetter.get).to.be.a('function'); + }); + it('should handle array in data', function () { + var ArrayComp = (function (_super) { + __extends(ArrayComp, _super); + function ArrayComp() { + var _this = _super.apply(this, arguments) || this; + _this.myArray = [1, 2, 3]; + return _this; + } + return ArrayComp; + }(index_1.Vue)); + ArrayComp = __decorate([ + index_1.Component, + __metadata("design:paramtypes", []) + ], ArrayComp); + var options = ArrayComp['options']; + chai_1.expect(options).to.haveOwnProperty('data'); + var data = options.data(); + chai_1.expect(data).to.haveOwnProperty('myArray'); + var myArray = data.myArray; + chai_1.expect(myArray).to.be.a('array'); + chai_1.expect(myArray.push).to.be.a('function'); + chai_1.expect(myArray).to.have.length(3); + chai_1.expect(myArray[0]).to.equal(1); + chai_1.expect(myArray[1]).to.equal(2); + chai_1.expect(myArray[2]).to.equal(3); + }); + it('should only handle own property', function () { + var sbmoz = Object.create({ + watch: function () { } + }); + chai_1.expect(sbmoz).to.not.have.ownProperty('watch'); + chai_1.expect(sbmoz.watch).to.be.a('function'); + var Test = (function (_super) { + __extends(Test, _super); + function Test() { + return _super.apply(this, arguments) || this; + } + return Test; + }(index_1.Vue)); + Test = __decorate([ + index_1.Component(sbmoz), + __metadata("design:paramtypes", []) + ], Test); + var options = Test['options']; + chai_1.expect(options).to.haveOwnProperty('watch'); + chai_1.expect(options.watch).to.be.an('object'); + }); +}); diff --git a/dist/test/prop.d.ts b/dist/test/prop.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/dist/test/prop.js b/dist/test/prop.js new file mode 100644 index 0000000..4fb9c95 --- /dev/null +++ b/dist/test/prop.js @@ -0,0 +1,54 @@ +"use strict"; +var spec_1 = require("./spec"); +var chai_1 = require("chai"); +describe('prop options', function () { + it('should have prop suboptions in options', function () { + var options = spec_1.MyComponent['options']; + chai_1.expect(options).to.haveOwnProperty('props'); + var props = options.props; + chai_1.expect(props).to.be.a('object'); + chai_1.expect(props).to.haveOwnProperty('myProp'); + chai_1.expect(props).to.haveOwnProperty('complex'); + chai_1.expect(props).to.haveOwnProperty('screwed'); + }); + it('should handle simple prop option', function () { + var props = spec_1.MyComponent['options'].props; + chai_1.expect(props['myProp']).to.deep.equal({ type: Function }, 'simple prop'); + }); + it('it should handle complex prop', function () { + var props = spec_1.MyComponent['options'].props; + var complex = props['complex']; + chai_1.expect(complex['type']).to.equal(Object); + chai_1.expect(complex['required']).to.equal(true); + chai_1.expect(complex['default']).to.be.a('function'); + var defaultProp1 = complex['default'](); + chai_1.expect(defaultProp1).to.deep.equal({ a: 123, b: 456 }); + var defaultProp2 = complex['default'](); + chai_1.expect(defaultProp2).to.deep.equal({ a: 123, b: 456 }, 'idempotency'); + chai_1.expect(defaultProp1).to.not.equal(defaultProp2); + }); + it('should handle prop for function', function () { + var props = spec_1.MyComponent['options'].props; + var screwed = props['screwed']; + chai_1.expect(screwed['type']).to.equal(Function); + chai_1.expect(screwed['default']).to.be.a('function'); + chai_1.expect(screwed).to.not.haveOwnProperty('defaultFunc'); + chai_1.expect(screwed['defaultFunc']).to.equal(undefined); + }); + it('should not set in data options', function () { + var data = spec_1.MyComponent['options'].data; + chai_1.expect(data).to.not.have.property('myProp'); + chai_1.expect(data).to.not.have.property('complex'); + chai_1.expect(data).to.not.have.property('screwed'); + }); + it('should new instance', function () { + var instance = new spec_1.MyComponent({ + propsData: { + complex: { test: 123 }, + required: 456, + } + }); + chai_1.expect(instance.required).to.equal(456); + chai_1.expect(instance.complex['test']).to.equal(123); + }); +}); diff --git a/dist/test/spec.d.ts b/dist/test/spec.d.ts new file mode 100644 index 0000000..5cc4f83 --- /dev/null +++ b/dist/test/spec.d.ts @@ -0,0 +1,30 @@ +import { Vue } from '../index'; +export declare class MyMixin extends Vue { + k: string; +} +declare module 'vue/types/options' { + interface ComponentOptions { + vuex?: {}; + } +} +export declare class MyComponent extends Vue { + myData: string; + funcData: () => void; + myProp: Function | undefined; + complex: Object; + required: Number; + default: number; + screwed: (a: number) => boolean; + myMethod(): void; + readonly myGetter: Function | undefined; + myWatchee: string; + logWatch(str: string): void; + $parent: MyMixin; + $refs: { + mychild: Vue; + }; + $el: HTMLDivElement; + beforeCreate(): void; + created(): void; + render(h: Function): any; +} diff --git a/dist/test/spec.js b/dist/test/spec.js new file mode 100644 index 0000000..157959b --- /dev/null +++ b/dist/test/spec.js @@ -0,0 +1,136 @@ +"use strict"; +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var index_1 = require("../index"); +// import 'reflect-metadata' +var MyMixin = (function (_super) { + __extends(MyMixin, _super); + function MyMixin() { + return _super.apply(this, arguments) || this; + } + return MyMixin; +}(index_1.Vue)); +MyMixin = __decorate([ + index_1.Component, + __metadata("design:paramtypes", []) +], MyMixin); +exports.MyMixin = MyMixin; +var MyComponent = (function (_super) { + __extends(MyComponent, _super); + function MyComponent() { + var _this = _super.apply(this, arguments) || this; + _this.myData = '123'; + _this.funcData = function () { + console.log('ひふみ'); + }; + _this.myProp = index_1.p(Function); + _this.complex = index_1.p({ + type: Object, + required: true, + default: function () { + return { a: 123, b: 456 }; + } + }); + _this.required = index_1.p({ + type: Number, + required: true, + }); + _this.default = index_1.p({ + default: function () { + return 123; + } + }); + _this.screwed = index_1.p({ + type: Function, + // bug: TS cannot infer return type + defaultFunc: function (a) { + return false; + } + }); + _this.myWatchee = 'watch me!'; + return _this; + } + MyComponent.prototype.myMethod = function () { + }; + Object.defineProperty(MyComponent.prototype, "myGetter", { + get: function () { + return this.myProp; + }, + enumerable: true, + configurable: true + }); + MyComponent.prototype.logWatch = function (str) { + console.log(this.myData); + }; + // lifecycle + MyComponent.prototype.beforeCreate = function () { }; + // as method + MyComponent.prototype.created = function () { }; + MyComponent.prototype.render = function (h) { + return h('h1', 'Daisuke'); + }; + return MyComponent; +}(index_1.Vue)); +__decorate([ + index_1.Prop, + __metadata("design:type", Object) +], MyComponent.prototype, "myProp", void 0); +__decorate([ + index_1.Prop, + __metadata("design:type", Object) +], MyComponent.prototype, "complex", void 0); +__decorate([ + index_1.Prop, + __metadata("design:type", Object) +], MyComponent.prototype, "required", void 0); +__decorate([ + index_1.Prop, + __metadata("design:type", Object) +], MyComponent.prototype, "default", void 0); +__decorate([ + index_1.Prop, + __metadata("design:type", Object) +], MyComponent.prototype, "screwed", void 0); +__decorate([ + index_1.Watch('myWatchee'), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], MyComponent.prototype, "logWatch", null); +__decorate([ + index_1.Lifecycle, + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], MyComponent.prototype, "beforeCreate", null); +__decorate([ + index_1.Render, + __metadata("design:type", Function), + __metadata("design:paramtypes", [Function]), + __metadata("design:returntype", void 0) +], MyComponent.prototype, "render", null); +MyComponent = __decorate([ + index_1.Component({ + directives: {}, + components: { abc: {} }, + vuex: {}, + filters: {}, + name: 'my-component', + transitions: {}, + delimiters: ['{{', '}}'], + }), + __metadata("design:paramtypes", []) +], MyComponent); +exports.MyComponent = MyComponent;