', [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;
  }
}