', [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, 'div', '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, 'div', 'TestComp > div:nth-child(0)']
              ]);
        });
      });
    });
    describe('content projection', () => {
      function createComp(selector: string, ngContentSelectors: string[]): DirectiveMetadata {
        return new DirectiveMetadata({
          selector: selector,
          isComponent: true,
          type: new TypeMetadata({typeName: 'SomeComp'}),
          template: new TemplateMetadata({ngContentSelectors: ngContentSelectors})
        })
      }
      describe('project text nodes', () => {
        it('should project text nodes with wildcard selector', () => {
          expect(humanizeContentProjection(parse('
hello
', [createComp('div', ['*'])])))
              .toEqual([['div', null], ['#text(hello)', 0]]);
        });
      });
      describe('project elements', () => {
        it('should project elements with wildcard selector', () => {
          expect(humanizeContentProjection(
                     parse('
', [createComp('div', ['*'])])))
              .toEqual([['div', null], ['span', 0]]);
        });
        it('should project elements with css selector', () => {
          expect(humanizeContentProjection(
                     parse('
', [createComp('div', ['a[x]'])])))
              .toEqual([['div', null], ['a', 0], ['b', null]]);
        });
      });
      describe('embedded templates', () => {
        it('should project embedded templates with wildcard selector', () => {
          expect(humanizeContentProjection(
                     parse('
', [createComp('div', ['*'])])))
              .toEqual([['div', null], ['template', 0]]);
        });
        it('should project embedded templates with css selector', () => {
          expect(humanizeContentProjection(
                     parse('
',
                           [createComp('div', ['template[x]'])])))
              .toEqual([['div', null], ['template', 0], ['template', null]]);
        });
      });
      describe('ng-content', () => {
        it('should project ng-content with wildcard selector', () => {
          expect(humanizeContentProjection(
                     parse('
', [createComp('div', ['*'])])))
              .toEqual([['div', null], ['ng-content', 0]]);
        });
        it('should project ng-content with css selector', () => {
          expect(humanizeContentProjection(
                     parse('
',
                           [createComp('div', ['ng-content[x]'])])))
              .toEqual([['div', null], ['ng-content', 0], ['ng-content', null]]);
        });
      });
      it('should project into the first matching ng-content', () => {
        expect(humanizeContentProjection(
                   parse('
', [createComp('div', ['a', 'b', '*'])])))
            .toEqual([['div', null], ['#text(hello)', 2], ['b', 1], ['a', 0]]);
      });
      it('should only project direct child nodes', () => {
        expect(humanizeContentProjection(
                   parse('
', [createComp('div', ['a'])])))
            .toEqual([['div', null], ['span', null], ['a', null], ['a', 0]]);
      });
      it('should project nodes of nested components', () => {
        expect(humanizeContentProjection(
                   parse('
hello', [createComp('a', ['*']), createComp('b', ['*'])])))
            .toEqual([['a', null], ['b', 0], ['#text(hello)', 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('
', [])).toThrowErrorWith(`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'}),
          template: new TemplateMetadata({ngContentSelectors: []})
        });
        var dirB = new DirectiveMetadata({
          selector: 'div',
          isComponent: true,
          type: new TypeMetadata({typeName: 'DirB'}),
          template: new TemplateMetadata({ngContentSelectors: []})
        });
        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'}),
             template: new TemplateMetadata({ngContentSelectors: []})
           });
           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'}),
          template: new TemplateMetadata({ngContentSelectors: []})
        });
        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.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.name, 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;
  }
}
function humanizeContentProjection(templateAsts: TemplateAst[]): any[] {
  var humanizer = new TemplateContentProjectionHumanizer();
  templateVisitAll(humanizer, templateAsts);
  return humanizer.result;
}
class TemplateContentProjectionHumanizer implements TemplateAstVisitor {
  result: any[] = [];
  visitNgContent(ast: NgContentAst, context: any): any {
    this.result.push(['ng-content', ast.ngContentIndex]);
    return null;
  }
  visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
    this.result.push(['template', ast.ngContentIndex]);
    templateVisitAll(this, ast.children);
    return null;
  }
  visitElement(ast: ElementAst, context: any): any {
    this.result.push([ast.name, ast.ngContentIndex]);
    templateVisitAll(this, ast.children);
    return null;
  }
  visitVariable(ast: VariableAst, context: any): any { return null; }
  visitEvent(ast: BoundEventAst, context: any): any { return null; }
  visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
  visitAttr(ast: AttrAst, context: any): any { return null; }
  visitBoundText(ast: BoundTextAst, context: any): any {
    this.result.push([`#text(${expressionUnparser.unparse(ast.value)})`, ast.ngContentIndex]);
    return null;
  }
  visitText(ast: TextAst, context: any): any {
    this.result.push([`#text(${ast.value})`, ast.ngContentIndex]);
    return null;
  }
  visitDirective(ast: DirectiveAst, context: any): any { return null; }
  visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { 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;
  }
}