test(ivy): add onChanges acceptance tests (#30445)

- moves render3 tests to acceptance tests.

PR Close #30445
This commit is contained in:
Ben Lesh 2019-05-14 15:23:16 -07:00 committed by Jason Aden
parent 222dde129d
commit d18c58816f
2 changed files with 926 additions and 769 deletions

View File

@ -8,11 +8,12 @@
import {CommonModule} from '@angular/common';
import {Component, ComponentFactoryResolver, Directive, Input, NgModule, OnChanges, SimpleChanges, 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('ngOnChanges', () => {
describe('onChanges', () => {
it('should correctly support updating one Input among many', () => {
let log: string[] = [];
@ -59,6 +60,930 @@ describe('ngOnChanges', () => {
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', () => {
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 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', () => {

View File

@ -116,774 +116,6 @@ describe('lifecycles', () => {
});
});
describe('onChanges', () => {
let events: ({type: string, name: string, [key: string]: any})[];
beforeEach(() => { events = []; });
/**
* <div>
* <ng-content/>
* </div>
*/
const Comp = createOnChangesComponent('comp', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
ΔprojectionDef();
ΔelementStart(0, 'div');
{ Δprojection(1); }
ΔelementEnd();
}
}, 2);
/**
* <comp [val1]="a" [publicVal2]="b"/>
*/
const Parent = createOnChangesComponent('parent', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(ctx.a));
ΔelementProperty(0, 'publicVal2', Δbind(ctx.b));
}
}, 1, 2, [Comp]);
const ProjectedComp = createOnChangesComponent('projected', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
Δtext(0, 'content');
}
}, 1);
function createOnChangesComponent(
name: string, template: ComponentTemplate<any>, consts: number = 0, vars: number = 0,
directives: any[] = []) {
return class Component {
// @Input() val1: string;
// @Input('publicVal2') val2: string;
a: string = 'wasVal1BeforeMinification';
b: string = 'wasVal2BeforeMinification';
ngOnChanges(changes: SimpleChanges) {
if (changes.a && this.a !== changes.a.currentValue) {
throw Error(
`SimpleChanges invalid expected this.a ${this.a} to equal currentValue ${changes.a.currentValue}`);
}
if (changes.b && this.b !== changes.b.currentValue) {
throw Error(
`SimpleChanges invalid expected this.b ${this.b} to equal currentValue ${changes.b.currentValue}`);
}
events.push({type: 'onChanges', name: 'comp - ' + name, changes});
}
static ngComponentDef = ΔdefineComponent({
type: Component,
selectors: [[name]],
factory: () => new Component(),
consts: consts,
vars: vars,
inputs: {a: 'val1', b: ['publicVal2', 'val2']}, template,
directives: directives,
features: [ΔNgOnChangesFeature()],
});
};
}
class Directive {
// @Input() val1: string;
// @Input('publicVal2') val2: string;
a: string = 'wasVal1BeforeMinification';
b: string = 'wasVal2BeforeMinification';
ngOnChanges(changes: SimpleChanges) {
events.push({type: 'onChanges', name: 'dir - dir', changes});
}
static ngDirectiveDef = ΔdefineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => new Directive(),
inputs: {a: 'val1', b: ['publicVal2', 'val2']},
features: [ΔNgOnChangesFeature()],
});
}
const defs = [Comp, Parent, Directive, ProjectedComp];
it('should call onChanges method after inputs are set in creation and update mode', () => {
/** <comp [val1]="val1" [publicVal2]="val2"></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(ctx.val1));
ΔelementProperty(0, 'publicVal2', Δbind(ctx.val2));
}
}, 1, 2, defs);
// First changes happen here.
const fixture = new ComponentFixture(App);
events = [];
fixture.component.val1 = '1';
fixture.component.val2 = 'a';
fixture.update();
expect(events).toEqual([{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(
undefined, '1', false), // we cleared `events` above, this is the second change
'val2': new SimpleChange(undefined, 'a', false),
}
}]);
events = [];
fixture.component.val1 = '2';
fixture.component.val2 = 'b';
fixture.update();
expect(events).toEqual([{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange('1', '2', false),
'val2': new SimpleChange('a', 'b', false),
}
}]);
});
it('should call parent onChanges before child onChanges', () => {
/**
* <parent></parent>
* parent temp: <comp></comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'parent');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(ctx.val1));
ΔelementProperty(0, 'publicVal2', Δbind(ctx.val2));
}
}, 1, 2, defs);
const fixture = new ComponentFixture(App);
// We're clearing events after the first change here
events = [];
fixture.component.val1 = '1';
fixture.component.val2 = 'a';
fixture.update();
expect(events).toEqual([
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, '1', false),
'val2': new SimpleChange(undefined, 'a', false),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, '1', false),
'val2': new SimpleChange(undefined, 'a', false),
}
},
]);
});
it('should call all parent onChanges across view before calling children onChanges', () => {
/**
* <parent [val1]="1"></parent>
* <parent [val1]="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, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
Δselect(1);
ΔelementProperty(1, 'val1', Δbind(2));
ΔelementProperty(1, 'publicVal2', Δbind(2));
}
}, 2, 4, defs);
const fixture = new ComponentFixture(App);
expect(events).toEqual([
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, 2, true),
'val2': new SimpleChange(undefined, 2, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 2, true),
'val2': new SimpleChange(undefined, 2, true),
}
},
]);
});
it('should call onChanges every time a new view is created (if block)', () => {
/**
* % if (condition) {
* <comp></comp>
* % }
*/
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, 2);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
// Show the `comp` component, causing it to initialize. (first change is true)
fixture.component.condition = true;
fixture.update();
expect(events).toEqual([{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
}]);
// Hide the `comp` component, no onChanges should fire
fixture.component.condition = false;
fixture.update();
expect(events).toEqual([{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
}]);
// Show the `comp` component, it initializes again. (first change is true)
fixture.component.condition = true;
fixture.update();
expect(events).toEqual([
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
}
]);
});
it('should call onChanges 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();
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
Δselect(1);
ΔelementProperty(1, 'val1', Δbind(2));
ΔelementProperty(1, 'publicVal2', Δbind(2));
}
}, 2, 4, defs);
const fixture = new ComponentFixture(App);
expect(events).toEqual([
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - projected',
changes: {
'val1': new SimpleChange(undefined, 2, true),
'val2': new SimpleChange(undefined, 2, true),
}
},
]);
});
it('should call onChanges in host and its content children before next host', () => {
/**
* <comp [val1]="1" [publicVal2]="1">
* <projected [val1]="2" [publicVal2]="2"></projected>
* </comp>
* <comp [val1]="3" [publicVal2]="3">
* <projected [val1]="4" [publicVal2]="4"></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, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
Δselect(1);
ΔelementProperty(1, 'val1', Δbind(2));
ΔelementProperty(1, 'publicVal2', Δbind(2));
Δselect(2);
ΔelementProperty(2, 'val1', Δbind(3));
ΔelementProperty(2, 'publicVal2', Δbind(3));
Δselect(3);
ΔelementProperty(3, 'val1', Δbind(4));
ΔelementProperty(3, 'publicVal2', Δbind(4));
}
}, 4, 8, defs);
const fixture = new ComponentFixture(App);
expect(events).toEqual([
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - projected',
changes: {
'val1': new SimpleChange(undefined, 2, true),
'val2': new SimpleChange(undefined, 2, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 3, true),
'val2': new SimpleChange(undefined, 3, true),
}
},
{
type: 'onChanges',
name: 'comp - projected',
changes: {
'val1': new SimpleChange(undefined, 4, true),
'val2': new SimpleChange(undefined, 4, true),
}
},
]);
});
it('should be called on directives after component', () => {
/**
* <comp dir [val1]="1" [publicVal2]="1"></comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'comp', ['dir', '']);
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
}
}, 1, 2, defs);
const fixture = new ComponentFixture(App);
expect(events).toEqual([
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'dir - dir',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
]);
// Update causes no changes to be fired, since the bindings didn't change.
events = [];
fixture.update();
expect(events).toEqual([]);
});
it('should be called on directives on an element', () => {
/**
* <div dir [val]="1" [publicVal2]="1"></div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'div', ['dir', '']);
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
}
}, 1, 2, defs);
const fixture = new ComponentFixture(App);
expect(events).toEqual([{
type: 'onChanges',
name: 'dir - dir',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
}]);
events = [];
fixture.update();
expect(events).toEqual([]);
});
it('should call onChanges properly in for loop', () => {
/**
* <comp [val1]="1" [publicVal2]="1"></comp>
* % for (let j = 2; j < 5; j++) {
* <comp [val1]="j" [publicVal2]="j"></comp>
* % }
* <comp [val1]="5" [publicVal2]="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, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
Δselect(2);
ΔelementProperty(2, 'val1', Δbind(5));
ΔelementProperty(2, 'publicVal2', Δbind(5));
ΔcontainerRefreshStart(1);
{
for (let j = 2; j < 5; j++) {
let rf1 = ΔembeddedViewStart(0, 1, 2);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(j));
ΔelementProperty(0, 'publicVal2', Δbind(j));
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 3, 4, defs);
const fixture = new ComponentFixture(App);
// onChanges 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([
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 5, true),
'val2': new SimpleChange(undefined, 5, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 2, true),
'val2': new SimpleChange(undefined, 2, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 3, true),
'val2': new SimpleChange(undefined, 3, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 4, true),
'val2': new SimpleChange(undefined, 4, true),
}
},
]);
});
it('should call onChanges properly in for loop with children', () => {
/**
* <parent [val1]="1" [publicVal2]="1"></parent>
* % for (let j = 2; j < 5; j++) {
* <parent [val1]="j" [publicVal2]="j"></parent>
* % }
* <parent [val1]="5" [publicVal2]="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, 'val1', Δbind(1));
ΔelementProperty(0, 'publicVal2', Δbind(1));
Δselect(2);
ΔelementProperty(2, 'val1', Δbind(5));
ΔelementProperty(2, 'publicVal2', Δbind(5));
ΔcontainerRefreshStart(1);
{
for (let j = 2; j < 5; j++) {
let rf1 = ΔembeddedViewStart(0, 1, 2);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'parent');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val1', Δbind(j));
ΔelementProperty(0, 'publicVal2', Δbind(j));
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 3, 4, defs);
const fixture = new ComponentFixture(App);
// onChanges 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([
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, 5, true),
'val2': new SimpleChange(undefined, 5, true),
}
},
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, 2, true),
'val2': new SimpleChange(undefined, 2, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 2, true),
'val2': new SimpleChange(undefined, 2, true),
}
},
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, 3, true),
'val2': new SimpleChange(undefined, 3, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 3, true),
'val2': new SimpleChange(undefined, 3, true),
}
},
{
type: 'onChanges',
name: 'comp - parent',
changes: {
'val1': new SimpleChange(undefined, 4, true),
'val2': new SimpleChange(undefined, 4, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 4, true),
'val2': new SimpleChange(undefined, 4, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 1, true),
'val2': new SimpleChange(undefined, 1, true),
}
},
{
type: 'onChanges',
name: 'comp - comp',
changes: {
'val1': new SimpleChange(undefined, 5, true),
'val2': new SimpleChange(undefined, 5, true),
}
},
]);
});
it('should not call onChanges if props are set directly', () => {
let events: SimpleChanges[] = [];
let compInstance: MyComp;
class MyComp {
value = 0;
ngOnChanges(changes: SimpleChanges) { events.push(changes); }
static ngComponentDef = ΔdefineComponent({
type: MyComp,
factory: () => {
// Capture the instance so we can test setting the property directly
compInstance = new MyComp();
return compInstance;
},
template: (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
Δelement(0, 'div');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'id', Δbind(ctx.a));
}
},
selectors: [['my-comp']],
inputs: {
value: 'value',
},
consts: 1,
vars: 1,
});
}
/**
* <my-comp [value]="1"></my-comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'my-comp');
}
if (rf & RenderFlags.Update) {
ΔelementProperty(0, 'value', Δbind(1));
}
}, 1, 1, [MyComp]);
const fixture = new ComponentFixture(App);
events = [];
// Try setting the property directly
compInstance !.value = 2;
fixture.update();
expect(events).toEqual([]);
});
});
describe('hook order', () => {
let events: string[];