diff --git a/packages/core/test/acceptance/lifecycle_spec.ts b/packages/core/test/acceptance/lifecycle_spec.ts
index a99d80120a..ba58110df6 100644
--- a/packages/core/test/acceptance/lifecycle_spec.ts
+++ b/packages/core/test/acceptance/lifecycle_spec.ts
@@ -10,6 +10,7 @@ import {CommonModule} from '@angular/common';
import {Component, ComponentFactoryResolver, Directive, Input, NgModule, OnChanges, SimpleChanges, ViewChild, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
+import {onlyInIvy} from '@angular/private/testing';
describe('ngOnChanges', () => {
it('should correctly support updating one Input among many', () => {
@@ -1656,7 +1657,7 @@ describe('afterViewInit', () => {
});
});
-describe('ngAfterViewChecked', () => {
+describe('afterViewChecked', () => {
it('should call ngAfterViewChecked every update', () => {
let afterViewCheckedCalls = 0;
@@ -1887,3 +1888,566 @@ describe('ngAfterViewChecked', () => {
});
});
+
+describe('onDestroy', () => {
+
+
+ it('should call destroy when view is removed', () => {
+ let destroyCalled = 0;
+
+ @Component({
+ selector: 'comp',
+ template: `
test
`,
+ })
+ class Comp {
+ ngOnDestroy() { destroyCalled++; }
+ }
+
+ @Component({
+ template: ``,
+ })
+ 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: `test
`,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+
+
+ `
+ })
+ 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: `test
`,
+ })
+ class Child {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('child of parent ' + this.name); }
+ }
+
+ @Component({
+ selector: 'parent',
+ template: ``,
+ })
+ class Parent {
+ @Input()
+ name = '';
+ ngOnDestroy() { events.push('parent ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+ `
+ })
+ 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: `test
`,
+ })
+ class Child {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('child ' + this.name); }
+ }
+
+ @Component({
+ selector: 'parent',
+ template: ``,
+ })
+ class Parent {
+ @Input()
+ name = '';
+ ngOnDestroy() { events.push('parent ' + this.name); }
+ }
+
+ @Component({
+ selector: 'grandparent',
+ template: ``,
+ })
+ class Grandparent {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('grandparent ' + this.name); }
+ }
+ @Component({
+ template: `
+
+
+
+
+ `
+ })
+ 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: `test
`,
+ })
+ class Projected {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('projected ' + this.name); }
+ }
+
+ @Component({
+ selector: 'comp',
+ template: `
`,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+ `
+ })
+ 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: `test
`,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+
+
+
+ `
+ })
+ 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: `test
`,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ 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: `test
`,
+ })
+ class Comp {
+ ngOnDestroy() { events.push('comp'); }
+ }
+ @Component({
+ template: `
+
+
+
+
+
+ `
+ })
+ 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']);
+ });
+
+ 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: `test
`,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnDestroy() { events.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+
+
+ `
+ })
+ 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: ``})
+ 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']);
+ });
+});
diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts
index 0989f3db3a..858dc5a269 100644
--- a/packages/core/test/render3/lifecycle_spec.ts
+++ b/packages/core/test/render3/lifecycle_spec.ts
@@ -116,573 +116,6 @@ describe('lifecycles', () => {
});
});
- describe('onDestroy', () => {
- let events: string[];
-
- beforeEach(() => { events = []; });
-
- let Comp = createOnDestroyComponent('comp', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef();
- Δprojection(0);
- }
- }, 1);
- let Parent = createOnDestroyComponent('parent', getParentTemplate('comp'), 1, 1, [Comp]);
-
- function createOnDestroyComponent(
- name: string, template: ComponentTemplate, consts: number = 0, vars: number = 0,
- directives: any[] = []) {
- return class Component {
- val: string = '';
- ngOnDestroy() { events.push(`${name}${this.val}`); }
-
- static ngComponentDef = ΔdefineComponent({
- type: Component,
- selectors: [[name]],
- factory: () => new Component(),
- consts: consts,
- vars: vars,
- inputs: {val: 'val'},
- template: template,
- directives: directives
- });
- };
- }
-
- let Grandparent = createOnDestroyComponent('grandparent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'parent');
- }
- }, 1, 0, [Parent]);
-
- const ProjectedComp = createOnDestroyComponent('projected', (rf: RenderFlags, ctx: any) => {});
-
- class Directive {
- ngOnDestroy() { events.push('dir'); }
-
- static ngDirectiveDef = ΔdefineDirective(
- {type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive()});
- }
-
- const defs = [Comp, Parent, Grandparent, ProjectedComp, Directive];
-
- it('should call destroy when view is removed', () => {
- /**
- * % if (!skip) {
- *
- * % }
- */
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (!ctx.skip) {
- let rf1 = ΔembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
-
- fixture.component.skip = true;
- fixture.update();
- expect(events).toEqual(['comp']);
- });
-
- it('should call destroy when multiple views are removed', () => {
- /**
- * % if (!skip) {
- *
- *
- * % }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (!ctx.skip) {
- let rf1 = ΔembeddedViewStart(0, 2, 2);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp');
- Δelement(1, 'comp');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', Δbind('1'));
- Δselect(1);
- ΔelementProperty(1, 'val', Δbind('2'));
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
-
- fixture.component.skip = true;
- fixture.update();
- expect(events).toEqual(['comp1', 'comp2']);
- });
-
- it('should be called in child components before parent components', () => {
- /**
- * % if (!skip) {
- *
- * % }
- *
- * parent template:
- */
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (!ctx.skip) {
- let rf1 = ΔembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'parent');
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
-
- fixture.component.skip = true;
- fixture.update();
- expect(events).toEqual(['comp', 'parent']);
- });
-
- it('should be called bottom up with children nested 2 levels deep', () => {
- /**
- * % if (!skip) {
- *
- * % }
- *
- * grandparent template:
- * parent template:
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (!ctx.skip) {
- let rf1 = ΔembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'grandparent');
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
-
- fixture.component.skip = true;
- fixture.update();
- expect(events).toEqual(['comp', 'parent', 'grandparent']);
- });
-
- it('should be called in projected components before their hosts', () => {
- /**
- * % if (!skip) {
- *
- *
- *
- *
- *
- *
- * }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (!ctx.skip) {
- let rf1 = ΔembeddedViewStart(0, 4, 0);
- if (rf1 & RenderFlags.Create) {
- ΔelementStart(0, 'comp');
- { Δelement(1, 'projected'); }
- ΔelementEnd();
- ΔelementStart(2, 'comp');
- { Δelement(3, 'projected'); }
- ΔelementEnd();
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', 1);
- Δselect(1);
- ΔelementProperty(1, 'val', 1);
- Δselect(2);
- ΔelementProperty(2, 'val', 2);
- Δselect(3);
- ΔelementProperty(3, 'val', 2);
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
-
- fixture.component.skip = true;
- fixture.update();
- expect(events).toEqual(['projected1', 'comp1', 'projected2', 'comp2']);
- });
-
-
- it('should be called in consistent order if views are removed and re-added', () => {
- /**
- * % if (condition) {
- *
- * % if (condition2) {
- *
- * % }
- *
- * % }
- */
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (ctx.condition) {
- let rf1 = ΔembeddedViewStart(0, 3, 2);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp');
- Δcontainer(1);
- Δelement(2, 'comp');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', Δbind('1'));
- Δselect(2);
- ΔelementProperty(2, 'val', Δbind('3'));
- ΔcontainerRefreshStart(1);
- {
- if (ctx.condition2) {
- let rf2 = ΔembeddedViewStart(0, 1, 1);
- if (rf2 & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf2 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', Δbind('2'));
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
- fixture.component.condition = true;
- fixture.component.condition2 = true;
- fixture.update();
-
- // remove all views
- fixture.component.condition = false;
- fixture.update();
-
- /**
- * Current angular will process in this same order (root is the top-level removed view):
- *
- * root.child (comp1 view) onDestroy: null
- * root.child.next (container) -> embeddedView
- * embeddedView.child (comp2 view) onDestroy: null
- * embeddedView onDestroy: [comp2]
- * root.child.next.next (comp3 view) onDestroy: null
- * root onDestroy: [comp1, comp3]
- */
- expect(events).toEqual(['comp2', 'comp1', 'comp3']);
-
- events = [];
- // remove inner view
- fixture.component.condition = true;
- fixture.component.condition2 = false;
- fixture.update();
-
- // remove outer view
- fixture.component.condition = false;
- fixture.update();
- expect(events).toEqual(['comp1', 'comp3']);
-
- events = [];
- // restore both views
- fixture.component.condition = true;
- fixture.component.condition2 = true;
- fixture.update();
-
- // remove both views
- fixture.component.condition = false;
- fixture.update();
- expect(events).toEqual(['comp2', 'comp1', 'comp3']);
- });
-
- it('should be called in every iteration of a destroyed for loop', () => {
- /**
- * % if (condition) {
- *
- * % for (let i = 2; i < len; i++) {
- *
- * % }
- *
- * % }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (ctx.condition) {
- let rf1 = ΔembeddedViewStart(0, 3, 2);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp');
- Δcontainer(1);
- Δelement(2, 'comp');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', Δbind('1'));
- Δselect(2);
- ΔelementProperty(2, 'val', Δbind('5'));
- ΔcontainerRefreshStart(1);
- {
- for (let j = 2; j < ctx.len; j++) {
- let rf2 = ΔembeddedViewStart(0, 1, 1);
- if (rf2 & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf2 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', Δbind(j));
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
- fixture.component.condition = true;
- fixture.component.len = 5;
- fixture.update();
-
- fixture.component.condition = false;
- fixture.update();
-
- /**
- * Current angular will process in this same order (root is the top-level removed view):
- *
- * root.child (comp1 view) onDestroy: null
- * root.child.next (container) -> embeddedView (children[0].data)
- * embeddedView.child (comp2 view) onDestroy: null
- * embeddedView onDestroy: [comp2]
- * embeddedView.next.child (comp3 view) onDestroy: null
- * embeddedView.next onDestroy: [comp3]
- * embeddedView.next.next.child (comp4 view) onDestroy: null
- * embeddedView.next.next onDestroy: [comp4]
- * embeddedView.next.next -> container -> root
- * root onDestroy: [comp1, comp5]
- */
- expect(events).toEqual(['comp2', 'comp3', 'comp4', 'comp1', 'comp5']);
-
- events = [];
- fixture.component.condition = true;
- fixture.component.len = 4;
- fixture.update();
-
- fixture.component.condition = false;
- fixture.update();
- expect(events).toEqual(['comp2', 'comp3', 'comp1', 'comp5']);
-
- events = [];
- fixture.component.condition = true;
- fixture.component.len = 5;
- fixture.update();
-
- fixture.component.condition = false;
- fixture.update();
- expect(events).toEqual(['comp2', 'comp3', 'comp4', 'comp1', 'comp5']);
- });
-
- it('should call destroy properly if view also has listeners', () => {
- /**
- * % if (condition) {
- *
- *
- *
- * % }
- */
- function Template(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (ctx.condition) {
- let rf1 = ΔembeddedViewStart(0, 5, 0);
- if (rf1 & RenderFlags.Create) {
- ΔelementStart(0, 'button');
- {
- Δlistener('click', ctx.onClick.bind(ctx));
- Δtext(1, 'Click me');
- }
- ΔelementEnd();
- Δelement(2, 'comp');
- ΔelementStart(3, 'button');
- {
- Δlistener('click', ctx.onClick.bind(ctx));
- Δtext(4, 'Click me');
- }
- ΔelementEnd();
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }
-
- class App {
- counter = 0;
- condition = true;
- onClick() { this.counter++; }
- }
-
- const ctx: {counter: number} = new App();
- renderToHtml(Template, ctx, 1, 0, defs);
-
- const buttons = containerEl.querySelectorAll('button') !;
- buttons[0].click();
- expect(ctx.counter).toEqual(1);
- buttons[1].click();
- expect(ctx.counter).toEqual(2);
-
- renderToHtml(Template, {condition: false}, 1, 0, defs);
-
- buttons[0].click();
- buttons[1].click();
- expect(events).toEqual(['comp']);
- expect(ctx.counter).toEqual(2);
- });
-
- it('should be called on directives after component', () => {
- /**
- * % if (condition) {
- *
- * % }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (ctx.condition) {
- let rf1 = ΔembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp', ['dir', '']);
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
- fixture.component.condition = true;
- fixture.update();
- expect(events).toEqual([]);
-
- fixture.component.condition = false;
- fixture.update();
- expect(events).toEqual(['comp', 'dir']);
-
- });
-
- it('should be called on directives on an element', () => {
- /**
- * % if (condition) {
- *
- * % }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (ctx.condition) {
- let rf1 = ΔembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'div', ['dir', '']);
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
- fixture.component.condition = true;
- fixture.update();
- expect(events).toEqual([]);
-
- fixture.component.condition = false;
- fixture.update();
- expect(events).toEqual(['dir']);
- });
-
- });
-
describe('onChanges', () => {
let events: ({type: string, name: string, [key: string]: any})[];