feat: support decorator chaining and class creation in ES5
Closes #2534
This commit is contained in:
parent
4f581671dc
commit
c3ae34f066
|
@ -6,6 +6,8 @@
|
||||||
export {
|
export {
|
||||||
Component as ComponentAnnotation,
|
Component as ComponentAnnotation,
|
||||||
Directive as DirectiveAnnotation,
|
Directive as DirectiveAnnotation,
|
||||||
|
ComponentArgs,
|
||||||
|
DirectiveArgs,
|
||||||
onDestroy,
|
onDestroy,
|
||||||
onChange,
|
onChange,
|
||||||
onCheck,
|
onCheck,
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import {ComponentAnnotation, DirectiveAnnotation} from './annotations';
|
import {
|
||||||
import {ViewAnnotation} from './view';
|
ComponentAnnotation,
|
||||||
|
DirectiveAnnotation,
|
||||||
|
ComponentArgs,
|
||||||
|
DirectiveArgs
|
||||||
|
} from './annotations';
|
||||||
|
import {ViewAnnotation, ViewArgs} from './view';
|
||||||
import {
|
import {
|
||||||
SelfAnnotation,
|
SelfAnnotation,
|
||||||
ParentAnnotation,
|
ParentAnnotation,
|
||||||
|
@ -7,14 +12,39 @@ import {
|
||||||
UnboundedAnnotation
|
UnboundedAnnotation
|
||||||
} from './visibility';
|
} from './visibility';
|
||||||
import {AttributeAnnotation, QueryAnnotation} from './di';
|
import {AttributeAnnotation, QueryAnnotation} from './di';
|
||||||
import {makeDecorator, makeParamDecorator} from '../../util/decorators';
|
import {makeDecorator, makeParamDecorator, TypeDecorator, Class} from '../../util/decorators';
|
||||||
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
export interface DirectiveTypeDecorator extends TypeDecorator {}
|
||||||
|
|
||||||
|
export interface ComponentTypeDecorator extends TypeDecorator {
|
||||||
|
View(obj: ViewArgs): ViewTypeDecorator;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewTypeDecorator extends TypeDecorator { View(obj: ViewArgs): ViewTypeDecorator }
|
||||||
|
|
||||||
|
export interface Directive {
|
||||||
|
(obj: any): DirectiveTypeDecorator;
|
||||||
|
new (obj: DirectiveAnnotation): DirectiveAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Component {
|
||||||
|
(obj: any): ComponentTypeDecorator;
|
||||||
|
new (obj: ComponentAnnotation): ComponentAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface View {
|
||||||
|
(obj: ViewArgs): ViewTypeDecorator;
|
||||||
|
new (obj: ViewArgs): ViewAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* from annotations */
|
/* from annotations */
|
||||||
export var Component = makeDecorator(ComponentAnnotation);
|
export var Component = <Component>makeDecorator(ComponentAnnotation, (fn: any) => fn.View = View);
|
||||||
export var Directive = makeDecorator(DirectiveAnnotation);
|
export var Directive = <Directive>makeDecorator(DirectiveAnnotation);
|
||||||
|
|
||||||
/* from view */
|
/* from view */
|
||||||
export var View = makeDecorator(ViewAnnotation);
|
export var View = <View>makeDecorator(ViewAnnotation, (fn: any) => fn.View = View);
|
||||||
|
|
||||||
/* from visibility */
|
/* from visibility */
|
||||||
export var Self = makeParamDecorator(SelfAnnotation);
|
export var Self = makeParamDecorator(SelfAnnotation);
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
export {
|
export {View as ViewAnnotation, ViewArgs} from '../annotations_impl/view';
|
||||||
View as ViewAnnotation,
|
|
||||||
} from '../annotations_impl/view';
|
|
||||||
|
|
|
@ -787,16 +787,7 @@ export class Directive extends Injectable {
|
||||||
constructor({
|
constructor({
|
||||||
selector, properties, events, host, lifecycle, hostInjector, exportAs,
|
selector, properties, events, host, lifecycle, hostInjector, exportAs,
|
||||||
compileChildren = true,
|
compileChildren = true,
|
||||||
}: {
|
}: ComponentArgs = {}) {
|
||||||
selector?: string,
|
|
||||||
properties?: List<string>,
|
|
||||||
events?: List<string>,
|
|
||||||
host?: StringMap<string, string>,
|
|
||||||
lifecycle?: List<LifecycleEvent>,
|
|
||||||
hostInjector?: List<any>,
|
|
||||||
exportAs?: string,
|
|
||||||
compileChildren?: boolean
|
|
||||||
} = {}) {
|
|
||||||
super();
|
super();
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
@ -809,6 +800,17 @@ export class Directive extends Injectable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ComponentArgs {
|
||||||
|
selector?: string;
|
||||||
|
properties?: List<string>;
|
||||||
|
events?: List<string>;
|
||||||
|
host?: StringMap<string, string>;
|
||||||
|
lifecycle?: List<LifecycleEvent>;
|
||||||
|
hostInjector?: List<any>;
|
||||||
|
exportAs?: string;
|
||||||
|
compileChildren?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declare reusable UI building blocks for an application.
|
* Declare reusable UI building blocks for an application.
|
||||||
*
|
*
|
||||||
|
@ -1007,19 +1009,8 @@ export class Component extends Directive {
|
||||||
viewInjector: List<any>;
|
viewInjector: List<any>;
|
||||||
|
|
||||||
constructor({selector, properties, events, host, exportAs, appInjector, lifecycle, hostInjector,
|
constructor({selector, properties, events, host, exportAs, appInjector, lifecycle, hostInjector,
|
||||||
viewInjector, changeDetection = DEFAULT, compileChildren = true}: {
|
viewInjector, changeDetection = DEFAULT,
|
||||||
selector?: string,
|
compileChildren = true}: DirectiveArgs = {}) {
|
||||||
properties?: List<string>,
|
|
||||||
events?: List<string>,
|
|
||||||
host?: StringMap<string, string>,
|
|
||||||
exportAs?: string,
|
|
||||||
appInjector?: List<any>,
|
|
||||||
lifecycle?: List<LifecycleEvent>,
|
|
||||||
hostInjector?: List<any>,
|
|
||||||
viewInjector?: List<any>,
|
|
||||||
changeDetection?: string,
|
|
||||||
compileChildren?: boolean
|
|
||||||
} = {}) {
|
|
||||||
super({
|
super({
|
||||||
selector: selector,
|
selector: selector,
|
||||||
properties: properties,
|
properties: properties,
|
||||||
|
@ -1036,6 +1027,20 @@ export class Component extends Directive {
|
||||||
this.viewInjector = viewInjector;
|
this.viewInjector = viewInjector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export interface DirectiveArgs {
|
||||||
|
selector?: string;
|
||||||
|
properties?: List<string>;
|
||||||
|
events?: List<string>;
|
||||||
|
host?: StringMap<string, string>;
|
||||||
|
exportAs?: string;
|
||||||
|
appInjector?: List<any>;
|
||||||
|
lifecycle?: List<LifecycleEvent>;
|
||||||
|
hostInjector?: List<any>;
|
||||||
|
viewInjector?: List<any>;
|
||||||
|
changeDetection?: string;
|
||||||
|
compileChildren?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lifecycle events are guaranteed to be called in the following order:
|
* Lifecycle events are guaranteed to be called in the following order:
|
||||||
|
|
|
@ -82,15 +82,16 @@ export class View {
|
||||||
*/
|
*/
|
||||||
renderer: string;
|
renderer: string;
|
||||||
|
|
||||||
constructor({templateUrl, template, directives, renderer}: {
|
constructor({templateUrl, template, directives, renderer}: ViewArgs = {}) {
|
||||||
templateUrl?: string,
|
|
||||||
template?: string,
|
|
||||||
directives?: List<Type | any | List<any>>,
|
|
||||||
renderer?: string
|
|
||||||
} = {}) {
|
|
||||||
this.templateUrl = templateUrl;
|
this.templateUrl = templateUrl;
|
||||||
this.template = template;
|
this.template = template;
|
||||||
this.directives = directives;
|
this.directives = directives;
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export interface ViewArgs {
|
||||||
|
templateUrl?: string;
|
||||||
|
template?: string;
|
||||||
|
directives?: List<Type | any | List<any>>;
|
||||||
|
renderer?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -1,48 +1,149 @@
|
||||||
import {global} from 'angular2/src/facade/lang';
|
import {global, Type, isFunction, stringify} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
export function makeDecorator(annotationCls) {
|
export interface ClassDefinition {
|
||||||
return function(...args) {
|
extends?: Type;
|
||||||
var Reflect = global.Reflect;
|
constructor: (Function | Array<any>);
|
||||||
if (!(Reflect && Reflect.getMetadata)) {
|
}
|
||||||
throw 'reflect-metadata shim is required when using class decorators';
|
|
||||||
|
export interface TypeDecorator {
|
||||||
|
(cls: any): any;
|
||||||
|
annotations: Array<any>;
|
||||||
|
Class(obj: ClassDefinition): Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractAnnotation(annotation: any) {
|
||||||
|
if (isFunction(annotation) && annotation.hasOwnProperty('annotation')) {
|
||||||
|
// it is a decorator, extract annotation
|
||||||
|
annotation = annotation.annotation;
|
||||||
|
}
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyParams(fnOrArray: (Function | Array<any>), key: string): Function {
|
||||||
|
if (fnOrArray === Object || fnOrArray === String || fnOrArray === Function ||
|
||||||
|
fnOrArray === Number || fnOrArray === Array) {
|
||||||
|
throw new Error(`Can not use native ${stringify(fnOrArray)} as constructor`);
|
||||||
|
}
|
||||||
|
if (isFunction(fnOrArray)) {
|
||||||
|
return <Function>fnOrArray;
|
||||||
|
} else if (fnOrArray instanceof Array) {
|
||||||
|
var annotations: Array<any> = fnOrArray;
|
||||||
|
var fn: Function = fnOrArray[fnOrArray.length - 1];
|
||||||
|
if (!isFunction(fn)) {
|
||||||
|
throw new Error(
|
||||||
|
`Last position of Class method array must be Function in key ${key} was '${stringify(fn)}'`);
|
||||||
}
|
}
|
||||||
var annotationInstance = Object.create(annotationCls.prototype);
|
var annoLength = annotations.length - 1;
|
||||||
annotationCls.apply(annotationInstance, args);
|
if (annoLength != fn.length) {
|
||||||
return function(cls) {
|
throw new Error(
|
||||||
|
`Number of annotations (${annoLength}) does not match number of arguments (${fn.length}) in the function: ${stringify(fn)}`);
|
||||||
|
}
|
||||||
|
var paramsAnnotations: Array<Array<any>> = [];
|
||||||
|
for (var i = 0, ii = annotations.length - 1; i < ii; i++) {
|
||||||
|
var paramAnnotations: Array<any> = [];
|
||||||
|
paramsAnnotations.push(paramAnnotations);
|
||||||
|
var annotation = annotations[i];
|
||||||
|
if (annotation instanceof Array) {
|
||||||
|
for (var j = 0; j < annotation.length; j++) {
|
||||||
|
paramAnnotations.push(extractAnnotation(annotation[j]));
|
||||||
|
}
|
||||||
|
} else if (isFunction(annotation)) {
|
||||||
|
paramAnnotations.push(extractAnnotation(annotation));
|
||||||
|
} else {
|
||||||
|
paramAnnotations.push(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reflect.defineMetadata('parameters', paramsAnnotations, fn);
|
||||||
|
return fn;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Only Function or Array is supported in Class definition for key '${key}' is '${stringify(fnOrArray)}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var annotations = Reflect.getMetadata('annotations', cls);
|
export function Class(clsDef: ClassDefinition): Type {
|
||||||
annotations = annotations || [];
|
var constructor = applyParams(
|
||||||
annotations.push(annotationInstance);
|
clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor');
|
||||||
Reflect.defineMetadata('annotations', annotations, cls);
|
var proto = constructor.prototype;
|
||||||
return cls;
|
if (clsDef.hasOwnProperty('extends')) {
|
||||||
|
if (isFunction(clsDef.extends)) {
|
||||||
|
(<Function>constructor).prototype = proto =
|
||||||
|
Object.create((<Function>clsDef.extends).prototype);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Class definition 'extends' property must be a constructor function was: ${stringify(clsDef.extends)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (var key in clsDef) {
|
||||||
|
if (key != 'extends' && key != 'prototype' && clsDef.hasOwnProperty(key)) {
|
||||||
|
proto[key] = applyParams(clsDef[key], key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <Type>constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Reflect = global.Reflect;
|
||||||
|
if (!(Reflect && Reflect.getMetadata)) {
|
||||||
|
throw 'reflect-metadata shim is required when using class decorators';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeDecorator(annotationCls, chainFn: (fn: Function) => void = null): (...args) =>
|
||||||
|
(cls: any) => any {
|
||||||
|
function DecoratorFactory(objOrType): (cls: any) => any {
|
||||||
|
var annotationInstance = new (<any>annotationCls)(objOrType);
|
||||||
|
if (this instanceof annotationCls) {
|
||||||
|
return annotationInstance;
|
||||||
|
} else {
|
||||||
|
var chainAnnotation = isFunction(this) && this.annotations instanceof Array ?
|
||||||
|
this.annotations :
|
||||||
|
[];
|
||||||
|
chainAnnotation.push(annotationInstance);
|
||||||
|
var TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls) {
|
||||||
|
var annotations = Reflect.getMetadata('annotations', cls);
|
||||||
|
annotations = annotations || [];
|
||||||
|
annotations.push(annotationInstance);
|
||||||
|
Reflect.defineMetadata('annotations', annotations, cls);
|
||||||
|
return cls;
|
||||||
|
};
|
||||||
|
TypeDecorator.annotations = chainAnnotation;
|
||||||
|
TypeDecorator.Class = Class;
|
||||||
|
if (chainFn) chainFn(TypeDecorator);
|
||||||
|
return TypeDecorator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DecoratorFactory.prototype = Object.create(annotationCls.prototype);
|
||||||
|
return DecoratorFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeParamDecorator(annotationCls): any {
|
export function makeParamDecorator(annotationCls): any {
|
||||||
return function(...args) {
|
function ParamDecoratorFactory(...args) {
|
||||||
var Reflect = global.Reflect;
|
|
||||||
if (!(Reflect && Reflect.getMetadata)) {
|
|
||||||
throw 'reflect-metadata shim is required when using parameter decorators';
|
|
||||||
}
|
|
||||||
var annotationInstance = Object.create(annotationCls.prototype);
|
var annotationInstance = Object.create(annotationCls.prototype);
|
||||||
annotationCls.apply(annotationInstance, args);
|
annotationCls.apply(annotationInstance, args);
|
||||||
return function(cls, unusedKey, index) {
|
if (this instanceof annotationCls) {
|
||||||
var parameters: Array<Array<any>> = Reflect.getMetadata('parameters', cls);
|
return annotationInstance;
|
||||||
parameters = parameters || [];
|
} else {
|
||||||
|
function ParamDecorator(cls, unusedKey, index) {
|
||||||
|
var parameters: Array<Array<any>> = Reflect.getMetadata('parameters', cls);
|
||||||
|
parameters = parameters || [];
|
||||||
|
|
||||||
// there might be gaps if some in between parameters do not have annotations.
|
// there might be gaps if some in between parameters do not have annotations.
|
||||||
// we pad with nulls.
|
// we pad with nulls.
|
||||||
while (parameters.length <= index) {
|
while (parameters.length <= index) {
|
||||||
parameters.push(null);
|
parameters.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[index] = parameters[index] || [];
|
||||||
|
var annotationsForParam: Array<any> = parameters[index];
|
||||||
|
annotationsForParam.push(annotationInstance);
|
||||||
|
|
||||||
|
Reflect.defineMetadata('parameters', parameters, cls);
|
||||||
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters[index] = parameters[index] || [];
|
(<any>ParamDecorator).annotation = annotationInstance;
|
||||||
var annotationsForParam: Array<any> = parameters[index];
|
return ParamDecorator;
|
||||||
annotationsForParam.push(annotationInstance);
|
|
||||||
|
|
||||||
Reflect.defineMetadata('parameters', parameters, cls);
|
|
||||||
return cls;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ParamDecoratorFactory.prototype = Object.create(annotationCls.prototype);
|
||||||
|
return ParamDecoratorFactory;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
library angular2.test.core.annotations.decorators_dart_spec;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
// not relavant for dart.
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {Component, View, Directive} from 'angular2/angular2';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('es5 decorators', () => {
|
||||||
|
it('should declare directive class', () => {
|
||||||
|
var MyDirective = Directive({}).Class({constructor: function() { this.works = true; }});
|
||||||
|
expect(new MyDirective().works).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should declare Component class', () => {
|
||||||
|
var MyComponent =
|
||||||
|
Component({}).View({}).View({}).Class({constructor: function() { this.works = true; }});
|
||||||
|
expect(new MyComponent().works).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -451,17 +451,17 @@ function createRenderViewportElementBinder(nestedProtoView) {
|
||||||
class MainComponent {
|
class MainComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component()
|
@Component({selector: 'nested'})
|
||||||
class NestedComponent {
|
class NestedComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecursiveComponent {}
|
class RecursiveComponent {}
|
||||||
|
|
||||||
@Component()
|
@Component({selector: 'some-dynamic'})
|
||||||
class SomeDynamicComponentDirective {
|
class SomeDynamicComponentDirective {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive()
|
@Directive({selector: 'some'})
|
||||||
class SomeDirective {
|
class SomeDirective {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,7 +481,7 @@ class DirectiveWithProperties {
|
||||||
class DirectiveWithBind {
|
class DirectiveWithBind {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive()
|
@Directive({selector: 'directive-with-accts'})
|
||||||
class DirectiveWithAttributes {
|
class DirectiveWithAttributes {
|
||||||
constructor(@Attribute('someAttr') someAttr: String) {}
|
constructor(@Attribute('someAttr') someAttr: String) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,4 +21,4 @@ export function paramDecorator(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export var ClassDecorator = makeDecorator(ClassDecoratorImpl);
|
export var ClassDecorator = makeDecorator(ClassDecoratorImpl);
|
||||||
export var ParamDecorator = makeParamDecorator(ParamDecoratorImpl);
|
export var ParamDecorator = makeParamDecorator(ParamDecoratorImpl);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
library angular2.test.util.decorators_dart_spec;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
// not relavant for dart.
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {makeDecorator, makeParamDecorator, Class} from 'angular2/src/util/decorators';
|
||||||
|
import {global} from 'angular2/src/facade/lang';
|
||||||
|
import {Inject} from 'angular2/angular2';
|
||||||
|
import {reflector} from 'angular2/src/reflection/reflection';
|
||||||
|
|
||||||
|
class TestAnnotation {
|
||||||
|
constructor(public arg: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TerminalAnnotation {
|
||||||
|
terminal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
var Reflect = global.Reflect;
|
||||||
|
|
||||||
|
var TerminalDecorator = makeDecorator(TerminalAnnotation);
|
||||||
|
var TestDecorator = makeDecorator(TestAnnotation, (fn: any) => fn.Terminal = TerminalDecorator);
|
||||||
|
var TestParamDecorator = makeParamDecorator(TestAnnotation);
|
||||||
|
|
||||||
|
describe('decorators', () => {
|
||||||
|
it('shoulld invoke as decorator', () => {
|
||||||
|
function Type(){};
|
||||||
|
TestDecorator({marker: 'WORKS'})(Type);
|
||||||
|
var annotations = Reflect.getMetadata('annotations', Type);
|
||||||
|
expect(annotations[0].arg.marker).toEqual('WORKS');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should invoke as new', () => {
|
||||||
|
var annotation = new (<any>TestDecorator)({marker: 'WORKS'});
|
||||||
|
expect(annotation instanceof TestAnnotation).toEqual(true);
|
||||||
|
expect(annotation.arg.marker).toEqual('WORKS');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should invoke as chain', () => {
|
||||||
|
var chain: any = TestDecorator({marker: 'WORKS'});
|
||||||
|
expect(typeof chain.Terminal).toEqual('function');
|
||||||
|
chain = chain.Terminal();
|
||||||
|
expect(chain.annotations[0] instanceof TestAnnotation).toEqual(true);
|
||||||
|
expect(chain.annotations[0].arg.marker).toEqual('WORKS');
|
||||||
|
expect(chain.annotations[1] instanceof TerminalAnnotation).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Class', () => {
|
||||||
|
it('should create a class', () => {
|
||||||
|
var i0, i1;
|
||||||
|
var MyClass = Class({
|
||||||
|
extends: Class({
|
||||||
|
constructor: function() {},
|
||||||
|
extendWorks: function() { return 'extend ' + this.arg; }
|
||||||
|
}),
|
||||||
|
constructor: [String, function(arg) { this.arg = arg; }],
|
||||||
|
methodA: [i0 = new Inject(String), [i1 = Inject(String), Number], function(a, b) {}],
|
||||||
|
works: function() { return this.arg; },
|
||||||
|
prototype: 'IGNORE'
|
||||||
|
});
|
||||||
|
var obj: any = new MyClass('WORKS');
|
||||||
|
expect(obj.arg).toEqual('WORKS');
|
||||||
|
expect(obj.works()).toEqual('WORKS');
|
||||||
|
expect(obj.extendWorks()).toEqual('extend WORKS');
|
||||||
|
expect(reflector.parameters(MyClass)).toEqual([[String]]);
|
||||||
|
expect(reflector.parameters(obj.methodA)).toEqual([[i0], [i1.annotation, Number]]);
|
||||||
|
|
||||||
|
var proto = (<Function>MyClass).prototype;
|
||||||
|
expect(proto.extends).toEqual(undefined);
|
||||||
|
expect(proto.prototype).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
it('should ensure that last constructor is required', () => {
|
||||||
|
expect(() => { (<Function>Class)({}); })
|
||||||
|
.toThrowError(
|
||||||
|
"Only Function or Array is supported in Class definition for key 'constructor' is 'undefined'");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should ensure that we dont accidently patch native objects', () => {
|
||||||
|
expect(() => { (<Function>Class)({constructor: Object}); })
|
||||||
|
.toThrowError("Can not use native Object as constructor");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should ensure that last possition is function', () => {
|
||||||
|
expect(() => {Class({constructor: []})})
|
||||||
|
.toThrowError(
|
||||||
|
"Last position of Class method array must be Function in key constructor was 'undefined'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ensure that annotation count matches paramaters count', () => {
|
||||||
|
expect(() => {Class({constructor: [String, function MyType() {}]})})
|
||||||
|
.toThrowError(
|
||||||
|
"Number of annotations (1) does not match number of arguments (0) in the function: MyType");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ensure that only Function|Arrays are supported', () => {
|
||||||
|
expect(() => { Class({constructor: function() {}, method: 'non_function'}); })
|
||||||
|
.toThrowError(
|
||||||
|
"Only Function or Array is supported in Class definition for key 'method' is 'non_function'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ensure that extends is a Function', () => {
|
||||||
|
expect(() => {(<Function>Class)({extends: 'non_type', constructor: function() {}})})
|
||||||
|
.toThrowError(
|
||||||
|
"Class definition 'extends' property must be a constructor function was: non_type");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue