diff --git a/packages/core/test/acceptance/lifecycle_spec.ts b/packages/core/test/acceptance/lifecycle_spec.ts index ba58110df6..611876f0fd 100644 --- a/packages/core/test/acceptance/lifecycle_spec.ts +++ b/packages/core/test/acceptance/lifecycle_spec.ts @@ -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: `

test

`, + }) + class Comp { + @Input() + val1 = 'a'; + + @Input('publicVal2') + val2 = 'b'; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); } + } + + @Component({template: ``}) + 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: ``, + }) + class Parent { + @Input() + val = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent', changes}); } + } + + @Component({ + selector: 'child', + template: `

test

`, + }) + class Child { + @Input() + val = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'child', changes}); } + } + + @Component({template: ``}) + 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: ``, + }) + class Parent { + @Input() + val = ''; + + @Input() + name = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); } + } + + @Component({ + selector: 'child', + template: `

test

`, + }) + class Child { + @Input() + val = ''; + + @Input() + name = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'child ' + this.name, changes}); } + } + + @Component({ + template: ` + + + ` + }) + 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: `

{{val}}

`, + }) + class Comp { + @Input() + val = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); } + } + + @Component({template: ``}) + 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: `

{{val}}

`, + }) + class Projected { + @Input() + val = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'projected', changes}); } + } + + @Component({ + selector: 'comp', + template: `
`, + }) + class Comp { + @Input() + val = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); } + } + + @Component({ + template: ``, + }) + 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: `

{{val}}

`, + }) + class Projected { + @Input() + val = ''; + + @Input() + name = ''; + + ngOnChanges(changes: SimpleChanges) { + events.push({name: 'projected ' + this.name, changes}); + } + } + + @Component({ + selector: 'comp', + template: `
`, + }) + class Comp { + @Input() + val = ''; + + @Input() + name = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); } + } + + @Component({ + template: ` + + + + + + + `, + }) + 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: `

{{val}}

`, + }) + class Comp { + @Input() + val = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); } + } + + @Component({ + template: ``, + }) + 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: `
`}) + 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: `

{{val}}

`, + }) + class Comp { + @Input() + val = ''; + + @Input() + name = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); } + } + + @Component({ + template: ` + + + + ` + }) + 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: `

{{val}}

`, + }) + class Child { + @Input() + val = ''; + + @Input() + name = ''; + + ngOnChanges(changes: SimpleChanges) { + events.push({name: 'child of parent ' + this.name, changes}); + } + } + + @Component({ + selector: 'parent', + template: ``, + }) + class Parent { + @Input() + val = ''; + + @Input() + name = ''; + + ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); } + } + + @Component({ + template: ` + + + + ` + }) + 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: `

{{value}}

`}) + 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', () => { diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts index 858dc5a269..0c3c35edca 100644 --- a/packages/core/test/render3/lifecycle_spec.ts +++ b/packages/core/test/render3/lifecycle_spec.ts @@ -116,774 +116,6 @@ describe('lifecycles', () => { }); }); - describe('onChanges', () => { - let events: ({type: string, name: string, [key: string]: any})[]; - - beforeEach(() => { events = []; }); - - /** - *
- * - *
- */ - const Comp = createOnChangesComponent('comp', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ΔprojectionDef(); - ΔelementStart(0, 'div'); - { Δprojection(1); } - ΔelementEnd(); - } - }, 2); - - /** - * - */ - 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, 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', () => { - /** */ - 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 temp: - */ - 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', () => { - /** - * - * - */ - - 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) { - * - * % } - */ - - 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', () => { - /** - * - * - * - */ - 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', () => { - /** - * - * - * - * - * - * - */ - 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', () => { - /** - * - */ - 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', () => { - /** - *
- */ - 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', () => { - /** - * - * % for (let j = 2; j < 5; j++) { - * - * % } - * - */ - - 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', () => { - /** - * - * % for (let j = 2; j < 5; j++) { - * - * % } - * - */ - - 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, - }); - } - - /** - * - */ - - 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[];