test(ivy): add additional lifecycle hook acceptance tests (#30445)

Moves additional lifecycle hook tests from render3 to acceptance

PR Close #30445
This commit is contained in:
Ben Lesh 2019-05-15 11:52:38 -07:00 committed by Jason Aden
parent d18c58816f
commit 60235b5aef
2 changed files with 338 additions and 279 deletions

View File

@ -3376,3 +3376,335 @@ describe('onDestroy', () => {
expect(events).toEqual(['dir']); 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();
});
});

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ComponentFactoryResolver, OnDestroy, SimpleChange, SimpleChanges, ViewContainerRef} from '../../src/core'; import {OnDestroy} from '../../src/core';
import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, injectComponentFactoryResolver, ΔNgOnChangesFeature, ΔdefineComponent, ΔdefineDirective} from '../../src/render3/index'; import {AttributeMarker, ComponentTemplate, ΔNgOnChangesFeature, ΔdefineComponent, ΔdefineDirective} from '../../src/render3/index';
import {markDirty, Δbind, Δcontainer, ΔcontainerRefreshEnd, ΔcontainerRefreshStart, ΔdirectiveInject, Δelement, ΔelementEnd, ΔelementProperty, ΔelementStart, ΔembeddedViewEnd, ΔembeddedViewStart, Δlistener, Δprojection, ΔprojectionDef, Δselect, Δtemplate, Δtext} from '../../src/render3/instructions/all'; import {Δbind, Δcontainer, ΔcontainerRefreshEnd, ΔcontainerRefreshStart, Δelement, ΔelementEnd, ΔelementProperty, ΔelementStart, ΔembeddedViewEnd, ΔembeddedViewStart, Δprojection, ΔprojectionDef, Δselect, Δtemplate, Δtext} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
import {ComponentFixture, containerEl, createComponent, renderComponent, renderToHtml, requestAnimationFrame} from './render_util'; import {ComponentFixture, createComponent} from './render_util';
describe('lifecycles', () => { describe('lifecycles', () => {
@ -32,7 +32,7 @@ describe('lifecycles', () => {
beforeEach(() => { events = []; }); beforeEach(() => { events = []; });
let Comp = createOnInitComponent('comp', (rf: RenderFlags, ctx: any) => { let Comp = createOnInitComponent('comp', (rf: RenderFlags) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
ΔprojectionDef(); ΔprojectionDef();
ΔelementStart(0, 'div'); ΔelementStart(0, 'div');
@ -41,7 +41,7 @@ describe('lifecycles', () => {
} }
}, 2); }, 2);
let Parent = createOnInitComponent('parent', getParentTemplate('comp'), 1, 1, [Comp]); let Parent = createOnInitComponent('parent', getParentTemplate('comp'), 1, 1, [Comp]);
let ProjectedComp = createOnInitComponent('projected', (rf: RenderFlags, ctx: any) => { let ProjectedComp = createOnInitComponent('projected', (rf: RenderFlags) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
Δtext(0, 'content'); Δtext(0, 'content');
} }
@ -115,277 +115,4 @@ describe('lifecycles', () => {
expect(events).toEqual(['comp', 'comp']); expect(events).toEqual(['comp', 'comp']);
}); });
}); });
describe('hook order', () => {
let events: string[];
beforeEach(() => { events = []; });
function createAllHooksComponent(
name: string, template: ComponentTemplate<any>, consts: number = 0, vars: number = 0,
directives: any[] = []) {
return class Component {
val: string = '';
ngOnChanges() { events.push(`changes ${name}${this.val}`); }
ngOnInit() { events.push(`init ${name}${this.val}`); }
ngDoCheck() { events.push(`check ${name}${this.val}`); }
ngAfterContentInit() { events.push(`contentInit ${name}${this.val}`); }
ngAfterContentChecked() { events.push(`contentCheck ${name}${this.val}`); }
ngAfterViewInit() { events.push(`viewInit ${name}${this.val}`); }
ngAfterViewChecked() { events.push(`viewCheck ${name}${this.val}`); }
static ngComponentDef = ΔdefineComponent({
type: Component,
selectors: [[name]],
factory: () => new Component(),
consts: consts,
vars: vars,
inputs: {val: 'val'}, template,
directives: directives,
features: [ΔNgOnChangesFeature()],
});
};
}
it('should call all hooks in correct order', () => {
const Comp = createAllHooksComponent('comp', (rf: RenderFlags, ctx: any) => {});
/**
* <comp [val]="1"></comp>
* <comp [val]="2"></comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
Δelement(1, 'comp');
}
// This template function is a little weird in that the `elementProperty` calls
// below are directly setting values `1` and `2`, where normally there would be
// a call to `bind()` that would do the work of seeing if something changed.
// This means when `fixture.update()` is called below, ngOnChanges should fire,
// even though the *value* itself never changed.
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', 1);
Δselect(1);
ΔelementProperty(1, 'val', 2);
}
}, 2, 0, [Comp]);
const fixture = new ComponentFixture(App);
expect(events).toEqual([
'changes comp1', 'init comp1', 'check comp1', 'changes comp2', 'init comp2', 'check comp2',
'contentInit comp1', 'contentCheck comp1', 'contentInit comp2', 'contentCheck comp2',
'viewInit comp1', 'viewCheck comp1', 'viewInit comp2', 'viewCheck comp2'
]);
events = [];
fixture.update(); // Changes are made due to lack of `bind()` call in template fn.
expect(events).toEqual([
'changes comp1', 'check comp1', 'changes comp2', 'check comp2', 'contentCheck comp1',
'contentCheck comp2', 'viewCheck comp1', 'viewCheck comp2'
]);
});
it('should call all hooks in correct order with children', () => {
const Comp = createAllHooksComponent('comp', (rf: RenderFlags, ctx: any) => {});
/** <comp [val]="val"></comp> */
const Parent = createAllHooksComponent('parent', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind(ctx.val));
}
}, 1, 1, [Comp]);
/**
* <parent [val]="1"></parent>
* <parent [val]="2"></parent>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'parent');
Δelement(1, 'parent');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', 1);
Δselect(1);
ΔelementProperty(1, 'val', 2);
}
}, 2, 0, [Parent]);
const fixture = new ComponentFixture(App);
expect(events).toEqual([
'changes parent1', 'init parent1', 'check parent1',
'changes parent2', 'init parent2', 'check parent2',
'contentInit parent1', 'contentCheck parent1', 'contentInit parent2',
'contentCheck parent2', 'changes comp1', 'init comp1',
'check comp1', 'contentInit comp1', 'contentCheck comp1',
'viewInit comp1', 'viewCheck comp1', 'changes comp2',
'init comp2', 'check comp2', 'contentInit comp2',
'contentCheck comp2', 'viewInit comp2', 'viewCheck comp2',
'viewInit parent1', 'viewCheck parent1', 'viewInit parent2',
'viewCheck parent2'
]);
events = [];
fixture.update();
expect(events).toEqual([
'changes parent1', 'check parent1', 'changes parent2', 'check parent2',
'contentCheck parent1', 'contentCheck parent2', 'check comp1', 'contentCheck comp1',
'viewCheck comp1', 'check comp2', 'contentCheck comp2', 'viewCheck comp2',
'viewCheck parent1', 'viewCheck parent2'
]);
});
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-ng
it('should call all hooks in correct order with view and content', () => {
const Content = createAllHooksComponent('content', (rf: RenderFlags, ctx: any) => {});
const View = createAllHooksComponent('view', (rf: RenderFlags, ctx: any) => {});
/** <ng-content></ng-content><view [val]="val"></view> */
const Parent = createAllHooksComponent('parent', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
ΔprojectionDef();
Δprojection(0);
Δelement(1, 'view');
}
if (rf & RenderFlags.Update) {
Δselect(1);
ΔelementProperty(1, 'val', Δbind(ctx.val));
}
}, 2, 1, [View]);
/**
* <parent [val]="1">
* <content [val]="1"></content>
* </parent>
* <parent [val]="2">
* <content [val]="2"></content>
* </parent>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ΔelementStart(0, 'parent');
{ Δelement(1, 'content'); }
ΔelementEnd();
ΔelementStart(2, 'parent');
{ Δelement(3, 'content'); }
ΔelementEnd();
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind(1));
Δselect(1);
ΔelementProperty(1, 'val', Δbind(1));
Δselect(2);
ΔelementProperty(2, 'val', Δbind(2));
Δselect(3);
ΔelementProperty(3, 'val', Δbind(2));
}
}, 4, 4, [Parent, Content]);
const fixture = new ComponentFixture(App);
expect(events).toEqual([
'changes parent1', 'init parent1',
'check parent1', 'changes content1',
'init content1', 'check content1',
'changes parent2', 'init parent2',
'check parent2', 'changes content2',
'init content2', 'check content2',
'contentInit content1', 'contentCheck content1',
'contentInit parent1', 'contentCheck parent1',
'contentInit content2', 'contentCheck content2',
'contentInit parent2', 'contentCheck parent2',
'changes view1', 'init view1',
'check view1', 'contentInit view1',
'contentCheck view1', 'viewInit view1',
'viewCheck view1', 'changes view2',
'init view2', 'check view2',
'contentInit view2', 'contentCheck view2',
'viewInit view2', 'viewCheck view2',
'viewInit content1', 'viewCheck content1',
'viewInit parent1', 'viewCheck parent1',
'viewInit content2', 'viewCheck content2',
'viewInit parent2', 'viewCheck parent2'
]);
events = [];
fixture.update();
expect(events).toEqual([
'check parent1', 'check content1', 'check parent2', 'check content2',
'contentCheck content1', 'contentCheck parent1', 'contentCheck content2',
'contentCheck parent2', 'check view1', 'contentCheck view1', 'viewCheck view1',
'check view2', 'contentCheck view2', 'viewCheck view2', 'viewCheck content1',
'viewCheck parent1', 'viewCheck content2', 'viewCheck parent2'
]);
});
});
describe('non-regression', () => {
it('should call lifecycle hooks for directives active on <ng-template>', () => {
let destroyed = false;
class OnDestroyDirective implements OnDestroy {
ngOnDestroy() { destroyed = true; }
static ngDirectiveDef = ΔdefineDirective({
type: OnDestroyDirective,
selectors: [['', 'onDestroyDirective', '']],
factory: () => new OnDestroyDirective()
});
}
function conditionTpl(rf: RenderFlags, ctx: Cmpt) {
if (rf & RenderFlags.Create) {
Δtemplate(0, null, 0, 1, 'ng-template', [AttributeMarker.Bindings, 'onDestroyDirective']);
}
}
/**
* <ng-template [ngIf]="condition">
* <ng-template onDestroyDirective></ng-template>
* </ng-template>
*/
function cmptTpl(rf: RenderFlags, cmpt: Cmpt) {
if (rf & RenderFlags.Create) {
Δtemplate(0, conditionTpl, 1, 1, 'ng-template', [AttributeMarker.Bindings, 'ngIf']);
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'ngIf', Δbind(cmpt.showing));
}
}
class Cmpt {
showing = true;
static ngComponentDef = ΔdefineComponent({
type: Cmpt,
factory: () => new Cmpt(),
selectors: [['cmpt']],
consts: 1,
vars: 1,
template: cmptTpl,
directives: [NgIf, OnDestroyDirective]
});
}
const fixture = new ComponentFixture(Cmpt);
expect(destroyed).toBeFalsy();
fixture.component.showing = false;
fixture.update();
expect(destroyed).toBeTruthy();
});
});
}); });