/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import {CommonModule} from '@angular/common';
import {Component, ContentChild, Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, NgModule, OnInit, Output, Pipe, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
describe('acceptance integration tests', () => {
  function stripHtmlComments(str: string) { return str.replace(//g, ''); }
  describe('render', () => {
    it('should render basic template', () => {
      @Component({template: 'Greetings '})
      class App {
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      expect(fixture.nativeElement.innerHTML).toEqual('Greetings ');
    });
    it('should render and update basic "Hello, World" template', () => {
      ngDevModeResetPerfCounters();
      @Component({template: '
Hello, {{name}}! '})
      class App {
        name = '';
      }
      onlyInIvy('perf counters').expectPerfCounters({
        tView: 0,
        tNode: 0,
      });
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      fixture.componentInstance.name = 'World';
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('Hello, World! ');
      onlyInIvy('perf counters').expectPerfCounters({
        tView: 2,  // Host view + App
        tNode: 4,  // Host Node + App Node +  + #text
      });
      fixture.componentInstance.name = 'New World';
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('Hello, New World! ');
      // Assert that the tView/tNode count does not increase (they are correctly cached)
      onlyInIvy('perf counters').expectPerfCounters({
        tView: 2,
        tNode: 4,
      });
    });
  });
  describe('ng-container', () => {
    it('should insert as a child of a regular element', () => {
      @Component(
          {template: 'before|Greetings |after
'})
      class App {
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      // Strip comments since VE and Ivy put them in different places.
      expect(stripHtmlComments(fixture.nativeElement.innerHTML))
          .toBe('before|Greetings
');
    });
    it('should add and remove DOM nodes when ng-container is a child of a regular element', () => {
      @Component({
        template:
            'content 
content
');
      fixture.componentInstance.render = false;
      fixture.detectChanges();
      expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
    });
    it('should add and remove DOM nodes when ng-container is a child of an embedded view', () => {
      @Component({template: 'content '})
      class App {
        render = false;
      }
      TestBed.configureTestingModule({declarations: [App], imports: [CommonModule]});
      const fixture = TestBed.createComponent(App);
      expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
      fixture.componentInstance.render = true;
      fixture.detectChanges();
      expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('content');
      fixture.componentInstance.render = false;
      fixture.detectChanges();
      expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
    });
    // https://stackblitz.com/edit/angular-tfhcz1?file=src%2Fapp%2Fapp.component.ts
    it('should add and remove DOM nodes when ng-container is a child of a delayed embedded view',
       () => {
         @Directive({selector: '[testDirective]'})
         class TestDirective {
           constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {}
           createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
           clear() { this._vcRef.clear(); }
         }
         @Component({
           template: 'content component template '})
      class TestCmpt {
      }
      @Component({template: 'component template ');
    });
    it('should render inside another ng-container', () => {
      @Component({
        selector: 'test-cmpt',
        template:
            'content content ');
    });
    it('should render inside another ng-container at the root of a delayed view', () => {
      @Directive({selector: '[testDirective]'})
      class TestDirective {
        constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {}
        createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
        clear() { this._vcRef.clear(); }
      }
      @Component({
        template:
            'content 
'})
      class App {
        @ViewChild(TestDirective, {static: false}) testDirective !: TestDirective;
      }
      TestBed.configureTestingModule({declarations: [App, TestDirective]});
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('
');
      expect(fixture.componentInstance.testDirective.elRef.nativeElement.nodeType)
          .toBe(Node.COMMENT_NODE);
    });
    it('should support ViewContainerRef when ng-container is at the root of a view', () => {
      @Directive({selector: '[dir]'})
      class TestDirective {
        @Input()
        contentTpl: TemplateRef<{}>|null = null;
        constructor(private _vcRef: ViewContainerRef) {}
        insertView() { this._vcRef.createEmbeddedView(this.contentTpl as TemplateRef<{}>); }
        clear() { this._vcRef.clear(); }
      }
      @Component({
        template:
            'Content  inside ', () => {
      @Directive({selector: '[dir]'})
      class TestDirective {
        constructor(private _tplRef: TemplateRef<{}>, private _vcRef: ViewContainerRef) {}
        insertView() { this._vcRef.createEmbeddedView(this._tplRef); }
        clear() { this._vcRef.clear(); }
      }
      @Component({template: 'Content 
'})
      class App {
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('
');
    });
  });
  describe('text bindings', () => {
    it('should render "undefined" as ""', () => {
      @Component({template: '{{name}}'})
      class App {
        name: string|undefined = 'benoit';
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('benoit');
      fixture.componentInstance.name = undefined;
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('');
    });
    it('should render "null" as ""', () => {
      @Component({template: '{{name}}'})
      class App {
        name: string|null = 'benoit';
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('benoit');
      fixture.componentInstance.name = null;
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('');
    });
  });
  describe('ngNonBindable handling', () => {
    function stripNgNonBindable(str: string) { return str.replace(/ ngnonbindable=""/i, ''); }
    it('should keep local ref for host element', () => {
      @Component({
        template: `
          
            Hello {{ name }}! 
           
          {{ myRef.id }}
        `
      })
      class App {
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(stripNgNonBindable(fixture.nativeElement.innerHTML))
          .toEqual('Hello {{ name }}! 
            Hello {{ name }}! 
           
        `
      })
      class App {
        name = 'World';
      }
      TestBed.configureTestingModule({declarations: [App, TestDirective]});
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(stripNgNonBindable(fixture.nativeElement.innerHTML))
          .toEqual('Hello {{ name }}! 
            Hello {{ name }}! 
           
        `
      })
      class App {
        name = 'World';
      }
      TestBed.configureTestingModule({declarations: [App, TestDirective]});
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(stripNgNonBindable(fixture.nativeElement.innerHTML))
          .toEqual('Hello {{ name }}! Hello {{name}}! '})
      class App {
        name = '';
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      fixture.componentInstance.name = 'world';
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('Hello world! ');
      fixture.componentInstance.name = 'mundo';
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('Hello mundo! ');
    });
    it('should render/update text node as a child of a deep list of elements', () => {
      @Component({template: 'Hello {{name}}! Hello world! Hello mundo! hello
{{name}}'})
      class App {
        name = '';
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      fixture.componentInstance.name = 'world';
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('hello
world');
      fixture.componentInstance.name = 'mundo';
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('hello
mundo');
    });
  });
  describe('basic components', () => {
    @Component({selector: 'todo', template: 'Todo{{value}}
'})
    class TodoComponent {
      value = ' one';
    }
    it('should support a basic component template', () => {
      @Component({template: 'Todo one
Todo one
Todo one
Todo one
one ');
      fixture.componentInstance.todoComponentHostBinding.title = 'two';
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('two ');
    });
    it('should support root component with host attribute', () => {
      @Component({selector: 'host-attr-comp', template: '', host: {'role': 'button'}})
      class HostAttributeComp {
      }
      TestBed.configureTestingModule({declarations: [HostAttributeComp]});
      const fixture = TestBed.createComponent(HostAttributeComp);
      fixture.detectChanges();
      expect(fixture.nativeElement.getAttribute('role')).toEqual('button');
    });
    it('should support component with bindings in template', () => {
      @Component({selector: 'comp', template: '{{ name }}
'})
      class MyComp {
        name = 'Bess';
      }
      @Component({template: 'Bess
text
'})
      class MyComp {
        @Input()
        condition !: boolean;
      }
      @Component({template: 'text
');
      fixture.componentInstance.condition = false;
      fixture.detectChanges();
      expect(stripHtmlComments(compElement.innerHTML)).toEqual('');
    });
  });
  describe('element bindings', () => {
    describe('elementAttribute', () => {
      it('should support attribute bindings', () => {
        @Component({template: '
               
          `
        })
        class App {
          title: string|null = '';
          shouldRender = true;
        }
        TestBed.configureTestingModule({declarations: [App]});
        const fixture = TestBed.createComponent(App);
        fixture.detectChanges();
        const span: HTMLSpanElement = fixture.nativeElement.querySelector('span');
        const bold: HTMLElement = span.querySelector('b') !;
        fixture.componentInstance.title = 'Hello';
        fixture.detectChanges();
        // initial binding
        expect(span.getAttribute('title')).toBe('Hello');
        expect(bold.getAttribute('title')).toBe('Hello');
        // update DOM manually
        bold.setAttribute('title', 'Goodbye');
        // refresh with same binding
        fixture.detectChanges();
        expect(span.getAttribute('title')).toBe('Hello');
        expect(bold.getAttribute('title')).toBe('Goodbye');
        // refresh again with same binding
        fixture.detectChanges();
        expect(span.getAttribute('title')).toBe('Hello');
        expect(bold.getAttribute('title')).toBe('Goodbye');
      });
      it('should support host attribute bindings', () => {
        @Directive({selector: '[hostBindingDir]'})
        class HostBindingDir {
          @HostBinding('attr.aria-label')
          label = 'some label';
        }
        @Component({template: '
'})
        class App {
          @ViewChild(HostBindingDir, {static: false}) hostBindingDir !: HostBindingDir;
        }
        TestBed.configureTestingModule({declarations: [App, HostBindingDir]});
        const fixture = TestBed.createComponent(App);
        fixture.detectChanges();
        const hostBindingEl = fixture.nativeElement.querySelector('div');
        // Needs `toLowerCase`, because different browsers produce
        // attributes either in camel case or lower case.
        expect(hostBindingEl.getAttribute('aria-label')).toBe('some label');
        fixture.componentInstance.hostBindingDir.label = 'other label';
        fixture.detectChanges();
        expect(hostBindingEl.getAttribute('aria-label')).toBe('other label');
      });
    });
    describe('elementStyle', () => {
      it('should support binding to styles', () => {
        @Component({template: ';
          constructor(public vcr: ViewContainerRef) {}
          create() { this.vcr.createEmbeddedView(this.tmp); }
        }
        @Component({
          template: `
            Temp Content 
            
'})
           class App {
             @ViewChild(DirWithClassDirective, {static: false})
             mockClassDirective !: DirWithClassDirective;
           }
           TestBed.configureTestingModule({declarations: [App, DirWithClassDirective]});
           const fixture = TestBed.createComponent(App);
           fixture.detectChanges();
           // the initial values always get sorted in non VE code
           // but there is no sorting guarantee within VE code
           expect(fixture.componentInstance.mockClassDirective.classesVal.split(/\s+/).sort())
               .toEqual(['apple', 'banana', 'orange']);
         });
      it('should delegate initial styles to a [style] input binding if present on a directive on the same element',
         () => {
           @Component({template: '
'})
           class App {
             @ViewChild(DirWithStyleDirective, {static: false})
             mockStyleDirective !: DirWithStyleDirective;
           }
           TestBed.configureTestingModule({declarations: [App, DirWithStyleDirective]});
           const fixture = TestBed.createComponent(App);
           fixture.detectChanges();
           const styles = fixture.componentInstance.mockStyleDirective.stylesVal;
           // Use `toContain` since Ivy and ViewEngine have some slight differences in formatting.
           expect(styles).toContain('width:100px');
           expect(styles).toContain('height:200px');
         });
      it('should update `[class]` and bindings in the provided directive if the input is matched',
         () => {
           @Component({template: '
'})
           class App {
             @ViewChild(DirWithClassDirective, {static: false})
             mockClassDirective !: DirWithClassDirective;
             value = '';
           }
           TestBed.configureTestingModule({declarations: [App, DirWithClassDirective]});
           const fixture = TestBed.createComponent(App);
           fixture.componentInstance.value = 'cucumber grape';
           fixture.detectChanges();
           expect(fixture.componentInstance.mockClassDirective.classesVal)
               .toEqual('cucumber grape');
         });
      onlyInIvy('Passing an object into [style] works differently')
          .it('should update `[style]` and bindings in the provided directive if the input is matched',
              () => {
                @Component({template: '
'})
                class App {
                  @ViewChild(DirWithStyleDirective, {static: false})
                  mockStyleDirective !: DirWithStyleDirective;
                  value !: {[key: string]: string};
                }
                TestBed.configureTestingModule({declarations: [App, DirWithStyleDirective]});
                const fixture = TestBed.createComponent(App);
                fixture.componentInstance.value = {width: '200px', height: '500px'};
                fixture.detectChanges();
                expect(fixture.componentInstance.mockStyleDirective.stylesVal)
                    .toEqual({'width': '200px', 'height': '500px'});
              });
      onlyInIvy('Style binding merging works differently in Ivy')
          .it('should apply initial styling to the element that contains the directive with host styling',
              () => {
                @Directive({
                  selector: '[DirWithInitialStyling]',
                  host: {
                    'title': 'foo',
                    'class': 'heavy golden',
                    'style': 'color: purple',
                    '[style.font-weight]': '"bold"'
                  }
                })
                class DirWithInitialStyling {
                }
                @Component({
                  template: `
                
             `
                })
                class App {
                }
                TestBed.configureTestingModule({declarations: [App, DirWithInitialStyling]});
                const fixture = TestBed.createComponent(App);
                fixture.detectChanges();
                const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
                const classes = target.getAttribute('class') !.split(/\s+/).sort();
                expect(classes).toEqual(['big', 'golden', 'heavy']);
                expect(target.getAttribute('title')).toEqual('foo');
                expect(target.style.getPropertyValue('color')).toEqual('black');
                expect(target.style.getPropertyValue('font-size')).toEqual('200px');
                expect(target.style.getPropertyValue('font-weight')).toEqual('bold');
              });
      onlyInIvy('Style binding merging works differently in Ivy')
          .it('should apply single styling bindings present within a directive onto the same element and defer the element\'s initial styling values when missing',
              () => {
                @Directive({
                  selector: '[DirWithSingleStylingBindings]',
                  host: {
                    'class': 'def',
                    '[class.xyz]': 'activateXYZClass',
                    '[style.width]': 'width',
                    '[style.height]': 'height'
                  }
                })
                class DirWithSingleStylingBindings {
                  width: null|string = null;
                  height: null|string = null;
                  activateXYZClass: boolean = false;
                }
                @Component({
                  template: `
              
            `
                })
                class App {
                  @ViewChild(DirWithSingleStylingBindings, {static: false})
                  dirInstance !: DirWithSingleStylingBindings;
                }
                TestBed.configureTestingModule({declarations: [App, DirWithSingleStylingBindings]});
                const fixture = TestBed.createComponent(App);
                fixture.detectChanges();
                const dirInstance = fixture.componentInstance.dirInstance;
                const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
                expect(target.style.getPropertyValue('width')).toEqual('100px');
                expect(target.style.getPropertyValue('height')).toEqual('200px');
                expect(target.classList.contains('abc')).toBeTruthy();
                expect(target.classList.contains('def')).toBeTruthy();
                expect(target.classList.contains('xyz')).toBeFalsy();
                dirInstance.width = '444px';
                dirInstance.height = '999px';
                dirInstance.activateXYZClass = true;
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('444px');
                expect(target.style.getPropertyValue('height')).toEqual('999px');
                expect(target.classList.contains('abc')).toBeTruthy();
                expect(target.classList.contains('def')).toBeTruthy();
                expect(target.classList.contains('xyz')).toBeTruthy();
                dirInstance.width = null;
                dirInstance.height = null;
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('100px');
                expect(target.style.getPropertyValue('height')).toEqual('200px');
                expect(target.classList.contains('abc')).toBeTruthy();
                expect(target.classList.contains('def')).toBeTruthy();
                expect(target.classList.contains('xyz')).toBeTruthy();
              });
      onlyInIvy('Style binding merging works differently in Ivy')
          .it('should properly prioritize single style binding collisions when they exist on multiple directives',
              () => {
                @Directive({selector: '[Dir1WithStyle]', host: {'[style.width]': 'width'}})
                class Dir1WithStyle {
                  width: null|string = null;
                }
                @Directive({
                  selector: '[Dir2WithStyle]',
                  host: {'style': 'width: 111px', '[style.width]': 'width'}
                })
                class Dir2WithStyle {
                  width: null|string = null;
                }
                @Component(
                    {template: '
'})
                class App {
                  @ViewChild(Dir1WithStyle, {static: false}) dir1Instance !: Dir1WithStyle;
                  @ViewChild(Dir2WithStyle, {static: false}) dir2Instance !: Dir2WithStyle;
                  width: string|null = null;
                }
                TestBed.configureTestingModule({declarations: [App, Dir1WithStyle, Dir2WithStyle]});
                const fixture = TestBed.createComponent(App);
                fixture.detectChanges();
                const {dir1Instance, dir2Instance} = fixture.componentInstance;
                const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
                expect(target.style.getPropertyValue('width')).toEqual('111px');
                fixture.componentInstance.width = '999px';
                dir1Instance.width = '222px';
                dir2Instance.width = '333px';
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('999px');
                fixture.componentInstance.width = null;
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('222px');
                dir1Instance.width = null;
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('333px');
                dir2Instance.width = null;
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('111px');
                dir1Instance.width = '666px';
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('666px');
                fixture.componentInstance.width = '777px';
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('777px');
              });
      onlyInIvy('Style binding merging works differently in Ivy')
          .it('should properly prioritize multi style binding collisions when they exist on multiple directives',
              () => {
                @Directive({
                  selector: '[Dir1WithStyling]',
                  host: {'[style]': 'stylesExp', '[class]': 'classesExp'}
                })
                class Dir1WithStyling {
                  classesExp: any = {};
                  stylesExp: any = {};
                }
                @Directive({
                  selector: '[Dir2WithStyling]',
                  host: {'style': 'width: 111px', '[style]': 'stylesExp'}
                })
                class Dir2WithStyling {
                  stylesExp: any = {};
                }
                @Component({
                  template:
                      '
'
                })
                class App {
                  @ViewChild(Dir1WithStyling, {static: false}) dir1Instance !: Dir1WithStyling;
                  @ViewChild(Dir2WithStyling, {static: false}) dir2Instance !: Dir2WithStyling;
                  stylesExp: any = {};
                  classesExp: any = {};
                }
                TestBed.configureTestingModule(
                    {declarations: [App, Dir1WithStyling, Dir2WithStyling]});
                const fixture = TestBed.createComponent(App);
                fixture.detectChanges();
                const {dir1Instance, dir2Instance} = fixture.componentInstance;
                const target = fixture.nativeElement.querySelector('div') !;
                expect(target.style.getPropertyValue('width')).toEqual('111px');
                const compInstance = fixture.componentInstance;
                compInstance.stylesExp = {width: '999px', height: null};
                compInstance.classesExp = {one: true, two: false};
                dir1Instance.stylesExp = {width: '222px'};
                dir1Instance.classesExp = {two: true, three: false};
                dir2Instance.stylesExp = {width: '333px', height: '100px'};
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('999px');
                expect(target.style.getPropertyValue('height')).toEqual('100px');
                expect(target.classList.contains('one')).toBeTruthy();
                expect(target.classList.contains('two')).toBeFalsy();
                expect(target.classList.contains('three')).toBeFalsy();
                compInstance.stylesExp = {};
                compInstance.classesExp = {};
                dir1Instance.stylesExp = {width: '222px', height: '200px'};
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('222px');
                expect(target.style.getPropertyValue('height')).toEqual('200px');
                expect(target.classList.contains('one')).toBeFalsy();
                expect(target.classList.contains('two')).toBeTruthy();
                expect(target.classList.contains('three')).toBeFalsy();
                dir1Instance.stylesExp = {};
                dir1Instance.classesExp = {};
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('333px');
                expect(target.style.getPropertyValue('height')).toEqual('100px');
                expect(target.classList.contains('one')).toBeFalsy();
                expect(target.classList.contains('two')).toBeFalsy();
                expect(target.classList.contains('three')).toBeFalsy();
                dir2Instance.stylesExp = {};
                compInstance.stylesExp = {height: '900px'};
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('111px');
                expect(target.style.getPropertyValue('height')).toEqual('900px');
                dir1Instance.stylesExp = {width: '666px', height: '600px'};
                dir1Instance.classesExp = {four: true, one: true};
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('666px');
                expect(target.style.getPropertyValue('height')).toEqual('900px');
                expect(target.classList.contains('one')).toBeTruthy();
                expect(target.classList.contains('two')).toBeFalsy();
                expect(target.classList.contains('three')).toBeFalsy();
                expect(target.classList.contains('four')).toBeTruthy();
                compInstance.stylesExp = {width: '777px'};
                compInstance.classesExp = {four: false};
                fixture.detectChanges();
                expect(target.style.getPropertyValue('width')).toEqual('777px');
                expect(target.style.getPropertyValue('height')).toEqual('600px');
                expect(target.classList.contains('one')).toBeTruthy();
                expect(target.classList.contains('two')).toBeFalsy();
                expect(target.classList.contains('three')).toBeFalsy();
                expect(target.classList.contains('four')).toBeFalsy();
              });
    });
    it('should properly handle and render interpolation for class attribute bindings', () => {
      @Component({template: '
'})
      class App {
        name = '';
        age = '';
      }
      TestBed.configureTestingModule({declarations: [App]});
      const fixture = TestBed.createComponent(App);
      const target = fixture.nativeElement.querySelector('div') !;
      expect(target.classList.contains('-fred-36-')).toBeFalsy();
      fixture.componentInstance.name = 'fred';
      fixture.componentInstance.age = '36';
      fixture.detectChanges();
      expect(target.classList.contains('-fred-36-')).toBeTruthy();
    });
  });
  describe('NgModule assertions', () => {
    it('should throw with descriptive error message when a module imports itself', () => {
      @Component({template: ''})
      class FixtureComponent {
      }
      @NgModule({imports: [SomeModule], declarations: [FixtureComponent]})
      class SomeModule {
      }
      expect(() => {
        TestBed.configureTestingModule({imports: [SomeModule]}).createComponent(FixtureComponent);
      }).toThrowError(`'SomeModule' module can't import itself`);
    });
    it('should throw with descriptive error message when a directive is passed to imports', () => {
      @Component({template: ''})
      class SomeComponent {
      }
      @NgModule({imports: [SomeComponent]})
      class ModuleWithImportedComponent {
      }
      expect(() => {
        TestBed.configureTestingModule({imports: [ModuleWithImportedComponent]})
            .createComponent(SomeComponent);
      })
          .toThrowError(
              // The ViewEngine error has a typo, whereas the Ivy one fixes it.
              /^Unexpected directive 'SomeComponent' imported by the module 'ModuleWithImportedComponent'\. Please add (a|an) @NgModule annotation\.$/);
    });
    it('should throw with descriptive error message when a pipe is passed to imports', () => {
      @Component({template: ''})
      class FixtureComponent {
      }
      @Pipe({name: 'somePipe'})
      class SomePipe {
      }
      @NgModule({imports: [SomePipe], declarations: [FixtureComponent]})
      class ModuleWithImportedPipe {
      }
      expect(() => {
        TestBed.configureTestingModule({imports: [ModuleWithImportedPipe]})
            .createComponent(FixtureComponent);
      })
          .toThrowError(
              // The ViewEngine error has a typo, whereas the Ivy one fixes it.
              /^Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'\. Please add (a|an) @NgModule annotation\.$/);
    });
    it('should throw with descriptive error message when a module is passed to declarations', () => {
      @Component({template: ''})
      class FixtureComponent {
      }
      @NgModule({})
      class SomeModule {
      }
      @NgModule({declarations: [SomeModule, FixtureComponent]})
      class ModuleWithDeclaredModule {
      }
      // The error is almost the same in Ivy and ViewEngine, however since Ivy's
      // message is more correct it doesn't make sense to align it ViewEngine.
      const expectedErrorMessage = ivyEnabled ?
          `Unexpected value 'SomeModule' declared by the module 'ModuleWithDeclaredModule'. Please add a @Pipe/@Directive/@Component annotation.` :
          `Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'. Please add a @Pipe/@Directive/@Component annotation.`;
      expect(() => {
        TestBed.configureTestingModule({imports: [ModuleWithDeclaredModule]})
            .createComponent(FixtureComponent);
      }).toThrowError(expectedErrorMessage);
    });
    it('should throw with descriptive error message when a declaration is missing annotation', () => {
      @Component({template: ''})
      class FixtureComponent {
      }
      class SomeClass {}
      @NgModule({declarations: [SomeClass, FixtureComponent]})
      class SomeModule {
      }
      expect(() => {
        TestBed.configureTestingModule({imports: [SomeModule]}).createComponent(FixtureComponent);
      })
          .toThrowError(
              `Unexpected value 'SomeClass' declared by the module 'SomeModule'. Please add a @Pipe/@Directive/@Component annotation.`);
    });
    it('should throw with descriptive error message when an imported module is missing annotation',
       () => {
         @Component({template: ''})
         class FixtureComponent {
         }
         class SomeModule {}
         @NgModule({imports: [SomeModule], declarations: [FixtureComponent]})
         class ModuleWithImportedModule {
         }
         expect(() => {
           TestBed.configureTestingModule({imports: [ModuleWithImportedModule]})
               .createComponent(FixtureComponent);
         })
             .toThrowError(
                 // The ViewEngine error has a typo, whereas the Ivy one fixes it.
                 /^Unexpected value 'SomeModule' imported by the module 'ModuleWithImportedModule'\. Please add (a|an) @NgModule annotation\.$/);
       });
  });
  it('should only call inherited host listeners once', () => {
    let clicks = 0;
    @Component({template: ''})
    class ButtonSuperClass {
      @HostListener('click')
      clicked() { clicks++; }
    }
    @Component({selector: 'button[custom-button]', template: ''})
    class ButtonSubClass extends ButtonSuperClass {
    }
    @Component({template: '
'})
    class SuperComp {
      @ViewChildren(SomeDir) dirs !: QueryList;
    }
    @Component({selector: 'button[custom-button]', template: '
'})
    class SubComp extends SuperComp {
    }
    @Component({template: '
'})
    class App {
      directiveValue = 'initial-value';
    }
    TestBed.configureTestingModule({declarations: [NoAssignAfterDestroy, App]});
    let fixture = TestBed.createComponent(App);
    fixture.destroy();
    expect(() => {
      fixture = TestBed.createComponent(App);
      fixture.detectChanges();
    }).not.toThrow();
  });
  it('should support host attribute and @ContentChild on the same component', () => {
    @Component(
        {selector: 'test-component', template: `foo`, host: {'[attr.aria-disabled]': 'true'}})
    class TestComponent {
      @ContentChild(TemplateRef, {static: true}) tpl !: TemplateRef;
    }
    TestBed.configureTestingModule({declarations: [TestComponent]});
    const fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();
    expect(fixture.componentInstance.tpl).not.toBeNull();
    expect(fixture.debugElement.nativeElement.getAttribute('aria-disabled')).toBe('true');
  });
  it('should inherit inputs from undecorated superclasses', () => {
    class ButtonSuperClass {
      @Input() isDisabled !: boolean;
    }
    @Component({selector: 'button[custom-button]', template: ''})
    class ButtonSubClass extends ButtonSuperClass {
    }
    @Component({template: '();
      emitClick() { this.clicked.emit(); }
    }
    @Component({selector: 'button[custom-button]', template: ''})
    class ButtonSubClass extends ButtonSuperClass {
    }
    @Component({template: 'Click me '})
    class App {
    }
    TestBed.configureTestingModule({declarations: [SubButton, App]});
    const fixture = TestBed.createComponent(App);
    const button = fixture.debugElement.query(By.directive(SubButton));
    fixture.detectChanges();
    expect(button.nativeElement.getAttribute('tabindex')).toBe('-1');
    button.componentInstance.tabindex = 2;
    fixture.detectChanges();
    expect(button.nativeElement.getAttribute('tabindex')).toBe('2');
  });
  it('should inherit host bindings from undecorated grand superclasses', () => {
    class SuperBaseButton {
      @HostBinding('attr.tabindex')
      tabindex = -1;
    }
    class BaseButton extends SuperBaseButton {}
    @Component({selector: '[sub-button]', template: 'Click me '})
    class App {
    }
    TestBed.configureTestingModule({declarations: [SubButton, App]});
    const fixture = TestBed.createComponent(App);
    const button = fixture.debugElement.query(By.directive(SubButton));
    fixture.detectChanges();
    expect(button.nativeElement.getAttribute('tabindex')).toBe('-1');
    button.componentInstance.tabindex = 2;
    fixture.detectChanges();
    expect(button.nativeElement.getAttribute('tabindex')).toBe('2');
  });
  it('should inherit host listeners from undecorated superclasses', () => {
    let clicks = 0;
    class BaseButton {
      @HostListener('click')
      handleClick() { clicks++; }
    }
    @Component({selector: '[sub-button]', template: 'Click me '})
    class App {
    }
    TestBed.configureTestingModule({declarations: [SubButton, App]});
    const fixture = TestBed.createComponent(App);
    const button = fixture.debugElement.query(By.directive(SubButton)).nativeElement;
    button.click();
    fixture.detectChanges();
    expect(clicks).toBe(1);
  });
  it('should inherit host listeners from superclasses once', () => {
    let clicks = 0;
    @Directive({selector: '[baseButton]'})
    class BaseButton {
      @HostListener('click')
      handleClick() { clicks++; }
    }
    @Component({selector: '[subButton]', template: 'Click me '})
    class App {
    }
    TestBed.configureTestingModule({declarations: [SubButton, BaseButton, App]});
    const fixture = TestBed.createComponent(App);
    const button = fixture.debugElement.query(By.directive(SubButton)).nativeElement;
    button.click();
    fixture.detectChanges();
    expect(clicks).toBe(1);
  });
  it('should inherit host listeners from grand superclasses once', () => {
    let clicks = 0;
    @Directive({selector: '[superBaseButton]'})
    class SuperBaseButton {
      @HostListener('click')
      handleClick() { clicks++; }
    }
    @Directive({selector: '[baseButton]'})
    class BaseButton extends SuperBaseButton {
    }
    @Component({selector: '[subButton]', template: 'Click me '})
    class App {
    }
    TestBed.configureTestingModule({declarations: [SubButton, SuperBaseButton, BaseButton, App]});
    const fixture = TestBed.createComponent(App);
    const button = fixture.debugElement.query(By.directive(SubButton)).nativeElement;
    button.click();
    fixture.detectChanges();
    expect(clicks).toBe(1);
  });
  it('should inherit host listeners from grand grand superclasses once', () => {
    let clicks = 0;
    @Directive({selector: '[superSuperBaseButton]'})
    class SuperSuperBaseButton {
      @HostListener('click')
      handleClick() { clicks++; }
    }
    @Directive({selector: '[superBaseButton]'})
    class SuperBaseButton extends SuperSuperBaseButton {
    }
    @Directive({selector: '[baseButton]'})
    class BaseButton extends SuperBaseButton {
    }
    @Component({selector: '[subButton]', template: 'Click me '})
    class App {
    }
    TestBed.configureTestingModule(
        {declarations: [SubButton, SuperBaseButton, SuperSuperBaseButton, BaseButton, App]});
    const fixture = TestBed.createComponent(App);
    const button = fixture.debugElement.query(By.directive(SubButton)).nativeElement;
    button.click();
    fixture.detectChanges();
    expect(clicks).toBe(1);
  });
  it('should not mask errors thrown during lifecycle hooks', () => {
    @Directive({
      selector: '[dir]',
      inputs: ['dir'],
    })
    class Dir {
      get dir(): any { return null; }
      set dir(value: any) { throw new Error('this error is expected'); }
    }
    @Component({
      template: '
',
    })
    class Cmp {
      ngAfterViewInit(): void {
        // This lifecycle hook should never run, since attempting to bind to Dir's input will throw
        // an error. If the runtime continues to run lifecycle hooks after that error, then it will
        // execute this hook and throw this error, which will mask the real problem. This test
        // verifies this don't happen.
        throw new Error('this error is unexpected');
      }
    }
    TestBed.configureTestingModule({
      declarations: [Cmp, Dir],
    });
    const fixture = TestBed.createComponent(Cmp);
    expect(() => fixture.detectChanges()).toThrowError('this error is expected');
  });
});