Prior to this commit, calling change detection for destroyed views resulted in errors being thrown in some cases. This commit adds a check to make sure change detection is invoked for non-destroyed views only. PR Close #34241
		
			
				
	
	
		
			4073 lines
		
	
	
		
			91 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			4073 lines
		
	
	
		
			91 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | ||
|  * @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 {ChangeDetectorRef, Component, ComponentFactoryResolver, ContentChildren, Directive, Input, NgModule, OnChanges, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
 | ||
| import {SimpleChange} from '@angular/core/src/core';
 | ||
| import {TestBed} from '@angular/core/testing';
 | ||
| import {By} from '@angular/platform-browser';
 | ||
| import {onlyInIvy} from '@angular/private/testing';
 | ||
| 
 | ||
| describe('onChanges', () => {
 | ||
|   it('should correctly support updating one Input among many', () => {
 | ||
|     let log: string[] = [];
 | ||
| 
 | ||
|     @Component({selector: 'child-comp', template: 'child'})
 | ||
|     class ChildComp implements OnChanges {
 | ||
|       @Input() a: number = 0;
 | ||
|       @Input() b: number = 0;
 | ||
|       @Input() c: number = 0;
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) {
 | ||
|         for (let key in changes) {
 | ||
|           const simpleChange = changes[key];
 | ||
|           log.push(key + ': ' + simpleChange.previousValue + ' -> ' + simpleChange.currentValue);
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     @Component(
 | ||
|         {selector: 'app-comp', template: '<child-comp [a]="a" [b]="b" [c]="c"></child-comp>'})
 | ||
|     class AppComp {
 | ||
|       a = 0;
 | ||
|       b = 0;
 | ||
|       c = 0;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({declarations: [AppComp, ChildComp]});
 | ||
|     const fixture = TestBed.createComponent(AppComp);
 | ||
|     fixture.detectChanges();
 | ||
|     const appComp = fixture.componentInstance;
 | ||
|     expect(log).toEqual(['a: undefined -> 0', 'b: undefined -> 0', 'c: undefined -> 0']);
 | ||
|     log.length = 0;
 | ||
| 
 | ||
|     appComp.a = 1;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(log).toEqual(['a: 0 -> 1']);
 | ||
|     log.length = 0;
 | ||
| 
 | ||
|     appComp.b = 2;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(log).toEqual(['b: 0 -> 2']);
 | ||
|     log.length = 0;
 | ||
| 
 | ||
|     appComp.c = 3;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(log).toEqual(['c: 0 -> 3']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onChanges method after inputs are set in creation and update mode', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       val1 = 'a';
 | ||
| 
 | ||
|       @Input('publicVal2')
 | ||
|       val2 = 'b';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<comp [val1]="val1" [publicVal2]="val2"></comp>`})
 | ||
|     class App {
 | ||
|       val1 = 'a2';
 | ||
| 
 | ||
|       val2 = 'b2';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([{
 | ||
|       name: 'comp',
 | ||
|       changes: {
 | ||
|         val1: new SimpleChange(undefined, 'a2', true),
 | ||
|         val2: new SimpleChange(undefined, 'b2', true),
 | ||
|       }
 | ||
|     }]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val1 = 'a3';
 | ||
|     fixture.componentInstance.val2 = 'b3';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
| 
 | ||
|     expect(events).toEqual([{
 | ||
|       name: 'comp',
 | ||
|       changes: {
 | ||
|         val1: new SimpleChange('a2', 'a3', false),
 | ||
|         val2: new SimpleChange('b2', 'b3', false),
 | ||
|       }
 | ||
|     }]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call parent onChanges before child onChanges', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [val]="val"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'child', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<parent [val]="val"></parent>`})
 | ||
|     class App {
 | ||
|       val = 'foo';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Child, Parent],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'parent',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange(undefined, 'foo', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange(undefined, 'foo', true),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'bar';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'parent',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('foo', 'bar', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('foo', 'bar', false),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call all parent onChanges across view before calling children onChanges', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]="name" [val]="val"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'child ' + this.name, changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <parent name="1" [val]="val"></parent>
 | ||
|         <parent name="2" [val]="val"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'foo';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Child, Parent],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'parent 1',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '1', true),
 | ||
|           val: new SimpleChange(undefined, 'foo', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 2',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '2', true),
 | ||
|           val: new SimpleChange(undefined, 'foo', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child 1',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '1', true),
 | ||
|           val: new SimpleChange(undefined, 'foo', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child 2',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '2', true),
 | ||
|           val: new SimpleChange(undefined, 'foo', true),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'bar';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'parent 1',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('foo', 'bar', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 2',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('foo', 'bar', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child 1',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('foo', 'bar', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child 2',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('foo', 'bar', false),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onChanges every time a new view is created with ngIf', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>{{val}}</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<comp *ngIf="show" [val]="val"></comp>`})
 | ||
|     class App {
 | ||
|       show = true;
 | ||
| 
 | ||
|       val = 'a';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([{
 | ||
|       name: 'comp',
 | ||
|       changes: {
 | ||
|         val: new SimpleChange(undefined, 'a', true),
 | ||
|       }
 | ||
|     }]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.val = 'b';
 | ||
|     fixture.componentInstance.show = true;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([{
 | ||
|       name: 'comp',
 | ||
|       changes: {
 | ||
|         val: new SimpleChange(undefined, 'b', true),
 | ||
|       }
 | ||
|     }]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onChanges in hosts before their content children', () => {
 | ||
|     const events: any[] = [];
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: `<p>{{val}}</p>`,
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'projected', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<div><ng-content></ng-content></div>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<comp [val]="val"><projected [val]="val"></projected></comp>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'projected',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'projected',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onChanges in host and its content children before next host', () => {
 | ||
|     const events: any[] = [];
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: `<p>{{val}}</p>`,
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) {
 | ||
|         events.push({name: 'projected ' + this.name, changes});
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<div><ng-content></ng-content></div>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1" [val]="val">
 | ||
|           <projected name="1" [val]="val"></projected>
 | ||
|         </comp>
 | ||
|         <comp name="2" [val]="val">
 | ||
|           <projected name="2" [val]="val"></projected>
 | ||
|         </comp>
 | ||
|       `,
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp 1',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '1', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'projected 1',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '1', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 2',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '2', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'projected 2',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '2', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp 1',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'projected 1',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 2',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'projected 2',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives after component by default', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input()
 | ||
|       dir = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>{{val}}</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<comp [dir]="val" [val]="val"></comp>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'dir',
 | ||
|         changes: {
 | ||
|           dir: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'dir',
 | ||
|         changes: {
 | ||
|           dir: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives before component if component injects directives', () => {
 | ||
| 
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input()
 | ||
|       dir = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>{{val}}</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       constructor(public dir: Dir) {}
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<comp [dir]="val" [val]="val"></comp>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'dir',
 | ||
|         changes: {
 | ||
|           dir: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'dir',
 | ||
|         changes: {
 | ||
|           dir: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
| 
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on multiple directives in injection order', () => {
 | ||
| 
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input()
 | ||
|       dir = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[injectionDir]',
 | ||
|     })
 | ||
|     class InjectionDir {
 | ||
|       @Input()
 | ||
|       injectionDir = '';
 | ||
| 
 | ||
|       constructor(public dir: Dir) {}
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'injectionDir', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<div [injectionDir]="val" [dir]="val"></div>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, InjectionDir, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'dir',
 | ||
|         changes: {
 | ||
|           dir: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'injectionDir',
 | ||
|         changes: {
 | ||
|           injectionDir: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
| 
 | ||
|   it('should be called on directives on an element', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input()
 | ||
|       dir = '';
 | ||
| 
 | ||
|       @Input('dir-val')
 | ||
|       val = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<div [dir]="val1" [dir-val]="val2"></div>`})
 | ||
|     class App {
 | ||
|       val1 = 'a';
 | ||
|       val2 = 'b';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([{
 | ||
|       name: 'dir',
 | ||
|       changes: {
 | ||
|         dir: new SimpleChange(undefined, 'a', true),
 | ||
|         val: new SimpleChange(undefined, 'b', true),
 | ||
|       }
 | ||
|     }]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val1 = 'a1';
 | ||
|     fixture.componentInstance.val2 = 'b1';
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([{
 | ||
|       name: 'dir',
 | ||
|       changes: {
 | ||
|         dir: new SimpleChange('a', 'a1', false),
 | ||
|         val: new SimpleChange('b', 'b1', false),
 | ||
|       }
 | ||
|     }]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onChanges properly in for loop', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>{{val}}</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <comp name="0" [val]="val"></comp>
 | ||
|       <comp *ngFor="let number of numbers" [name]="number" [val]="val"></comp>
 | ||
|       <comp name="1" [val]="val"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
| 
 | ||
|       numbers = ['2', '3', '4'];
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp 0',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '0', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 1',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '1', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 2',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '2', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 3',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '3', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 4',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '4', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'comp 0',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 1',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 2',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 3',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'comp 4',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       }
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onChanges properly in for loop with children', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>{{val}}</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) {
 | ||
|         events.push({name: 'child of parent ' + this.name, changes});
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]="name" [val]="val"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       val = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <parent name="0" [val]="val"></parent>
 | ||
|         <parent *ngFor="let number of numbers" [name]="number" [val]="val"></parent>
 | ||
|         <parent name="1" [val]="val"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
|       numbers = ['2', '3', '4'];
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Child, Parent],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'parent 0',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '0', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 1',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '1', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 2',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '2', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 2',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '2', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 3',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '3', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 3',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '3', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 4',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '4', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 4',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '4', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 0',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '0', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 1',
 | ||
|         changes: {
 | ||
|           name: new SimpleChange(undefined, '1', true),
 | ||
|           val: new SimpleChange(undefined, 'a', true),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.val = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       {
 | ||
|         name: 'parent 0',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 1',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 2',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 2',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 3',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 3',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'parent 4',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 4',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 0',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|       {
 | ||
|         name: 'child of parent 1',
 | ||
|         changes: {
 | ||
|           val: new SimpleChange('a', 'b', false),
 | ||
|         }
 | ||
|       },
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should not call onChanges if props are set directly', () => {
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Component({template: `<p>{{value}}</p>`})
 | ||
|     class App {
 | ||
|       value = 'a';
 | ||
|       ngOnChanges(changes: SimpleChanges) { events.push(changes); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.value = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| it('should call all hooks in correct order when several directives on same node', () => {
 | ||
|   let log: string[] = [];
 | ||
| 
 | ||
|   class AllHooks {
 | ||
|     id: number = -1;
 | ||
| 
 | ||
|     /** @internal */
 | ||
|     private _log(hook: string, id: number) { log.push(hook + id); }
 | ||
| 
 | ||
|     ngOnChanges() { this._log('onChanges', this.id); }
 | ||
|     ngOnInit() { this._log('onInit', this.id); }
 | ||
|     ngDoCheck() { this._log('doCheck', this.id); }
 | ||
|     ngAfterContentInit() { this._log('afterContentInit', this.id); }
 | ||
|     ngAfterContentChecked() { this._log('afterContentChecked', this.id); }
 | ||
|     ngAfterViewInit() { this._log('afterViewInit', this.id); }
 | ||
|     ngAfterViewChecked() { this._log('afterViewChecked', this.id); }
 | ||
|   }
 | ||
| 
 | ||
|   @Directive({selector: 'div'})
 | ||
|   class DirA extends AllHooks {
 | ||
|     @Input('a') id: number = 0;
 | ||
|   }
 | ||
| 
 | ||
|   @Directive({selector: 'div'})
 | ||
|   class DirB extends AllHooks {
 | ||
|     @Input('b') id: number = 0;
 | ||
|   }
 | ||
| 
 | ||
|   @Directive({selector: 'div'})
 | ||
|   class DirC extends AllHooks {
 | ||
|     @Input('c') id: number = 0;
 | ||
|   }
 | ||
| 
 | ||
|   @Component({selector: 'app-comp', template: '<div [a]="1" [b]="2" [c]="3"></div>'})
 | ||
|   class AppComp {
 | ||
|   }
 | ||
| 
 | ||
|   TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
 | ||
|   const fixture = TestBed.createComponent(AppComp);
 | ||
|   fixture.detectChanges();
 | ||
| 
 | ||
|   expect(log).toEqual([
 | ||
|     'onChanges1',
 | ||
|     'onInit1',
 | ||
|     'doCheck1',
 | ||
|     'onChanges2',
 | ||
|     'onInit2',
 | ||
|     'doCheck2',
 | ||
|     'onChanges3',
 | ||
|     'onInit3',
 | ||
|     'doCheck3',
 | ||
|     'afterContentInit1',
 | ||
|     'afterContentChecked1',
 | ||
|     'afterContentInit2',
 | ||
|     'afterContentChecked2',
 | ||
|     'afterContentInit3',
 | ||
|     'afterContentChecked3',
 | ||
|     'afterViewInit1',
 | ||
|     'afterViewChecked1',
 | ||
|     'afterViewInit2',
 | ||
|     'afterViewChecked2',
 | ||
|     'afterViewInit3',
 | ||
|     'afterViewChecked3'
 | ||
|   ]);
 | ||
| });
 | ||
| 
 | ||
| it('should call hooks after setting directives inputs', () => {
 | ||
|   let log: string[] = [];
 | ||
| 
 | ||
|   @Directive({selector: 'div'})
 | ||
|   class DirA {
 | ||
|     @Input() a: number = 0;
 | ||
|     ngOnInit() { log.push('onInitA' + this.a); }
 | ||
|   }
 | ||
| 
 | ||
|   @Directive({selector: 'div'})
 | ||
|   class DirB {
 | ||
|     @Input() b: number = 0;
 | ||
|     ngOnInit() { log.push('onInitB' + this.b); }
 | ||
|     ngDoCheck() { log.push('doCheckB' + this.b); }
 | ||
|   }
 | ||
| 
 | ||
|   @Directive({selector: 'div'})
 | ||
|   class DirC {
 | ||
|     @Input() c: number = 0;
 | ||
|     ngOnInit() { log.push('onInitC' + this.c); }
 | ||
|     ngDoCheck() { log.push('doCheckC' + this.c); }
 | ||
|   }
 | ||
| 
 | ||
|   @Component({
 | ||
|     selector: 'app-comp',
 | ||
|     template: '<div [a]="id" [b]="id" [c]="id"></div><div [a]="id" [b]="id" [c]="id"></div>'
 | ||
|   })
 | ||
|   class AppComp {
 | ||
|     id = 0;
 | ||
|   }
 | ||
| 
 | ||
|   TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
 | ||
|   const fixture = TestBed.createComponent(AppComp);
 | ||
|   fixture.detectChanges();
 | ||
| 
 | ||
|   expect(log).toEqual([
 | ||
|     'onInitA0', 'onInitB0', 'doCheckB0', 'onInitC0', 'doCheckC0', 'onInitA0', 'onInitB0',
 | ||
|     'doCheckB0', 'onInitC0', 'doCheckC0'
 | ||
|   ]);
 | ||
| 
 | ||
|   log = [];
 | ||
|   fixture.componentInstance.id = 1;
 | ||
|   fixture.detectChanges();
 | ||
|   expect(log).toEqual(['doCheckB1', 'doCheckC1', 'doCheckB1', 'doCheckC1']);
 | ||
| });
 | ||
| 
 | ||
| describe('onInit', () => {
 | ||
|   it('should call onInit after inputs are the first time', () => {
 | ||
|     const input1Values: string[] = [];
 | ||
|     const input2Values: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'my-comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class MyComponent {
 | ||
|       @Input()
 | ||
|       input1 = '';
 | ||
| 
 | ||
|       @Input()
 | ||
|       input2 = '';
 | ||
| 
 | ||
|       ngOnInit() {
 | ||
|         input1Values.push(this.input1);
 | ||
|         input2Values.push(this.input2);
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <my-comp [input1]="value1" [input2]="value2"></my-comp>
 | ||
|       `,
 | ||
|     })
 | ||
|     class App {
 | ||
|       value1 = 'a';
 | ||
|       value2 = 'b';
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, MyComponent],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(input1Values).toEqual(['a']);
 | ||
|     expect(input2Values).toEqual(['b']);
 | ||
| 
 | ||
|     fixture.componentInstance.value1 = 'c';
 | ||
|     fixture.componentInstance.value2 = 'd';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     // Shouldn't be called again just because change detection ran.
 | ||
|     expect(input1Values).toEqual(['a']);
 | ||
|     expect(input2Values).toEqual(['b']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on root component', () => {
 | ||
|     let onInitCalled = 0;
 | ||
| 
 | ||
|     @Component({template: ``})
 | ||
|     class App {
 | ||
|       ngOnInit() { onInitCalled++; }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(onInitCalled).toBe(1);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call parent onInit before it calls child onInit', () => {
 | ||
|     const initCalls: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: `child-comp`,
 | ||
|       template: `<p>child</p>`,
 | ||
|     })
 | ||
|     class ChildComp {
 | ||
|       ngOnInit() { initCalls.push('child'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<child-comp></child-comp>`,
 | ||
|     })
 | ||
|     class ParentComp {
 | ||
|       ngOnInit() { initCalls.push('parent'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [ParentComp, ChildComp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(ParentComp);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initCalls).toEqual(['parent', 'child']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call all parent onInits across view before calling children onInits', () => {
 | ||
|     const initCalls: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: `child-comp`,
 | ||
|       template: `<p>child</p>`,
 | ||
|     })
 | ||
|     class ChildComp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initCalls.push(`child of parent ${this.name}`); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent-comp',
 | ||
|       template: `<child-comp [name]="name"></child-comp>`,
 | ||
|     })
 | ||
|     class ParentComp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initCalls.push(`parent ${this.name}`); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <parent-comp name="1"></parent-comp>
 | ||
|         <parent-comp name="2"></parent-comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, ParentComp, ChildComp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initCalls).toEqual(['parent 1', 'parent 2', 'child of parent 1', 'child of parent 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onInit every time a new view is created (if block)', () => {
 | ||
|     let onInitCalls = 0;
 | ||
| 
 | ||
|     @Component({selector: 'my-comp', template: '<p>test</p>'})
 | ||
|     class MyComp {
 | ||
|       ngOnInit() { onInitCalls++; }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show"><my-comp></my-comp></div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, MyComp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(onInitCalls).toBe(1);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(onInitCalls).toBe(1);
 | ||
| 
 | ||
|     fixture.componentInstance.show = true;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(onInitCalls).toBe(2);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onInit for children of dynamically created components', () => {
 | ||
|     @Component({selector: 'my-comp', template: '<p>test</p>'})
 | ||
|     class MyComp {
 | ||
|       onInitCalled = false;
 | ||
| 
 | ||
|       ngOnInit() { this.onInitCalled = true; }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'dynamic-comp',
 | ||
|       template: `
 | ||
|         <my-comp></my-comp>
 | ||
|       `,
 | ||
|     })
 | ||
|     class DynamicComp {
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div #container></div>
 | ||
|       `,
 | ||
|     })
 | ||
|     class App {
 | ||
|       @ViewChild('container', {read: ViewContainerRef})
 | ||
|       viewContainerRef !: ViewContainerRef;
 | ||
| 
 | ||
|       constructor(public compFactoryResolver: ComponentFactoryResolver) {}
 | ||
| 
 | ||
|       createDynamicView() {
 | ||
|         const dynamicCompFactory = this.compFactoryResolver.resolveComponentFactory(DynamicComp);
 | ||
|         this.viewContainerRef.createComponent(dynamicCompFactory);
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     // View Engine requires that DynamicComp be in entryComponents.
 | ||
|     @NgModule({
 | ||
|       declarations: [App, MyComp, DynamicComp],
 | ||
|       entryComponents: [DynamicComp, App],
 | ||
|     })
 | ||
|     class AppModule {
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({imports: [AppModule]});
 | ||
| 
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     fixture.componentInstance.createDynamicView();
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     const myComp = fixture.debugElement.query(By.directive(MyComp)).componentInstance;
 | ||
|     expect(myComp.onInitCalled).toBe(true);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onInit in hosts before their content children', () => {
 | ||
|     const initialized: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: '',
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       ngOnInit() { initialized.push('projected'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<ng-content></ng-content>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngOnInit() { initialized.push('comp'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp>
 | ||
|           <projected></projected>
 | ||
|         </comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngOnInit() { initialized.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initialized).toEqual(['app', 'comp', 'projected']);
 | ||
|   });
 | ||
| 
 | ||
| 
 | ||
|   it('should call onInit in host and its content children before next host', () => {
 | ||
|     const initialized: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: '',
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('projected ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<ng-content></ng-content>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1">
 | ||
|           <projected name="1"></projected>
 | ||
|         </comp>
 | ||
|         <comp name="2">
 | ||
|           <projected name="2"></projected>
 | ||
|         </comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngOnInit() { initialized.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initialized).toEqual(['app', 'comp 1', 'projected 1', 'comp 2', 'projected 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives after component by default', () => {
 | ||
|     const initialized: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir-name')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p></p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1" dir dir-name="1"></comp>
 | ||
|         <comp name="2" dir dir-name="2"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngOnInit() { initialized.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initialized).toEqual(['app', 'comp 1', 'dir 1', 'comp 2', 'dir 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on multiple directives in injection order', () => {
 | ||
| 
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input()
 | ||
|       dir = '';
 | ||
| 
 | ||
|       ngOnInit() { events.push('dir'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[injectionDir]',
 | ||
|     })
 | ||
|     class InjectionDir {
 | ||
|       @Input()
 | ||
|       injectionDir = '';
 | ||
| 
 | ||
|       constructor(public dir: Dir) {}
 | ||
| 
 | ||
|       ngOnInit() { events.push('injectionDir'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<div [injectionDir]="val" [dir]="val"></div>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
| 
 | ||
|       ngOnInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, InjectionDir, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['app', 'dir', 'injectionDir']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives before component if component injects directives', () => {
 | ||
|     const initialized: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir-name')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p></p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       constructor(public dir: Dir) {}
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1" dir dir-name="1"></comp>
 | ||
|         <comp name="2" dir dir-name="2"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngOnInit() { initialized.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initialized).toEqual(['app', 'dir 1', 'comp 1', 'dir 2', 'comp 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives on an element', () => {
 | ||
|     const initialized: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir-name')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <p name="1" dir dir-name="1"></p>
 | ||
|         <p name="2" dir dir-name="2"></p>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngOnInit() { initialized.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initialized).toEqual(['app', 'dir 1', 'dir 2']);
 | ||
|   });
 | ||
| 
 | ||
| 
 | ||
|   it('should call onInit properly in for loop', () => {
 | ||
|     const initialized: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p></p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="0"></comp>
 | ||
|         <comp *ngFor="let number of numbers" [name]="number"></comp>
 | ||
|         <comp name="1"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       numbers = [2, 3, 4, 5, 6];
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initialized).toEqual([
 | ||
|       'comp 0', 'comp 1', 'comp 2', 'comp 3', 'comp 4', 'comp 5', 'comp 6'
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call onInit properly in for loop with children', () => {
 | ||
|     const initialized: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p></p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('child of parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({selector: 'parent', template: '<child [name]="name"></child>'})
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnInit() { initialized.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <parent name="0"></parent>
 | ||
|         <parent *ngFor="let number of numbers" [name]="number"></parent>
 | ||
|         <parent name="1"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       numbers = [2, 3, 4, 5, 6];
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Child, Parent],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(initialized).toEqual([
 | ||
|       // First the two root level components
 | ||
|       'parent 0',
 | ||
|       'parent 1',
 | ||
| 
 | ||
|       // Then our 5 embedded views
 | ||
|       'parent 2',
 | ||
|       'child of parent 2',
 | ||
|       'parent 3',
 | ||
|       'child of parent 3',
 | ||
|       'parent 4',
 | ||
|       'child of parent 4',
 | ||
|       'parent 5',
 | ||
|       'child of parent 5',
 | ||
|       'parent 6',
 | ||
|       'child of parent 6',
 | ||
| 
 | ||
|       // Then the children of the root level components
 | ||
|       'child of parent 0',
 | ||
|       'child of parent 1',
 | ||
|     ]);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| describe('doCheck', () => {
 | ||
|   it('should call doCheck on every refresh', () => {
 | ||
|     let doCheckCalled = 0;
 | ||
| 
 | ||
|     @Component({template: ``})
 | ||
|     class App {
 | ||
|       ngDoCheck() { doCheckCalled++; }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(doCheckCalled).toBe(1);
 | ||
| 
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(doCheckCalled).toBe(2);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call parent doCheck before child doCheck', () => {
 | ||
|     const doChecks: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       ngDoCheck() { doChecks.push('parent'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: ``,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       ngDoCheck() { doChecks.push('child'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<parent></parent>`})
 | ||
|     class App {
 | ||
|       ngDoCheck() { doChecks.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(doChecks).toEqual(['app', 'parent', 'child']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call ngOnInit before ngDoCheck if creation mode', () => {
 | ||
|     const events: string[] = [];
 | ||
|     @Component({template: ``})
 | ||
|     class App {
 | ||
|       ngOnInit() { events.push('onInit'); }
 | ||
| 
 | ||
|       ngDoCheck() { events.push('doCheck'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['onInit', 'doCheck']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives after component by default', () => {
 | ||
|     const doChecks: string[] = [];
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngDoCheck() { doChecks.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngDoCheck() { doChecks.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <comp name="1" dir="1"></comp>
 | ||
|       <comp name="2" dir="2"></comp>
 | ||
|     `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngDoCheck() { doChecks.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(doChecks).toEqual(['app', 'comp 1', 'dir 1', 'comp 2', 'dir 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives before component if component injects directives', () => {
 | ||
|     const doChecks: string[] = [];
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngDoCheck() { doChecks.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       constructor(public dir: Dir) {}
 | ||
| 
 | ||
|       ngDoCheck() { doChecks.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <comp name="1" dir="1"></comp>
 | ||
|       <comp name="2" dir="2"></comp>
 | ||
|     `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngDoCheck() { doChecks.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(doChecks).toEqual(['app', 'dir 1', 'comp 1', 'dir 2', 'comp 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on multiple directives in injection order', () => {
 | ||
| 
 | ||
|     const events: any[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input()
 | ||
|       dir = '';
 | ||
| 
 | ||
|       ngDoCheck() { events.push('dir'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[injectionDir]',
 | ||
|     })
 | ||
|     class InjectionDir {
 | ||
|       @Input()
 | ||
|       injectionDir = '';
 | ||
| 
 | ||
|       constructor(public dir: Dir) {}
 | ||
| 
 | ||
|       ngDoCheck() { events.push('injectionDir'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<div [injectionDir]="val" [dir]="val"></div>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       val = 'a';
 | ||
| 
 | ||
|       ngDoCheck() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, InjectionDir, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['app', 'dir', 'injectionDir']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives on an element', () => {
 | ||
|     const doChecks: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngDoCheck() { doChecks.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <p dir="1"></p>
 | ||
|         <p dir="2"></p>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngDoCheck() { doChecks.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(doChecks).toEqual(['app', 'dir 1', 'dir 2']);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| describe('afterContentinit', () => {
 | ||
|   it('should be called only in creation mode', () => {
 | ||
|     let afterContentInitCalls = 0;
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngAfterContentInit() { afterContentInitCalls++; }
 | ||
|     }
 | ||
|     @Component({template: `<comp></comp>`})
 | ||
|     class App {
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     // two updates
 | ||
|     fixture.detectChanges();
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(afterContentInitCalls).toBe(1);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on root component in creation mode', () => {
 | ||
|     let afterContentInitCalls = 0;
 | ||
| 
 | ||
|     @Component({template: `<p>test</p>`})
 | ||
|     class App {
 | ||
|       ngAfterContentInit() { afterContentInitCalls++; }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     // two updates
 | ||
|     fixture.detectChanges();
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(afterContentInitCalls).toBe(1);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on every create ngIf', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngAfterContentInit() { events.push('comp afterContentInit'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<comp *ngIf="show"></comp>`})
 | ||
|     class App {
 | ||
|       show = true;
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('app afterContentInit'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['app afterContentInit', 'comp afterContentInit']);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['app afterContentInit', 'comp afterContentInit']);
 | ||
| 
 | ||
|     fixture.componentInstance.show = true;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
| 
 | ||
|     expect(events).toEqual(
 | ||
|         ['app afterContentInit', 'comp afterContentInit', 'comp afterContentInit']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in parents before children', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]="name"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('child of parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <parent name="1"></parent>
 | ||
|       <parent name="2"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterContentInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(
 | ||
|         ['app', 'parent 1', 'parent 2', 'child of parent 1', 'child of parent 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in projected components before their hosts', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected-child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class ProjectedChild {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('projected child ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<div><ng-content></ng-content></div>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: `<projected-child [name]=name></projected-child>`,
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('projected ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1">
 | ||
|           <projected name="1"></projected>
 | ||
|           <projected name="2"></projected>
 | ||
|         </comp>
 | ||
|         <comp name="2">
 | ||
|           <projected name="3"></projected>
 | ||
|           <projected name="4"></projected>
 | ||
|         </comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterContentInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected, ProjectedChild],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       // root
 | ||
|       'app',
 | ||
|       // projections of comp 1
 | ||
|       'projected 1',
 | ||
|       'projected 2',
 | ||
|       // comp 1
 | ||
|       'comp 1',
 | ||
|       // projections of comp 2
 | ||
|       'projected 3',
 | ||
|       'projected 4',
 | ||
|       // comp 2
 | ||
|       'comp 2',
 | ||
|       // children of projections
 | ||
|       'projected child 1',
 | ||
|       'projected child 2',
 | ||
|       'projected child 3',
 | ||
|       'projected child 4',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in correct order in a for loop', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="4"></comp>
 | ||
|         <comp *ngFor="let number of numbers" [name]="number"></comp>
 | ||
|         <comp name="5"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       numbers = [0, 1, 2, 3];
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['app', 'comp 0', 'comp 1', 'comp 2', 'comp 3', 'comp 4', 'comp 5']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in correct order in a for loop with children', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]=name></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('child of parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <parent name="4"></parent>
 | ||
|         <parent *ngFor="let number of numbers" [name]="number"></parent>
 | ||
|         <parent name="5"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       numbers = [0, 1, 2, 3];
 | ||
|       ngAfterContentInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       // root
 | ||
|       'app',
 | ||
|       // 4 embedded views
 | ||
|       'parent 0',
 | ||
|       'child of parent 0',
 | ||
|       'parent 1',
 | ||
|       'child of parent 1',
 | ||
|       'parent 2',
 | ||
|       'child of parent 2',
 | ||
|       'parent 3',
 | ||
|       'child of parent 3',
 | ||
|       // root children
 | ||
|       'parent 4',
 | ||
|       'parent 5',
 | ||
|       // children of root children
 | ||
|       'child of parent 4',
 | ||
|       'child of parent 5',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives after component', () => {
 | ||
|     const events: string[] = [];
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterContentInit() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1" dir="1"></comp>
 | ||
|         <comp name="2" dir="2"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterContentInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'app',
 | ||
|       'comp 1',
 | ||
|       'dir 1',
 | ||
|       'comp 2',
 | ||
|       'dir 2',
 | ||
|     ]);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| describe('afterContentChecked', () => {
 | ||
|   it('should be called every change detection run after afterContentInit', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngAfterContentInit() { events.push('comp afterContentInit'); }
 | ||
| 
 | ||
|       ngAfterContentChecked() { events.push('comp afterContentChecked'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<comp></comp>`})
 | ||
|     class App {
 | ||
|       ngAfterContentInit() { events.push('app afterContentInit'); }
 | ||
| 
 | ||
|       ngAfterContentChecked() { events.push('app afterContentChecked'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'app afterContentInit',
 | ||
|       'app afterContentChecked',
 | ||
|       'comp afterContentInit',
 | ||
|       'comp afterContentChecked',
 | ||
|     ]);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| describe('afterViewInit', () => {
 | ||
|   it('should be called on creation and not in update mode', () => {
 | ||
|     let afterViewInitCalls = 0;
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngAfterViewInit() { afterViewInitCalls++; }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<comp></comp>`})
 | ||
|     class App {
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     // two updates
 | ||
|     fixture.detectChanges();
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(afterViewInitCalls).toBe(1);
 | ||
| 
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on root component in creation mode', () => {
 | ||
|     let afterViewInitCalls = 0;
 | ||
| 
 | ||
|     @Component({template: `<p>test</p>`})
 | ||
|     class App {
 | ||
|       ngAfterViewInit() { afterViewInitCalls++; }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     // two updates
 | ||
|     fixture.detectChanges();
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(afterViewInitCalls).toBe(1);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called every time a view is initialized with ngIf', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngAfterViewInit() { events.push('comp'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<comp *ngIf="show"></comp>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['comp', 'app']);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['comp', 'app']);
 | ||
| 
 | ||
|     fixture.componentInstance.show = true;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['comp', 'app', 'comp']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in children before parents', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]=name></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('child of parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <parent name="1"></parent>
 | ||
|         <parent name="2"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child of parent 1',
 | ||
|       'child of parent 2',
 | ||
|       'parent 1',
 | ||
|       'parent 2',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in projected components before their hosts', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('projected ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<ng-content></ng-content>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1"><projected name="1"></projected></comp>
 | ||
|         <comp name="2"><projected name="2"></projected></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'projected 1',
 | ||
|       'comp 1',
 | ||
|       'projected 2',
 | ||
|       'comp 2',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call afterViewInit in content children and host before next host', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected-child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class ProjectedChild {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('child of projected ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: `<projected-child [name]="name"></projected-child>`,
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('projected ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<div><ng-content></ng-content></div>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1"><projected name="1"></projected></comp>
 | ||
|         <comp name="2"><projected name="2"></projected></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected, ProjectedChild],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child of projected 1',
 | ||
|       'child of projected 2',
 | ||
|       'projected 1',
 | ||
|       'comp 1',
 | ||
|       'projected 2',
 | ||
|       'comp 2',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in correct order with ngFor', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="4"></comp>
 | ||
|         <comp *ngFor="let number of numbers" [name]="number"></comp>
 | ||
|         <comp name="5"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       numbers = [0, 1, 2, 3];
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 0',
 | ||
|       'comp 1',
 | ||
|       'comp 2',
 | ||
|       'comp 3',
 | ||
|       'comp 4',
 | ||
|       'comp 5',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in correct order with for loops with children', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('child of parent ' + this.name); }
 | ||
|     }
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]="name"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <parent name="4"></parent>
 | ||
|         <parent *ngFor="let number of numbers" [name]="number"></parent>
 | ||
|         <parent name="5"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       numbers = [0, 1, 2, 3];
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child of parent 0',
 | ||
|       'parent 0',
 | ||
|       'child of parent 1',
 | ||
|       'parent 1',
 | ||
|       'child of parent 2',
 | ||
|       'parent 2',
 | ||
|       'child of parent 3',
 | ||
|       'parent 3',
 | ||
|       'child of parent 4',
 | ||
|       'child of parent 5',
 | ||
|       'parent 4',
 | ||
|       'parent 5',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives after component', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <comp name="1" dir="1"></comp>
 | ||
|         <comp name="2" dir="2"></comp>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 1',
 | ||
|       'dir 1',
 | ||
|       'comp 2',
 | ||
|       'dir 2',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives on an element', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewInit() { events.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div dir="1"></div>
 | ||
|         <div dir="2"></div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterViewInit() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'dir 1',
 | ||
|       'dir 2',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| describe('afterViewChecked', () => {
 | ||
|   it('should call ngAfterViewChecked every update', () => {
 | ||
|     let afterViewCheckedCalls = 0;
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngAfterViewChecked() { afterViewCheckedCalls++; }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<comp></comp>`})
 | ||
|     class App {
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
| 
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(1);
 | ||
| 
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(2);
 | ||
| 
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(3);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on root component', () => {
 | ||
|     let afterViewCheckedCalls = 0;
 | ||
| 
 | ||
|     @Component({template: `<p>test</p>`})
 | ||
|     class App {
 | ||
|       ngAfterViewChecked() { afterViewCheckedCalls++; }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
| 
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(1);
 | ||
| 
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(2);
 | ||
| 
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(3);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call ngAfterViewChecked with bindings', () => {
 | ||
|     let afterViewCheckedCalls = 0;
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>{{value}}</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       value = '';
 | ||
|       ngAfterViewChecked() { afterViewCheckedCalls++; }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<comp [value]="value"></comp>`})
 | ||
|     class App {
 | ||
|       value = 1;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(1);
 | ||
| 
 | ||
|     fixture.componentInstance.value = 1337;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(afterViewCheckedCalls).toBe(2);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in correct order with for loops with children', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewChecked() { events.push('child of parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]="name"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewChecked() { events.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <parent name="4"></parent>
 | ||
|       <parent *ngFor="let number of numbers" [name]="number"></parent>
 | ||
|       <parent name="5"></parent>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       numbers = [0, 1, 2, 3];
 | ||
| 
 | ||
|       ngAfterViewChecked() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child of parent 0',
 | ||
|       'parent 0',
 | ||
|       'child of parent 1',
 | ||
|       'parent 1',
 | ||
|       'child of parent 2',
 | ||
|       'parent 2',
 | ||
|       'child of parent 3',
 | ||
|       'parent 3',
 | ||
|       'child of parent 4',
 | ||
|       'child of parent 5',
 | ||
|       'parent 4',
 | ||
|       'parent 5',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives after component', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewChecked() { events.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewChecked() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <comp name="1" dir="1"></comp>
 | ||
|       <comp name="2" dir="2"></comp>
 | ||
|     `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterViewChecked() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 1',
 | ||
|       'dir 1',
 | ||
|       'comp 2',
 | ||
|       'dir 2',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on directives on an element', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       @Input('dir')
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngAfterViewChecked() { events.push('dir ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <div dir="1"></div>
 | ||
|       <div dir="2"></div>
 | ||
|     `
 | ||
|     })
 | ||
|     class App {
 | ||
|       ngAfterViewChecked() { events.push('app'); }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'dir 1',
 | ||
|       'dir 2',
 | ||
|       'app',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
| });
 | ||
| 
 | ||
| describe('onDestroy', () => {
 | ||
| 
 | ||
| 
 | ||
|   it('should call destroy when view is removed', () => {
 | ||
|     let destroyCalled = 0;
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngOnDestroy() { destroyCalled++; }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<comp *ngIf="show"></comp>`,
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(destroyCalled).toBe(0);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(destroyCalled).toBe(1);
 | ||
| 
 | ||
|     fixture.componentInstance.show = true;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(destroyCalled).toBe(1);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(destroyCalled).toBe(2);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call destroy when multiple views are removed', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <comp name="1"></comp>
 | ||
|           <comp name="2"></comp>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['comp 1', 'comp 2']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in child components before parent components', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('child of parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]="name"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
|       ngOnDestroy() { events.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <parent name="1"></parent>
 | ||
|           <parent name="2"></parent>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child of parent 1',
 | ||
|       'child of parent 2',
 | ||
|       'parent 1',
 | ||
|       'parent 2',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called bottom up with children nested 2 levels deep', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'child',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Child {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('child ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'parent',
 | ||
|       template: `<child [name]="name"></child>`,
 | ||
|     })
 | ||
|     class Parent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
|       ngOnDestroy() { events.push('parent ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'grandparent',
 | ||
|       template: `<parent [name]="name"></parent>`,
 | ||
|     })
 | ||
|     class Grandparent {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('grandparent ' + this.name); }
 | ||
|     }
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <grandparent name="1"></grandparent>
 | ||
|           <grandparent name="2"></grandparent>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Grandparent, Parent, Child],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child 1',
 | ||
|       'parent 1',
 | ||
|       'child 2',
 | ||
|       'parent 2',
 | ||
|       'grandparent 1',
 | ||
|       'grandparent 2',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called in projected components before their hosts', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'projected',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Projected {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('projected ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<div><ng-content></ng-content></div>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <comp name="1">
 | ||
|             <projected name="1"></projected>
 | ||
|           </comp>
 | ||
|           <comp name="2">
 | ||
|             <projected name="2"></projected>
 | ||
|           </comp>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp, Projected],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'projected 1',
 | ||
|       'comp 1',
 | ||
|       'projected 2',
 | ||
|       'comp 2',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
| 
 | ||
|   it('should be called in consistent order if views are removed and re-added', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|       <div *ngIf="showAll">
 | ||
|         <comp name="1"></comp>
 | ||
|         <comp *ngIf="showMiddle" name="2"></comp>
 | ||
|         <comp name="3"></comp>
 | ||
|       </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       showAll = true;
 | ||
|       showMiddle = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.showMiddle = false;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual(['comp 2']);
 | ||
| 
 | ||
|     fixture.componentInstance.showAll = false;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 2',
 | ||
|       'comp 1',
 | ||
|       'comp 3',
 | ||
|     ]);
 | ||
| 
 | ||
|     fixture.componentInstance.showAll = true;
 | ||
|     fixture.componentInstance.showMiddle = true;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 2',
 | ||
|       'comp 1',
 | ||
|       'comp 3',
 | ||
|     ]);
 | ||
| 
 | ||
|     fixture.componentInstance.showAll = false;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 2',
 | ||
|       'comp 1',
 | ||
|       'comp 3',
 | ||
|       'comp 2',
 | ||
|       'comp 1',
 | ||
|       'comp 3',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should be called on every iteration of a destroyed for loop', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       @Input()
 | ||
|       name = '';
 | ||
| 
 | ||
|       ngOnDestroy() { events.push('comp ' + this.name); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <comp *ngFor="let number of numbers" [name]="number"></comp>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|       numbers = [0, 1, 2, 3];
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 0',
 | ||
|       'comp 1',
 | ||
|       'comp 2',
 | ||
|       'comp 3',
 | ||
|     ]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = true;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 0',
 | ||
|       'comp 1',
 | ||
|       'comp 2',
 | ||
|       'comp 3',
 | ||
|     ]);
 | ||
| 
 | ||
|     fixture.componentInstance.numbers.splice(1, 1);
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 0',
 | ||
|       'comp 1',
 | ||
|       'comp 2',
 | ||
|       'comp 3',
 | ||
|       'comp 1',
 | ||
|     ]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp 0',
 | ||
|       'comp 1',
 | ||
|       'comp 2',
 | ||
|       'comp 3',
 | ||
|       'comp 1',
 | ||
|       'comp 0',
 | ||
|       'comp 2',
 | ||
|       'comp 3',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call destroy properly if view also has listeners', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'comp',
 | ||
|       template: `<p>test</p>`,
 | ||
|     })
 | ||
|     class Comp {
 | ||
|       ngOnDestroy() { events.push('comp'); }
 | ||
|     }
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <button (click)="handleClick1()">test 1</button>
 | ||
|           <comp></comp>
 | ||
|           <button (click)="handleClick2()">test 2</button>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
| 
 | ||
|       clicksToButton1 = 0;
 | ||
| 
 | ||
|       clicksToButton2 = 0;
 | ||
| 
 | ||
|       handleClick1() { this.clicksToButton1++; }
 | ||
| 
 | ||
|       handleClick2() { this.clicksToButton2++; }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     const buttons = fixture.debugElement.queryAll(By.css('button'));
 | ||
|     buttons.forEach(button => button.nativeElement.click());
 | ||
| 
 | ||
|     expect(fixture.componentInstance.clicksToButton1).toBe(1);
 | ||
|     expect(fixture.componentInstance.clicksToButton2).toBe(1);
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     buttons.forEach(button => button.nativeElement.click());
 | ||
|     expect(fixture.componentInstance.clicksToButton1).toBe(1);
 | ||
|     expect(fixture.componentInstance.clicksToButton2).toBe(1);
 | ||
| 
 | ||
|     expect(events).toEqual(['comp']);
 | ||
|   });
 | ||
| 
 | ||
|   it('should not produce errors if change detection is triggered during ngOnDestroy', () => {
 | ||
|     @Component({selector: 'child', template: `<ng-content></ng-content>`})
 | ||
|     class Child {
 | ||
|     }
 | ||
| 
 | ||
|     @Component({selector: 'parent', template: `<ng-content></ng-content>`})
 | ||
|     class Parent {
 | ||
|       @ContentChildren(Child, {descendants: true}) child !: QueryList<Child>;
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       selector: 'app',
 | ||
|       template: `
 | ||
|         <ng-template #tpl>
 | ||
|           <parent>
 | ||
|             <child></child>
 | ||
|           </parent>
 | ||
|         </ng-template>
 | ||
|         <div #container dir></div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       @ViewChild('container', {read: ViewContainerRef, static: true})
 | ||
|       container !: ViewContainerRef;
 | ||
| 
 | ||
|       @ViewChild('tpl', {read: TemplateRef, static: true})
 | ||
|       tpl !: TemplateRef<any>;
 | ||
| 
 | ||
|       ngOnInit() { this.container.createEmbeddedView(this.tpl); }
 | ||
|     }
 | ||
| 
 | ||
|     @Directive({selector: '[dir]'})
 | ||
|     class Dir {
 | ||
|       constructor(public cdr: ChangeDetectorRef) {}
 | ||
| 
 | ||
|       ngOnDestroy() {
 | ||
|         // Important: calling detectChanges in destroy hook like that
 | ||
|         // doesn’t have practical purpose, but in real-world cases it might
 | ||
|         // happen, for example as a result of "focus()" call on a DOM element,
 | ||
|         // in case ZoneJS is active.
 | ||
|         this.cdr.detectChanges();
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Child, Dir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.autoDetectChanges();
 | ||
| 
 | ||
|     expect(() => fixture.destroy()).not.toThrow();
 | ||
|   });
 | ||
| 
 | ||
|   onlyInIvy(
 | ||
|       'View Engine has the opposite behavior, where it calls destroy on the directives first, then the components')
 | ||
|       .it('should be called on directives after component', () => {
 | ||
|         const events: string[] = [];
 | ||
| 
 | ||
|         @Directive({
 | ||
|           selector: '[dir]',
 | ||
|         })
 | ||
|         class Dir {
 | ||
|           @Input('dir')
 | ||
|           name = '';
 | ||
| 
 | ||
|           ngOnDestroy() { events.push('dir ' + this.name); }
 | ||
|         }
 | ||
| 
 | ||
|         @Component({
 | ||
|           selector: 'comp',
 | ||
|           template: `<p>test</p>`,
 | ||
|         })
 | ||
|         class Comp {
 | ||
|           @Input()
 | ||
|           name = '';
 | ||
| 
 | ||
|           ngOnDestroy() { events.push('comp ' + this.name); }
 | ||
|         }
 | ||
| 
 | ||
|         @Component({
 | ||
|           template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <comp name="1" dir="1"></comp>
 | ||
|           <comp name="2" dir="2"></comp>
 | ||
|         </div>
 | ||
|       `
 | ||
|         })
 | ||
|         class App {
 | ||
|           show = true;
 | ||
|         }
 | ||
| 
 | ||
|         TestBed.configureTestingModule({
 | ||
|           declarations: [App, Dir, Comp],
 | ||
|           imports: [CommonModule],
 | ||
|         });
 | ||
|         const fixture = TestBed.createComponent(App);
 | ||
|         fixture.detectChanges();
 | ||
| 
 | ||
|         expect(events).toEqual([]);
 | ||
| 
 | ||
|         fixture.componentInstance.show = false;
 | ||
|         fixture.detectChanges();
 | ||
| 
 | ||
|         expect(events).toEqual([
 | ||
|           'comp 1',
 | ||
|           'dir 1',
 | ||
|           'comp 2',
 | ||
|           'dir 2',
 | ||
|         ]);
 | ||
|       });
 | ||
| 
 | ||
|   it('should be called on directives on an element', () => {
 | ||
|     const events: string[] = [];
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[dir]',
 | ||
|     })
 | ||
|     class Dir {
 | ||
|       ngOnDestroy() { events.push('dir'); }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({template: `<p *ngIf="show" dir></p>`})
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Dir],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([]);
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual(['dir']);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| describe('hook order', () => {
 | ||
|   let events: string[] = [];
 | ||
| 
 | ||
|   beforeEach(() => events = []);
 | ||
| 
 | ||
|   @Component({
 | ||
|     selector: 'comp',
 | ||
|     template: `{{value}}<div><ng-content></ng-content></div>`,
 | ||
|   })
 | ||
|   class Comp {
 | ||
|     @Input()
 | ||
|     value = '';
 | ||
| 
 | ||
|     @Input()
 | ||
|     name = '';
 | ||
| 
 | ||
|     ngOnInit() { events.push(`${this.name} onInit`); }
 | ||
| 
 | ||
|     ngDoCheck() { events.push(`${this.name} doCheck`); }
 | ||
| 
 | ||
|     ngOnChanges() { events.push(`${this.name} onChanges`); }
 | ||
| 
 | ||
|     ngAfterContentInit() { events.push(`${this.name} afterContentInit`); }
 | ||
| 
 | ||
|     ngAfterContentChecked() { events.push(`${this.name} afterContentChecked`); }
 | ||
| 
 | ||
|     ngAfterViewInit() { events.push(`${this.name} afterViewInit`); }
 | ||
| 
 | ||
|     ngAfterViewChecked() { events.push(`${this.name} afterViewChecked`); }
 | ||
| 
 | ||
|     ngOnDestroy() { events.push(`${this.name} onDestroy`); }
 | ||
|   }
 | ||
| 
 | ||
|   @Component({
 | ||
|     selector: 'parent',
 | ||
|     template:
 | ||
|         `<comp [name]="'child of ' + this.name" [value]="value"><ng-content></ng-content></comp>`,
 | ||
|   })
 | ||
|   class Parent extends Comp {
 | ||
|   }
 | ||
| 
 | ||
|   it('should call all hooks in correct order', () => {
 | ||
|     @Component({template: `<comp *ngIf="show" name="comp" [value]="value"></comp>`})
 | ||
|     class App {
 | ||
|       value = 'a';
 | ||
| 
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'comp onChanges',
 | ||
|       'comp onInit',
 | ||
|       'comp doCheck',
 | ||
|       'comp afterContentInit',
 | ||
|       'comp afterContentChecked',
 | ||
|       'comp afterViewInit',
 | ||
|       'comp afterViewChecked',
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp doCheck',
 | ||
|       'comp afterContentChecked',
 | ||
|       'comp afterViewChecked',
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.value = 'b';
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp onChanges',
 | ||
|       'comp doCheck',
 | ||
|       'comp afterContentChecked',
 | ||
|       'comp afterViewChecked',
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
|     expect(events).toEqual([
 | ||
|       'comp onDestroy',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   it('should call all hooks in correct order with children', () => {
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <parent name="parent1" [value]="value"></parent>
 | ||
|           <parent name="parent2" [value]="value"></parent>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       value = 'a';
 | ||
| 
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'parent1 onChanges',
 | ||
|       'parent1 onInit',
 | ||
|       'parent1 doCheck',
 | ||
|       'parent2 onChanges',
 | ||
|       'parent2 onInit',
 | ||
|       'parent2 doCheck',
 | ||
|       'parent1 afterContentInit',
 | ||
|       'parent1 afterContentChecked',
 | ||
|       'parent2 afterContentInit',
 | ||
|       'parent2 afterContentChecked',
 | ||
|       'child of parent1 onChanges',
 | ||
|       'child of parent1 onInit',
 | ||
|       'child of parent1 doCheck',
 | ||
|       'child of parent1 afterContentInit',
 | ||
|       'child of parent1 afterContentChecked',
 | ||
|       'child of parent1 afterViewInit',
 | ||
|       'child of parent1 afterViewChecked',
 | ||
|       'child of parent2 onChanges',
 | ||
|       'child of parent2 onInit',
 | ||
|       'child of parent2 doCheck',
 | ||
|       'child of parent2 afterContentInit',
 | ||
|       'child of parent2 afterContentChecked',
 | ||
|       'child of parent2 afterViewInit',
 | ||
|       'child of parent2 afterViewChecked',
 | ||
|       'parent1 afterViewInit',
 | ||
|       'parent1 afterViewChecked',
 | ||
|       'parent2 afterViewInit',
 | ||
|       'parent2 afterViewChecked',
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.value = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'parent1 onChanges',
 | ||
|       'parent1 doCheck',
 | ||
|       'parent2 onChanges',
 | ||
|       'parent2 doCheck',
 | ||
|       'parent1 afterContentChecked',
 | ||
|       'parent2 afterContentChecked',
 | ||
|       'child of parent1 onChanges',
 | ||
|       'child of parent1 doCheck',
 | ||
|       'child of parent1 afterContentChecked',
 | ||
|       'child of parent1 afterViewChecked',
 | ||
|       'child of parent2 onChanges',
 | ||
|       'child of parent2 doCheck',
 | ||
|       'child of parent2 afterContentChecked',
 | ||
|       'child of parent2 afterViewChecked',
 | ||
|       'parent1 afterViewChecked',
 | ||
|       'parent2 afterViewChecked',
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child of parent1 onDestroy',
 | ||
|       'child of parent2 onDestroy',
 | ||
|       'parent1 onDestroy',
 | ||
|       'parent2 onDestroy',
 | ||
|     ]);
 | ||
|   });
 | ||
| 
 | ||
|   // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-ng
 | ||
|   it('should call all hooks in correct order with view and content', () => {
 | ||
|     @Component({
 | ||
|       template: `
 | ||
|         <div *ngIf="show">
 | ||
|           <parent name="parent1" [value]="value">
 | ||
|             <comp name="projected1" [value]="value"></comp>
 | ||
|           </parent>
 | ||
|           <parent name="parent2" [value]="value">
 | ||
|             <comp name="projected2" [value]="value"></comp>
 | ||
|           </parent>
 | ||
|         </div>
 | ||
|       `
 | ||
|     })
 | ||
|     class App {
 | ||
|       value = 'a';
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, Parent, Comp],
 | ||
|       imports: [CommonModule],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'parent1 onChanges',
 | ||
|       'parent1 onInit',
 | ||
|       'parent1 doCheck',
 | ||
|       'projected1 onChanges',
 | ||
|       'projected1 onInit',
 | ||
|       'projected1 doCheck',
 | ||
|       'parent2 onChanges',
 | ||
|       'parent2 onInit',
 | ||
|       'parent2 doCheck',
 | ||
|       'projected2 onChanges',
 | ||
|       'projected2 onInit',
 | ||
|       'projected2 doCheck',
 | ||
|       'projected1 afterContentInit',
 | ||
|       'projected1 afterContentChecked',
 | ||
|       'parent1 afterContentInit',
 | ||
|       'parent1 afterContentChecked',
 | ||
|       'projected2 afterContentInit',
 | ||
|       'projected2 afterContentChecked',
 | ||
|       'parent2 afterContentInit',
 | ||
|       'parent2 afterContentChecked',
 | ||
|       'child of parent1 onChanges',
 | ||
|       'child of parent1 onInit',
 | ||
|       'child of parent1 doCheck',
 | ||
|       'child of parent1 afterContentInit',
 | ||
|       'child of parent1 afterContentChecked',
 | ||
|       'child of parent1 afterViewInit',
 | ||
|       'child of parent1 afterViewChecked',
 | ||
|       'child of parent2 onChanges',
 | ||
|       'child of parent2 onInit',
 | ||
|       'child of parent2 doCheck',
 | ||
|       'child of parent2 afterContentInit',
 | ||
|       'child of parent2 afterContentChecked',
 | ||
|       'child of parent2 afterViewInit',
 | ||
|       'child of parent2 afterViewChecked',
 | ||
|       'projected1 afterViewInit',
 | ||
|       'projected1 afterViewChecked',
 | ||
|       'parent1 afterViewInit',
 | ||
|       'parent1 afterViewChecked',
 | ||
|       'projected2 afterViewInit',
 | ||
|       'projected2 afterViewChecked',
 | ||
|       'parent2 afterViewInit',
 | ||
|       'parent2 afterViewChecked',
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.value = 'b';
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'parent1 onChanges',
 | ||
|       'parent1 doCheck',
 | ||
|       'projected1 onChanges',
 | ||
|       'projected1 doCheck',
 | ||
|       'parent2 onChanges',
 | ||
|       'parent2 doCheck',
 | ||
|       'projected2 onChanges',
 | ||
|       'projected2 doCheck',
 | ||
|       'projected1 afterContentChecked',
 | ||
|       'parent1 afterContentChecked',
 | ||
|       'projected2 afterContentChecked',
 | ||
|       'parent2 afterContentChecked',
 | ||
|       'child of parent1 onChanges',
 | ||
|       'child of parent1 doCheck',
 | ||
|       'child of parent1 afterContentChecked',
 | ||
|       'child of parent1 afterViewChecked',
 | ||
|       'child of parent2 onChanges',
 | ||
|       'child of parent2 doCheck',
 | ||
|       'child of parent2 afterContentChecked',
 | ||
|       'child of parent2 afterViewChecked',
 | ||
|       'projected1 afterViewChecked',
 | ||
|       'parent1 afterViewChecked',
 | ||
|       'projected2 afterViewChecked',
 | ||
|       'parent2 afterViewChecked',
 | ||
|     ]);
 | ||
| 
 | ||
|     events.length = 0;
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(events).toEqual([
 | ||
|       'child of parent1 onDestroy',
 | ||
|       'child of parent2 onDestroy',
 | ||
|       'projected1 onDestroy',
 | ||
|       'parent1 onDestroy',
 | ||
|       'projected2 onDestroy',
 | ||
|       'parent2 onDestroy',
 | ||
|     ]);
 | ||
|   });
 | ||
| });
 | ||
| 
 | ||
| describe('non-regression', () => {
 | ||
|   it('should call lifecycle hooks for directives active on <ng-template>', () => {
 | ||
|     let destroyed = false;
 | ||
| 
 | ||
|     @Directive({
 | ||
|       selector: '[onDestroyDir]',
 | ||
|     })
 | ||
|     class OnDestroyDir {
 | ||
|       ngOnDestroy() { destroyed = true; }
 | ||
|     }
 | ||
| 
 | ||
|     @Component({
 | ||
|       template: `<ng-template [ngIf]="show">
 | ||
|         <ng-template onDestroyDir>content</ng-template>
 | ||
|       </ng-template>`
 | ||
|     })
 | ||
|     class App {
 | ||
|       show = true;
 | ||
|     }
 | ||
| 
 | ||
|     TestBed.configureTestingModule({
 | ||
|       declarations: [App, OnDestroyDir],
 | ||
|     });
 | ||
|     const fixture = TestBed.createComponent(App);
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(destroyed).toBeFalsy();
 | ||
| 
 | ||
|     fixture.componentInstance.show = false;
 | ||
|     fixture.detectChanges();
 | ||
| 
 | ||
|     expect(destroyed).toBeTruthy();
 | ||
|   });
 | ||
| });
 |