', [dirA, dirB])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[
BoundElementPropertyAst,
PropertyBindingType.Property,
'a',
'b',
null,
'TestComp > div:nth-child(0)[[a]=b]'
],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)']
]);
});
it('should locate directives in variable bindings', () => {
var dirA = new DirectiveMetadata(
{selector: '[a=b]', type: new TypeMetadata({typeName: 'DirA'})});
var dirB =
new DirectiveMetadata({selector: '[b]', type: new TypeMetadata({typeName: 'DirB'})});
expect(humanizeTemplateAsts(parse('
', [dirA, dirB])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[#a=b]'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)']
]);
});
it('should parse directive host properties', () => {
var dirA = new DirectiveMetadata({
selector: 'div',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({hostProperties: {'a': 'expr'}})
});
expect(humanizeTemplateAsts(parse('
', [dirA])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[
BoundElementPropertyAst,
PropertyBindingType.Property,
'a',
'expr',
null,
'TestComp > div:nth-child(0)'
]
]);
});
it('should parse directive host listeners', () => {
var dirA = new DirectiveMetadata({
selector: 'div',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({hostListeners: {'a': 'expr'}})
});
expect(humanizeTemplateAsts(parse('
', [dirA])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[BoundEventAst, 'a', null, 'expr', 'TestComp > div:nth-child(0)']
]);
});
it('should parse directive properties', () => {
var dirA = new DirectiveMetadata({
selector: 'div',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['aProp']})
});
expect(humanizeTemplateAsts(parse('
', [dirA])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[
BoundDirectivePropertyAst,
'aProp',
'expr',
'TestComp > div:nth-child(0)[[a-prop]=expr]'
]
]);
});
it('should parse renamed directive properties', () => {
var dirA = new DirectiveMetadata({
selector: 'div',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['b:a']})
});
expect(humanizeTemplateAsts(parse('
', [dirA])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[BoundDirectivePropertyAst, 'b', 'expr', 'TestComp > div:nth-child(0)[[a]=expr]']
]);
});
it('should parse literal directive properties', () => {
var dirA = new DirectiveMetadata({
selector: 'div',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
});
expect(humanizeTemplateAsts(parse('
', [dirA])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[AttrAst, 'a', 'literal', 'TestComp > div:nth-child(0)[a=literal]'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[
BoundDirectivePropertyAst,
'a',
'"literal"',
'TestComp > div:nth-child(0)[a=literal]'
]
]);
});
it('should support optional directive properties', () => {
var dirA = new DirectiveMetadata({
selector: 'div',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
});
expect(humanizeTemplateAsts(parse('
', [dirA])))
.toEqual([
[ElementAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)']
]);
});
});
describe('explicit templates', () => {
it('should create embedded templates for
elements', () => {
expect(humanizeTemplateAsts(parse('', [])))
.toEqual([[EmbeddedTemplateAst, 'TestComp > template:nth-child(0)']]);
});
});
describe('inline templates', () => {
it('should wrap the element into an EmbeddedTemplateAST', () => {
expect(humanizeTemplateAsts(parse('', [])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
[ElementAst, 'TestComp > div:nth-child(0)']
]);
});
it('should parse bound properties', () => {
expect(humanizeTemplateAsts(parse('
', [ngIf])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, ngIf, 'TestComp > div:nth-child(0)'],
[
BoundDirectivePropertyAst,
'ngIf',
'test',
'TestComp > div:nth-child(0)[template=ngIf test]'
],
[ElementAst, 'TestComp > div:nth-child(0)']
]);
});
it('should parse variables via #...', () => {
expect(humanizeTemplateAsts(parse('
', [])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[template=ngIf #a=b]'],
[ElementAst, 'TestComp > div:nth-child(0)']
]);
});
it('should parse variables via var ...', () => {
expect(humanizeTemplateAsts(parse('
', [])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[template=ngIf var a=b]'],
[ElementAst, 'TestComp > div:nth-child(0)']
]);
});
describe('directives', () => {
it('should locate directives in property bindings', () => {
var dirA = new DirectiveMetadata({
selector: '[a=b]',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
});
var dirB = new DirectiveMetadata(
{selector: '[b]', type: new TypeMetadata({typeName: 'DirB'})});
expect(humanizeTemplateAsts(parse('
', [dirA, dirB])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[
BoundDirectivePropertyAst,
'a',
'b',
'TestComp > div:nth-child(0)[template=a b]'
],
[ElementAst, 'TestComp > div:nth-child(0)'],
[AttrAst, 'b', '', 'TestComp > div:nth-child(0)[b=]'],
[DirectiveAst, dirB, 'TestComp > div:nth-child(0)']
]);
});
it('should locate directives in variable bindings', () => {
var dirA = new DirectiveMetadata(
{selector: '[a=b]', type: new TypeMetadata({typeName: 'DirA'})});
var dirB = new DirectiveMetadata(
{selector: '[b]', type: new TypeMetadata({typeName: 'DirB'})});
expect(humanizeTemplateAsts(parse('
', [dirA, dirB])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[template=#a=b]'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[ElementAst, 'TestComp > div:nth-child(0)'],
[AttrAst, 'b', '', 'TestComp > div:nth-child(0)[b=]'],
[DirectiveAst, dirB, 'TestComp > div:nth-child(0)']
]);
});
});
it('should work with *... and use the attribute name as property binding name', () => {
expect(humanizeTemplateAsts(parse('
', [ngIf])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
[DirectiveAst, ngIf, 'TestComp > div:nth-child(0)'],
[
BoundDirectivePropertyAst,
'ngIf',
'test',
'TestComp > div:nth-child(0)[*ng-if=test]'
],
[ElementAst, 'TestComp > div:nth-child(0)']
]);
});
});
});
describe('splitClasses', () => {
it('should keep an empty class', () => { expect(splitClasses('a')).toEqual(['a']); });
it('should split 2 classes', () => { expect(splitClasses('a b')).toEqual(['a', 'b']); });
it('should trim classes', () => { expect(splitClasses(' a b ')).toEqual(['a', 'b']); });
});
describe('error cases', () => {
it('should throw on invalid property names', () => {
expect(() => parse('
', [])).toThrowError(`Template parse errors:
Can't bind to 'invalidProp' since it isn't a known native property in TestComp > div:nth-child(0)[[invalid-prop]=]`);
});
it('should report errors in expressions', () => {
expect(() => parse('
', [])).toThrowError(`Template parse errors:
Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp > div:nth-child(0)[[prop]=a b]`);
});
it('should not throw on invalid property names if the property is used by a directive',
() => {
var dirA = new DirectiveMetadata({
selector: 'div',
type: new TypeMetadata({typeName: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['invalidProp']})
});
expect(() => parse('
', [dirA])).not.toThrow();
});
it('should not allow more than 1 component per element', () => {
var dirA = new DirectiveMetadata(
{selector: 'div', isComponent: true, type: new TypeMetadata({typeName: 'DirA'})});
var dirB = new DirectiveMetadata(
{selector: 'div', isComponent: true, type: new TypeMetadata({typeName: 'DirB'})});
expect(() => parse('
', [dirB, dirA])).toThrowError(`Template parse errors:
More than one component: DirA,DirB in TestComp > div:nth-child(0)`);
});
it('should not allow components or element nor event bindings on explicit embedded templates',
() => {
var dirA = new DirectiveMetadata(
{selector: '[a]', isComponent: true, type: new TypeMetadata({typeName: 'DirA'})});
expect(() => parse('
', [dirA]))
.toThrowError(`Template parse errors:
Components on an embedded template: DirA in TestComp > template:nth-child(0)
Property binding a not used by any directive on an embedded template in TestComp > template:nth-child(0)[[a]=b]
Event binding e on an embedded template in TestComp > template:nth-child(0)[(e)=f]`);
});
it('should not allow components or element bindings on inline embedded templates', () => {
var dirA = new DirectiveMetadata(
{selector: '[a]', isComponent: true, type: new TypeMetadata({typeName: 'DirA'})});
expect(() => parse('
', [dirA])).toThrowError(`Template parse errors:
Components on an embedded template: DirA in TestComp > div:nth-child(0)
Property binding a not used by any directive on an embedded template in TestComp > div:nth-child(0)[*a=b]`);
});
});
});
}
export function humanizeTemplateAsts(templateAsts: TemplateAst[]): any[] {
var humanizer = new TemplateHumanizer();
templateVisitAll(humanizer, templateAsts);
return humanizer.result;
}
class TemplateHumanizer implements TemplateAstVisitor {
result: any[] = [];
visitNgContent(ast: NgContentAst, context: any): any {
this.result.push([NgContentAst, ast.select, ast.sourceInfo]);
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
this.result.push([EmbeddedTemplateAst, ast.sourceInfo]);
templateVisitAll(this, ast.attrs);
templateVisitAll(this, ast.vars);
templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children);
return null;
}
visitElement(ast: ElementAst, context: any): any {
this.result.push([ElementAst, ast.sourceInfo]);
templateVisitAll(this, ast.attrs);
templateVisitAll(this, ast.properties);
templateVisitAll(this, ast.events);
templateVisitAll(this, ast.vars);
templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children);
return null;
}
visitVariable(ast: VariableAst, context: any): any {
this.result.push([VariableAst, ast.name, ast.value, ast.sourceInfo]);
return null;
}
visitEvent(ast: BoundEventAst, context: any): any {
this.result.push([
BoundEventAst,
ast.name,
ast.target,
expressionUnparser.unparse(ast.handler),
ast.sourceInfo
]);
return null;
}
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
this.result.push([
BoundElementPropertyAst,
ast.type,
ast.name,
expressionUnparser.unparse(ast.value),
ast.unit,
ast.sourceInfo
]);
return null;
}
visitAttr(ast: AttrAst, context: any): any {
this.result.push([AttrAst, ast.name, ast.value, ast.sourceInfo]);
return null;
}
visitBoundText(ast: BoundTextAst, context: any): any {
this.result.push([BoundTextAst, expressionUnparser.unparse(ast.value), ast.sourceInfo]);
return null;
}
visitText(ast: TextAst, context: any): any {
this.result.push([TextAst, ast.value, ast.sourceInfo]);
return null;
}
visitDirective(ast: DirectiveAst, context: any): any {
this.result.push([DirectiveAst, ast.directive, ast.sourceInfo]);
templateVisitAll(this, ast.properties);
templateVisitAll(this, ast.hostProperties);
templateVisitAll(this, ast.hostEvents);
return null;
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {
this.result.push([
BoundDirectivePropertyAst,
ast.directiveName,
expressionUnparser.unparse(ast.value),
ast.sourceInfo
]);
return null;
}
}
export class MockSchemaRegistry implements ElementSchemaRegistry {
constructor(public existingProperties: StringMap,
public attrPropMapping: StringMap) {}
hasProperty(tagName: string, property: string): boolean {
var result = this.existingProperties[property];
return isPresent(result) ? result : true;
}
getMappedPropName(attrName: string): string {
var result = this.attrPropMapping[attrName];
return isPresent(result) ? result : attrName;
}
}