test(ivy): onInit lifecycle acceptance tests (#30445)

Ports render3 lifecycle onInit tests to acceptance tests

PR Close #30445
This commit is contained in:
Ben Lesh 2019-05-13 18:54:14 -07:00 committed by Jason Aden
parent 07ebe9624f
commit 6ec621b72d
2 changed files with 494 additions and 303 deletions

View File

@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, Input, OnChanges, SimpleChanges} from '@angular/core';
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';
describe('ngOnChanges', () => {
it('should correctly support updating one Input among many', () => {
@ -169,3 +171,494 @@ it('should call hooks after setting directives inputs', () => {
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', () => {
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 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',
]);
});
});

View File

@ -78,75 +78,6 @@ describe('lifecycles', () => {
const directives = [Comp, Parent, ProjectedComp, Directive, NgIf];
it('should call onInit method after inputs are set in creation mode (and not in update mode)',
() => {
/** <comp [val]="val"></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind(ctx.val));
}
}, 1, 1, directives);
const fixture = new ComponentFixture(App);
fixture.update();
expect(events).toEqual(['comp']);
fixture.component.val = '2';
fixture.update();
expect(events).toEqual(['comp']);
});
it('should be called on root component in creation mode', () => {
const comp = renderComponent(Comp, {hostFeatures: [LifecycleHooksFeature]});
expect(events).toEqual(['comp']);
markDirty(comp);
requestAnimationFrame.flush();
expect(events).toEqual(['comp']);
});
it('should call parent onInit before child onInit', () => {
/**
* <parent></parent>
* parent temp: <comp></comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'parent');
}
}, 1, 0, directives);
const fixture = new ComponentFixture(App);
expect(events).toEqual(['parent', 'comp']);
});
it('should call all parent onInits across view before calling children onInits', () => {
/**
* <parent [val]="1"></parent>
* <parent [val]="2"></parent>
*
* parent temp: <comp [val]="val"></comp>
*/
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, directives);
const fixture = new ComponentFixture(App);
expect(events).toEqual(['parent1', 'parent2', 'comp1', 'comp2']);
});
it('should call onInit every time a new view is created (if block)', () => {
/**
* % if (!skip) {
@ -183,239 +114,6 @@ describe('lifecycles', () => {
fixture.update();
expect(events).toEqual(['comp', 'comp']);
});
it('should call onInit every time a new view is created (ngIf)', () => {
function IfTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
}
}
/** <comp *ngIf="showing"></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δtemplate(0, IfTemplate, 1, 0, 'comp', [AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'ngIf', Δbind(ctx.showing));
}
}, 1, 0, directives);
const fixture = new ComponentFixture(App);
fixture.component.showing = true;
fixture.update();
expect(events).toEqual(['comp']);
fixture.component.showing = false;
fixture.update();
expect(events).toEqual(['comp']);
fixture.component.showing = true;
fixture.update();
expect(events).toEqual(['comp', 'comp']);
});
it('should call onInit for children of dynamically created components', () => {
let viewContainerComp !: ViewContainerComp;
class ViewContainerComp {
constructor(public vcr: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
static ngComponentDef = ΔdefineComponent({
type: ViewContainerComp,
selectors: [['view-container-comp']],
factory: () => viewContainerComp = new ViewContainerComp(
ΔdirectiveInject(ViewContainerRef as any), injectComponentFactoryResolver()),
consts: 0,
vars: 0,
template: (rf: RenderFlags, ctx: ViewContainerComp) => {}
});
}
const DynamicComp = createComponent('dynamic-comp', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
}
}, 1, 0, [Comp]);
const fixture = new ComponentFixture(ViewContainerComp);
expect(events).toEqual([]);
viewContainerComp.vcr.createComponent(
viewContainerComp.cfr.resolveComponentFactory(DynamicComp));
fixture.update();
expect(events).toEqual(['comp']);
});
it('should call onInit in hosts before their content children', () => {
/**
* <comp>
* <projected></projected>
* </comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ΔelementStart(0, 'comp');
{ ΔelementStart(1, 'projected'); }
ΔelementEnd();
}
}, 2, 0, directives);
const fixture = new ComponentFixture(App);
expect(events).toEqual(['comp', 'projected']);
});
it('should call onInit in host and its content children before next host', () => {
/**
* <comp [val]="1">
* <projected [val]="1"></projected>
* </comp>
* <comp [val]="2">
* <projected [val]="1"></projected>
* </comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ΔelementStart(0, 'comp');
{ ΔelementStart(1, 'projected'); }
ΔelementEnd();
ΔelementStart(2, 'comp');
{ ΔelementStart(3, 'projected'); }
ΔelementEnd();
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', 1);
Δselect(1);
ΔelementProperty(1, 'val', 1);
Δselect(2);
ΔelementProperty(2, 'val', 2);
Δselect(3);
ΔelementProperty(3, 'val', 2);
}
}, 4, 0, directives);
const fixture = new ComponentFixture(App);
expect(events).toEqual(['comp1', 'projected1', 'comp2', 'projected2']);
});
it('should be called on directives after component', () => {
/** <comp directive></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp', ['dir', '']);
}
}, 1, 0, directives);
const fixture = new ComponentFixture(App);
expect(events).toEqual(['comp', 'dir']);
fixture.update();
expect(events).toEqual(['comp', 'dir']);
});
it('should be called on directives on an element', () => {
/** <div directive></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'div', ['dir', '']);
}
}, 1, 0, directives);
const fixture = new ComponentFixture(App);
expect(events).toEqual(['dir']);
fixture.update();
expect(events).toEqual(['dir']);
});
it('should call onInit properly in for loop', () => {
/**
* <comp [val]="1"></comp>
* % for (let j = 2; j < 5; j++) {
* <comp [val]="j"></comp>
* % }
* <comp [val]="5"></comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
Δcontainer(1);
Δelement(2, 'comp');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', 1);
Δselect(2);
ΔelementProperty(2, 'val', 5);
ΔcontainerRefreshStart(1);
{
for (let j = 2; j < 5; j++) {
let rf1 = ΔembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val', j);
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 3, 0, directives);
const fixture = new ComponentFixture(App);
// onInit is called top to bottom, so top level comps (1 and 5) are called
// before the comps inside the for loop's embedded view (2, 3, and 4)
expect(events).toEqual(['comp1', 'comp5', 'comp2', 'comp3', 'comp4']);
});
it('should call onInit properly in for loop with children', () => {
/**
* <parent [val]="1"></parent>
* % for (let j = 2; j < 5; j++) {
* <parent [val]="j"></parent>
* % }
* <parent [val]="5"></parent>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'parent');
Δcontainer(1);
Δelement(2, 'parent');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val', 1);
Δselect(2);
ΔelementProperty(2, 'val', 5);
ΔcontainerRefreshStart(1);
{
for (let j = 2; j < 5; j++) {
let rf1 = ΔembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'parent');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val', j);
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 3, 0, directives);
const fixture = new ComponentFixture(App);
// onInit is called top to bottom, so top level comps (1 and 5) are called
// before the comps inside the for loop's embedded view (2, 3, and 4)
expect(events).toEqual([
'parent1', 'parent5', 'parent2', 'comp2', 'parent3', 'comp3', 'parent4', 'comp4', 'comp1',
'comp5'
]);
});
});
describe('doCheck', () => {