feat: support decorator chaining and class creation in ES5

Closes #2534
This commit is contained in:
Misko Hevery
2015-06-12 23:51:42 -07:00
parent 4f581671dc
commit c3ae34f066
12 changed files with 371 additions and 75 deletions

View File

@ -0,0 +1,5 @@
library angular2.test.core.annotations.decorators_dart_spec;
main() {
// not relavant for dart.
}

View File

@ -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);
});
});
}

View File

@ -451,17 +451,17 @@ function createRenderViewportElementBinder(nestedProtoView) {
class MainComponent {
}
@Component()
@Component({selector: 'nested'})
class NestedComponent {
}
class RecursiveComponent {}
@Component()
@Component({selector: 'some-dynamic'})
class SomeDynamicComponentDirective {
}
@Directive()
@Directive({selector: 'some'})
class SomeDirective {
}
@ -481,7 +481,7 @@ class DirectiveWithProperties {
class DirectiveWithBind {
}
@Directive()
@Directive({selector: 'directive-with-accts'})
class DirectiveWithAttributes {
constructor(@Attribute('someAttr') someAttr: String) {}
}

View File

@ -21,4 +21,4 @@ export function paramDecorator(value) {
}
export var ClassDecorator = makeDecorator(ClassDecoratorImpl);
export var ParamDecorator = makeParamDecorator(ParamDecoratorImpl);
export var ParamDecorator = makeParamDecorator(ParamDecoratorImpl);

View File

@ -0,0 +1,5 @@
library angular2.test.util.decorators_dart_spec;
main() {
// not relavant for dart.
}

View File

@ -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");
});
});
});
});
}