fix(Compiler): add an error when a directive is null or undefined

fixes #1908
This commit is contained in:
Victor Berchet 2015-05-15 10:16:04 +02:00
parent 666336be1a
commit 25cd6e4321
3 changed files with 82 additions and 12 deletions

View File

@ -92,7 +92,7 @@ export class Compiler {
// Used for bootstrapping. // Used for bootstrapping.
compileInHost(componentTypeOrBinding:any):Promise<ProtoViewRef> { compileInHost(componentTypeOrBinding:any):Promise<ProtoViewRef> {
var componentBinding = this._bindDirective(componentTypeOrBinding); var componentBinding = this._bindDirective(componentTypeOrBinding);
this._assertTypeIsComponent(componentBinding); Compiler._assertTypeIsComponent(componentBinding);
var directiveMetadata = componentBinding.metadata; var directiveMetadata = componentBinding.metadata;
return this._render.compileHost(directiveMetadata).then( (hostRenderPv) => { return this._render.compileHost(directiveMetadata).then( (hostRenderPv) => {
@ -104,7 +104,7 @@ export class Compiler {
compile(component: Type):Promise<ProtoViewRef> { compile(component: Type):Promise<ProtoViewRef> {
var componentBinding = this._bindDirective(component); var componentBinding = this._bindDirective(component);
this._assertTypeIsComponent(componentBinding); Compiler._assertTypeIsComponent(componentBinding);
var protoView = this._compile(componentBinding); var protoView = this._compile(componentBinding);
var pvPromise = PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView); var pvPromise = PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
return pvPromise.then( (appProtoView) => { return pvPromise.then( (appProtoView) => {
@ -134,13 +134,21 @@ export class Compiler {
if (isBlank(template)) { if (isBlank(template)) {
return null; return null;
} }
var directives = ListWrapper.map(
this._flattenDirectives(template), var directives = this._flattenDirectives(template);
(directive) => this._bindDirective(directive)
); for (var i = 0; i < directives.length; i++) {
var renderTemplate = this._buildRenderTemplate(component, template, directives); if (!Compiler._isValidDirective(directives[i])) {
throw new BaseException(
`Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`);
}
}
var boundDirectives = ListWrapper.map(directives, (directive) => this._bindDirective(directive));
var renderTemplate = this._buildRenderTemplate(component, template, boundDirectives);
pvPromise = this._render.compile(renderTemplate).then( (renderPv) => { pvPromise = this._render.compile(renderTemplate).then( (renderPv) => {
return this._compileNestedProtoViews(componentBinding, renderPv, directives); return this._compileNestedProtoViews(componentBinding, renderPv, boundDirectives);
}); });
MapWrapper.set(this._compiling, component, pvPromise); MapWrapper.set(this._compiling, component, pvPromise);
@ -227,7 +235,7 @@ export class Compiler {
return directives; return directives;
} }
_flattenList(tree:List<any>, out:List<Type>):void { _flattenList(tree:List<any>, out:List<any> /*<Type|Binding>*/):void {
for (var i = 0; i < tree.length; i++) { for (var i = 0; i < tree.length; i++) {
var item = tree[i]; var item = tree[i];
if (ListWrapper.isList(item)) { if (ListWrapper.isList(item)) {
@ -238,7 +246,11 @@ export class Compiler {
} }
} }
_assertTypeIsComponent(directiveBinding:DirectiveBinding):void { static _isValidDirective(value: any): boolean {
return isPresent(value) && (value instanceof Type || value instanceof Binding);
}
static _assertTypeIsComponent(directiveBinding:DirectiveBinding):void {
if (directiveBinding.metadata.type !== renderApi.DirectiveMetadata.COMPONENT_TYPE) { if (directiveBinding.metadata.type !== renderApi.DirectiveMetadata.COMPONENT_TYPE) {
throw new BaseException(`Could not load '${stringify(directiveBinding.key.token)}' because it is not a component.`); throw new BaseException(`Could not load '${stringify(directiveBinding.key.token)}' because it is not a component.`);
} }

View File

@ -9,6 +9,7 @@ import {
expect, expect,
iit, iit,
inject, inject,
IS_DARTIUM,
beforeEachBindings, beforeEachBindings,
it, it,
xit xit
@ -823,6 +824,57 @@ export function main() {
}); });
describe("error handling", () => { describe("error handling", () => {
it('should report a meaningful error when a directive is missing annotation',
inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
directives: [SomeDirectiveMissingAnnotation]
}));
PromiseWrapper.catchError(
tb.createView(MyComp, {context: ctx}),
(e) => {
expect(e.message).toEqual('No Directive annotation found on SomeDirectiveMissingAnnotation');
async.done();
}
);
}));
it('should report a meaningful error when a directive is null',
inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
directives: [[null]]
}));
PromiseWrapper.catchError(
tb.createView(MyComp, {context: ctx}),
(e) => {
expect(e.message).toEqual("Unexpected directive value 'null' on the View of component 'MyComp'");
async.done();
}
);
}));
if (!IS_DARTIUM) {
it('should report a meaningful error when a directive is undefined',
inject([TestBed, AsyncTestCompleter], (tb, async) => {
var undefinedValue;
tb.overrideView(MyComp, new View({
directives: [undefinedValue]
}));
PromiseWrapper.catchError(
tb.createView(MyComp, {context: ctx}),
(e) => {
expect(e.message).toEqual("Unexpected directive value 'undefined' on the View of component 'MyComp'");
async.done();
}
);
}));
}
it('should specify a location of an error that happened during change detection (text)', it('should specify a location of an error that happened during change detection (text)',
inject([TestBed, AsyncTestCompleter], (tb, async) => { inject([TestBed, AsyncTestCompleter], (tb, async) => {
@ -1110,7 +1162,6 @@ class MyComp {
} }
} }
@Component({ @Component({
selector: 'component-with-pipes', selector: 'component-with-pipes',
properties: { properties: {
@ -1159,6 +1210,8 @@ class ChildCompUsingService {
}) })
class SomeDirective { } class SomeDirective { }
class SomeDirectiveMissingAnnotation { }
@Component({ @Component({
selector: 'cmp-with-parent' selector: 'cmp-with-parent'
}) })

View File

@ -121,6 +121,11 @@ export function main() {
reflector.registerType(TestObj, {"annotations" : [1,2]}); reflector.registerType(TestObj, {"annotations" : [1,2]});
expect(reflector.annotations(TestObj)).toEqual([1,2]); expect(reflector.annotations(TestObj)).toEqual([1,2]);
}); });
it("should work for a clas without annotations", () => {
var p = reflector.annotations(ClassWithoutAnnotations);
expect(p).toEqual([]);
});
}); });
describe("getter", () => { describe("getter", () => {