feat(DirectiveParser): throw errors when expected directives are not present

closes #527
Closes #570
This commit is contained in:
Bertrand Laporte
2015-02-06 15:41:02 -08:00
committed by Misko Hevery
parent 715ee14ced
commit 94e203b9df
18 changed files with 354 additions and 123 deletions

View File

@ -1,6 +1,8 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib';
import {DOM} from 'angular2/src/facade/dom';
import {Type, isPresent, BaseException} from 'angular2/src/facade/lang';
import {assertionsEnabled, isJsObject} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di';
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection,
@ -50,7 +52,6 @@ export function main() {
it('should consume text node changes', (done) => {
tplResolver.setTemplate(MyComp, new Template({inline: '<div>{{ctxProp}}</div>'}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
ctx.ctxProp = 'Hello World!';
@ -365,6 +366,60 @@ export function main() {
})
});
});
// TODO support these tests with DART e.g. with Promise.catch (JS) transpiled to Future.catchError (DART)
if (assertionsEnabled() && isJsObject({})) {
function expectCompileError(inlineTpl, errMessage, done) {
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
compiler.compile(MyComp).then(() => {
throw new BaseException("Test failure: should not have come here as an exception was expected");
},(err) => {
expect(err.message).toBe(errMessage);
done();
});
}
it('should raise an error if no directive is registered for an unsupported DOM property', (done) => {
expectCompileError(
'<div [some-prop]="foo"></div>',
'Missing directive to handle \'some-prop\' in MyComp: <div [some-prop]="foo">',
done
);
});
it('should raise an error if no directive is registered for a template with template bindings', (done) => {
expectCompileError(
'<div><div template="if: foo"></div></div>',
'Missing directive to handle \'if\' in <div template="if: foo">',
done
);
});
it('should raise an error for missing template directive (1)', (done) => {
expectCompileError(
'<div><template foo></template></div>',
'Missing directive to handle: <template foo>',
done
);
});
it('should raise an error for missing template directive (2)', (done) => {
expectCompileError(
'<div><template *if="condition"></template></div>',
'Missing directive to handle: <template *if="condition">',
done
);
});
it('should raise an error for missing template directive (3)', (done) => {
expectCompileError(
'<div *if="condition"></div>',
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
done
);
});
}
});
}
@ -473,6 +528,19 @@ class CompWithAncestor {
}
}
@Component({
selector: '[child-cmp2]',
componentServices: [MyService]
})
class ChildComp2 {
ctxProp:string;
dirProp:string;
constructor(service: MyService) {
this.ctxProp = service.greeting;
this.dirProp = null;
}
}
@Viewport({
selector: '[some-viewport]'
})

View File

@ -1,5 +1,5 @@
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib';
import {isPresent} from 'angular2/src/facade/lang';
import {isPresent, assertionsEnabled} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DirectiveParser} from 'angular2/src/core/compiler/pipeline/directive_parser';
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
@ -85,20 +85,20 @@ export function main() {
});
it('should not allow multiple component directives on the same element', () => {
expect( () => {
createPipeline().process(
el('<div some-comp some-comp2></div>')
);
}).toThrowError('Only one component directive per element is allowed!');
expect( () => {
createPipeline().process(
el('<div some-comp some-comp2></div>')
);
}).toThrowError('Multiple component directives not allowed on the same element - check <div some-comp some-comp2>');
});
it('should not allow component directives on <template> elements', () => {
expect( () => {
createPipeline().process(
el('<template some-comp></template>')
);
}).toThrowError('Only template directives are allowed on <template> elements!');
});
expect( () => {
createPipeline().process(
el('<template some-comp></template>')
);
}).toThrowError('Only template directives are allowed on template elements - check <template some-comp>');
});
});
describe('viewport directives', () => {
@ -128,7 +128,7 @@ export function main() {
createPipeline().process(
el('<template some-templ some-templ2></template>')
);
}).toThrowError('Only one template directive per element is allowed!');
}).toThrowError('Only one viewport directive can be used per element - check <template some-templ some-templ2>');
});
it('should not allow viewport directives on non <template> elements', () => {
@ -136,7 +136,8 @@ export function main() {
createPipeline().process(
el('<div some-templ></div>')
);
}).toThrowError('Viewport directives need to be placed on <template> elements or elements with template attribute!');
}).toThrowError('Viewport directives need to be placed on <template> elements or elements with template attribute - check <div some-templ>');
});
});
@ -172,14 +173,6 @@ export function main() {
expect(results[0].decoratorDirectives).toEqual([reader.read(SomeDecorator)]);
});
it('should not allow decorator directives on <template> elements', () => {
expect( () => {
createPipeline().process(
el('<template some-decor></template>')
);
}).toThrowError('Only template directives are allowed on <template> elements!');
});
it('should not instantiate decorator directive twice', () => {
var pipeline = createPipeline({propertyBindings: {
'some-decor-with-binding': 'someExpr'

View File

@ -76,7 +76,7 @@ export function main() {
} else if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView;
}
}), new ElementBinderBuilder(parser, null)
}), new ElementBinderBuilder(parser)
]);
}

View File

@ -12,7 +12,7 @@ export function main() {
function createPipeline(ignoreBindings = false) {
return new CompilePipeline([
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
new PropertyBindingParser(new Parser(new Lexer()), null)]);
new PropertyBindingParser(new Parser(new Lexer()))]);
}
it('should not parse bindings when ignoreBindings is true', () => {

View File

@ -14,7 +14,7 @@ export function main() {
return new CompilePipeline([
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
new IgnoreChildrenStep(),
new TextInterpolationParser(new Parser(new Lexer()), null)
new TextInterpolationParser(new Parser(new Lexer()))
]);
}

View File

@ -11,7 +11,7 @@ export function main() {
describe('ViewSplitter', () => {
function createPipeline() {
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()), null)]);
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]);
}
it('should mark root elements as viewRoot', () => {
@ -160,14 +160,14 @@ export function main() {
expect( () => {
var rootElement = el('<div><div *foo *bar="blah"></div></div>');
createPipeline().process(rootElement);
}).toThrowError('Only one template directive per element is allowed: foo and bar cannot be used simultaneously!');
}).toThrowError('Only one template directive per element is allowed: foo and bar cannot be used simultaneously in <div *foo *bar="blah">');
});
it('should not allow template and bang directives on the same element', () => {
it('should not allow template and star directives on the same element', () => {
expect( () => {
var rootElement = el('<div><div *foo template="blah"></div></div>');
var rootElement = el('<div><div *foo template="bar"></div></div>');
createPipeline().process(rootElement);
}).toThrowError('Only one template directive per element is allowed: blah and foo cannot be used simultaneously!');
}).toThrowError('Only one template directive per element is allowed: bar and foo cannot be used simultaneously in <div *foo template="bar">');
});
});

View File

@ -5,7 +5,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
export function main() {
describe('SelectorMatcher', () => {
var matcher, matched, selectableCollector;
var matcher, matched, selectableCollector, s1, s2, s3, s4;
function reset() {
matched = ListWrapper.create();
@ -13,79 +13,81 @@ export function main() {
beforeEach(() => {
reset();
selectableCollector = (selectable) => {
ListWrapper.push(matched, selectable);
s1 = s2 = s3 = s4 = null;
selectableCollector = (selector, context) => {
ListWrapper.push(matched, selector);
ListWrapper.push(matched, context);
}
matcher = new SelectorMatcher();
});
it('should select by element name case insensitive', () => {
matcher.addSelectable(CssSelector.parse('someTag'), 1);
matcher.addSelectable(s1 = CssSelector.parse('someTag'), 1);
matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector);
expect(matched).toEqual([]);
matcher.match(CssSelector.parse('SOMETAG'), selectableCollector);
expect(matched).toEqual([1]);
expect(matched).toEqual([s1,1]);
});
it('should select by class name case insensitive', () => {
matcher.addSelectable(CssSelector.parse('.someClass'), 1);
matcher.addSelectable(CssSelector.parse('.someClass.class2'), 2);
matcher.addSelectable(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass.class2'), 2);
matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector);
expect(matched).toEqual([]);
matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector);
expect(matched).toEqual([1]);
expect(matched).toEqual([s1,1]);
reset();
matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector);
expect(matched).toEqual([1,2]);
expect(matched).toEqual([s1,1,s2,2]);
});
it('should select by attr name case insensitive independent of the value', () => {
matcher.addSelectable(CssSelector.parse('[someAttr]'), 1);
matcher.addSelectable(CssSelector.parse('[someAttr][someAttr2]'), 2);
matcher.addSelectable(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectable(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector);
expect(matched).toEqual([]);
matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector);
expect(matched).toEqual([1]);
expect(matched).toEqual([s1,1]);
reset();
matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector);
expect(matched).toEqual([1]);
expect(matched).toEqual([s1,1]);
reset();
matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector);
expect(matched).toEqual([1,2]);
expect(matched).toEqual([s1,1,s2,2]);
});
it('should select by attr name only once if the value is from the DOM', () => {
matcher.addSelectable(CssSelector.parse('[some-decor]'), 1);
matcher.addSelectable(s1 = CssSelector.parse('[some-decor]'), 1);
var elementSelector = new CssSelector();
var element = el('<div attr></div>');
var empty = element.getAttribute('attr');
elementSelector.addAttribute('some-decor', empty);
matcher.match(elementSelector, selectableCollector);
expect(matched).toEqual([1]);
expect(matched).toEqual([s1,1]);
});
it('should select by attr name and value case insensitive', () => {
matcher.addSelectable(CssSelector.parse('[someAttr=someValue]'), 1);
matcher.addSelectable(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector);
expect(matched).toEqual([]);
matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector);
expect(matched).toEqual([1]);
expect(matched).toEqual([s1,1]);
});
it('should select by element name, class name and attribute name with value', () => {
matcher.addSelectable(CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
matcher.addSelectable(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector);
expect(matched).toEqual([]);
@ -100,29 +102,29 @@ export function main() {
expect(matched).toEqual([]);
matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector);
expect(matched).toEqual([1]);
expect(matched).toEqual([s1,1]);
});
it('should select independent of the order in the css selector', () => {
matcher.addSelectable(CssSelector.parse('[someAttr].someClass'), 1);
matcher.addSelectable(CssSelector.parse('.someClass[someAttr]'), 2);
matcher.addSelectable(CssSelector.parse('.class1.class2'), 3);
matcher.addSelectable(CssSelector.parse('.class2.class1'), 4);
matcher.addSelectable(s1 = CssSelector.parse('[someAttr].someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
matcher.addSelectable(s3 = CssSelector.parse('.class1.class2'), 3);
matcher.addSelectable(s4 = CssSelector.parse('.class2.class1'), 4);
matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector);
expect(matched).toEqual([1,2]);
expect(matched).toEqual([s1,1,s2,2]);
reset();
matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector);
expect(matched).toEqual([1,2]);
expect(matched).toEqual([s1,1,s2,2]);
reset();
matcher.match(CssSelector.parse('.class1.class2'), selectableCollector);
expect(matched).toEqual([3,4]);
expect(matched).toEqual([s3,3,s4,4]);
reset();
matcher.match(CssSelector.parse('.class2.class1'), selectableCollector);
expect(matched).toEqual([4,3]);
expect(matched).toEqual([s4,4,s3,3]);
});
});