import { ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, inject, beforeEachBindings } from 'angular2/testing_internal'; import {provide} from 'angular2/src/core/di'; import {TEST_PROVIDERS} from './test_bindings'; import {isPresent} from 'angular2/src/facade/lang'; import {TemplateParser, splitClasses} from 'angular2/src/compiler/template_parser'; import { CompileDirectiveMetadata, CompileTypeMetadata, CompileTemplateMetadata } from 'angular2/src/compiler/directive_metadata'; import { templateVisitAll, TemplateAstVisitor, TemplateAst, NgContentAst, EmbeddedTemplateAst, ElementAst, VariableAst, BoundEventAst, BoundElementPropertyAst, BoundDirectivePropertyAst, AttrAst, BoundTextAst, TextAst, PropertyBindingType, DirectiveAst } from 'angular2/src/compiler/template_ast'; import {ElementSchemaRegistry} from 'angular2/src/compiler/schema/element_schema_registry'; import {MockSchemaRegistry} from './schema_registry_mock'; import {Unparser} from '../core/change_detection/parser/unparser'; var expressionUnparser = new Unparser(); export function main() { describe('TemplateParser', () => { beforeEachBindings(() => [ TEST_PROVIDERS, provide(ElementSchemaRegistry, { useValue: new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'}) }) ]); var parser: TemplateParser; var ngIf; beforeEach(inject([TemplateParser], (_parser) => { parser = _parser; ngIf = CompileDirectiveMetadata.create( {selector: '[ng-if]', type: new CompileTypeMetadata({name: 'NgIf'}), inputs: ['ngIf']}); })); function parse(template: string, directives: CompileDirectiveMetadata[]): TemplateAst[] { return parser.parse(template, directives, 'TestComp'); } describe('parse', () => { describe('nodes without bindings', () => { it('should parse text nodes', () => { expect(humanizeTemplateAsts(parse('a', []))) .toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(0)']]); }); it('should parse elements with attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [AttrAst, 'a', 'b', 'TestComp > div:nth-child(0)[a=b]'] ]); }); }); it('should parse ngContent', () => { var parsed = parse('', []); expect(humanizeTemplateAsts(parsed)) .toEqual([[NgContentAst, 'TestComp > ng-content:nth-child(0)']]); }); it('should parse bound text nodes', () => { expect(humanizeTemplateAsts(parse('{{a}}', []))) .toEqual([[BoundTextAst, '{{ a }}', 'TestComp > #text({{a}}):nth-child(0)']]); }); describe('bound properties', () => { it('should parse and camel case bound properties', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Property, 'someProp', 'v', null, 'TestComp > div:nth-child(0)[[some-prop]=v]' ] ]); }); it('should normalize property names via the element schema', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Property, 'mappedProp', 'v', null, 'TestComp > div:nth-child(0)[[mapped-attr]=v]' ] ]); }); it('should parse and camel case bound attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Attribute, 'someAttr', 'v', null, 'TestComp > div:nth-child(0)[[attr.some-attr]=v]' ] ]); }); it('should parse and dash case bound classes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Class, 'some-class', 'v', null, 'TestComp > div:nth-child(0)[[class.some-class]=v]' ] ]); }); it('should parse and camel case bound styles', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Style, 'someStyle', 'v', null, 'TestComp > div:nth-child(0)[[style.some-style]=v]' ] ]); }); it('should parse bound properties via [...] and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null, 'TestComp > div:nth-child(0)[[prop]=v]' ] ]); }); it('should parse bound properties via bind- and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null, 'TestComp > div:nth-child(0)[bind-prop=v]' ] ]); }); it('should parse bound properties via {{...}} and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Property, 'prop', '{{ v }}', null, 'TestComp > div:nth-child(0)[prop={{v}}]' ] ]); }); }); describe('events', () => { it('should parse bound events with a target', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundEventAst, 'event', 'window', 'v', 'TestComp > div:nth-child(0)[(window:event)=v]' ] ]); }); it('should parse bound events via (...) and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [BoundEventAst, 'event', null, 'v', 'TestComp > div:nth-child(0)[(event)=v]'] ]); }); it('should camel case event names', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundEventAst, 'someEvent', null, 'v', 'TestComp > div:nth-child(0)[(some-event)=v]' ] ]); }); it('should parse bound events via on- and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [BoundEventAst, 'event', null, 'v', 'TestComp > div:nth-child(0)[on-event=v]'] ]); }); it('should allow events on explicit embedded templates that are emitted by a directive', () => { var dirA = CompileDirectiveMetadata.create({ selector: 'template', outputs: ['e'], type: new CompileTypeMetadata({name: 'DirA'}) }); expect(humanizeTemplateAsts(parse('', [dirA]))) .toEqual([ [EmbeddedTemplateAst, 'TestComp > template:nth-child(0)'], [BoundEventAst, 'e', null, 'f', 'TestComp > template:nth-child(0)[(e)=f]'], [DirectiveAst, dirA, 'TestComp > template:nth-child(0)'], ]); }); }); describe('bindon', () => { it('should parse bound events and properties via [(...)] and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null, 'TestComp > div:nth-child(0)[[(prop)]=v]' ], [ BoundEventAst, 'propChange', null, 'v = $event', 'TestComp > div:nth-child(0)[[(prop)]=v]' ] ]); }); it('should parse bound events and properties via bindon- and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ BoundElementPropertyAst, PropertyBindingType.Property, 'prop', 'v', null, 'TestComp > div:nth-child(0)[bindon-prop=v]' ], [ BoundEventAst, 'propChange', null, 'v = $event', 'TestComp > div:nth-child(0)[bindon-prop=v]' ] ]); }); }); describe('directives', () => { it('should locate directives components first and ordered by the directives array in the View', () => { var dirA = CompileDirectiveMetadata.create( {selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'})}); var dirB = CompileDirectiveMetadata.create( {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})}); var dirC = CompileDirectiveMetadata.create( {selector: '[c]', type: new CompileTypeMetadata({name: 'DirC'})}); var comp = CompileDirectiveMetadata.create({ selector: 'div', isComponent: true, type: new CompileTypeMetadata({name: 'ZComp'}), template: new CompileTemplateMetadata({ngContentSelectors: []}) }); expect(humanizeTemplateAsts(parse('
', [dirA, dirB, dirC, comp]))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [AttrAst, 'a', '', 'TestComp > div:nth-child(0)[a=]'], [AttrAst, 'b', '', 'TestComp > div:nth-child(0)[b=]'], [AttrAst, 'c', '', 'TestComp > div:nth-child(0)[c=]'], [DirectiveAst, comp, 'TestComp > div:nth-child(0)'], [DirectiveAst, dirA, 'TestComp > div:nth-child(0)'], [DirectiveAst, dirB, 'TestComp > div:nth-child(0)'], [DirectiveAst, dirC, 'TestComp > div:nth-child(0)'] ]); }); it('should locate directives in property bindings', () => { var dirA = CompileDirectiveMetadata.create( {selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})}); var dirB = CompileDirectiveMetadata.create( {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})}); expect(humanizeTemplateAsts(parse('
', [dirA, dirB]))) .toEqual([ [ElementAst, 'div', '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 parse directive host properties', () => { var dirA = CompileDirectiveMetadata.create({ selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), host: {'[a]': 'expr'} }); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', '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 = CompileDirectiveMetadata.create({ selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), host: {'(a)': 'expr'} }); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', '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 = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['aProp']}); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', '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 = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['b:a']}); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', '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 = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']}); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', '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 favor explicit bound properties over literal properties', () => { var dirA = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']}); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [AttrAst, 'a', 'literal', 'TestComp > div:nth-child(0)[a=literal]'], [DirectiveAst, dirA, 'TestComp > div:nth-child(0)'], [ BoundDirectivePropertyAst, 'a', '"literal2"', 'TestComp > div:nth-child(0)[[a]=\'literal2\']' ] ]); }); it('should support optional directive properties', () => { var dirA = CompileDirectiveMetadata.create( {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']}); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [DirectiveAst, dirA, 'TestComp > div:nth-child(0)'] ]); }); }); describe('variables', () => { it('should parse variables via #... and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]'] ]); }); it('should parse variables via var-... and not report them as attributes', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [VariableAst, 'a', '', 'TestComp > div:nth-child(0)[var-a=]'] ]); }); it('should camel case variables', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [VariableAst, 'someA', '', 'TestComp > div:nth-child(0)[var-some-a=]'] ]); }); it('should assign variables with empty value to the element', () => { expect(humanizeTemplateAsts(parse('
', []))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]'] ]); }); it('should assign variables to directives via exportAs', () => { var dirA = CompileDirectiveMetadata.create( {selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'}), exportAs: 'dirA'}); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [AttrAst, 'a', '', 'TestComp > div:nth-child(0)[a=]'], [DirectiveAst, dirA, 'TestComp > div:nth-child(0)'], [VariableAst, 'a', 'dirA', 'TestComp > div:nth-child(0)[#a=dirA]'] ]); }); it('should report variables with values that dont match a directive as errors', () => { expect(() => parse('
', [])).toThrowError(`Template parse errors: There is no directive with "exportAs" set to "dirA" at TestComp > div:nth-child(0)[#a=dirA]`); }); it('should allow variables with values that dont match a directive on embedded template elements', () => { expect(humanizeTemplateAsts(parse('', []))) .toEqual([ [EmbeddedTemplateAst, 'TestComp > template:nth-child(0)'], [VariableAst, 'a', 'b', 'TestComp > template:nth-child(0)[#a=b]'] ]); }); it('should assign variables with empty value to components', () => { var dirA = CompileDirectiveMetadata.create({ selector: '[a]', isComponent: true, type: new CompileTypeMetadata({name: 'DirA'}), exportAs: 'dirA', template: new CompileTemplateMetadata({ngContentSelectors: []}) }); expect(humanizeTemplateAsts(parse('
', [dirA]))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], [AttrAst, 'a', '', 'TestComp > div:nth-child(0)[a=]'], [VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]'], [DirectiveAst, dirA, 'TestComp > div:nth-child(0)'], [VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]'] ]); }); }); describe('explicit templates', () => { it('should create embedded templates for