diff --git a/packages/upgrade/test/common/downgrade_component_adapter_spec.ts b/packages/upgrade/test/common/downgrade_component_adapter_spec.ts index bf5d4cc471..29c758364d 100644 --- a/packages/upgrade/test/common/downgrade_component_adapter_spec.ts +++ b/packages/upgrade/test/common/downgrade_component_adapter_spec.ts @@ -7,6 +7,7 @@ */ import {Compiler, Component, ComponentFactory, Injector, NgModule, TestabilityRegistry} from '@angular/core'; import {TestBed} from '@angular/core/testing'; +import {fixmeIvy} from '@angular/private/testing'; import * as angular from '@angular/upgrade/src/common/angular1'; import {DowngradeComponentAdapter, groupNodesBySelector} from '@angular/upgrade/src/common/downgrade_component_adapter'; @@ -177,26 +178,28 @@ withEachNg1Version(() => { beforeEach(() => registry.unregisterAllApplications()); afterEach(() => registry.unregisterAllApplications()); - it('should add testabilities hook when creating components', () => { + fixmeIvy('FW-561: Runtime compiler is not loaded') && + it('should add testabilities hook when creating components', () => { - let registry = TestBed.get(TestabilityRegistry); - adapter.createComponent([]); - expect(registry.getAllTestabilities().length).toEqual(1); + let registry = TestBed.get(TestabilityRegistry); + adapter.createComponent([]); + expect(registry.getAllTestabilities().length).toEqual(1); - adapter = getAdaptor(); // get a new adaptor to creat a new component - adapter.createComponent([]); - expect(registry.getAllTestabilities().length).toEqual(2); - }); + adapter = getAdaptor(); // get a new adaptor to creat a new component + adapter.createComponent([]); + expect(registry.getAllTestabilities().length).toEqual(2); + }); - it('should remove the testability hook when destroy a component', () => { - const registry = TestBed.get(TestabilityRegistry); - expect(registry.getAllTestabilities().length).toEqual(0); - adapter.createComponent([]); - expect(registry.getAllTestabilities().length).toEqual(1); - adapter.registerCleanup(); - element.remove !(); - expect(registry.getAllTestabilities().length).toEqual(0); - }); + fixmeIvy('FW-561: Runtime compiler is not loaded') && + it('should remove the testability hook when destroy a component', () => { + const registry = TestBed.get(TestabilityRegistry); + expect(registry.getAllTestabilities().length).toEqual(0); + adapter.createComponent([]); + expect(registry.getAllTestabilities().length).toEqual(1); + adapter.registerCleanup(); + element.remove !(); + expect(registry.getAllTestabilities().length).toEqual(0); + }); }); }); diff --git a/packages/upgrade/test/dynamic/upgrade_spec.ts b/packages/upgrade/test/dynamic/upgrade_spec.ts index 84f52cd90b..5ebc3e42aa 100644 --- a/packages/upgrade/test/dynamic/upgrade_spec.ts +++ b/packages/upgrade/test/dynamic/upgrade_spec.ts @@ -10,6 +10,7 @@ import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgM import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {fixmeIvy} from '@angular/private/testing'; import * as angular from '@angular/upgrade/src/common/angular1'; import {$EXCEPTION_HANDLER} from '@angular/upgrade/src/common/constants'; import {UpgradeAdapter, UpgradeAdapterRef} from '@angular/upgrade/src/dynamic/upgrade_adapter'; @@ -22,67 +23,73 @@ declare global { withEachNg1Version(() => { describe('adapter: ng1 to ng2', () => { + beforeEach(() => destroyPlatform()); afterEach(() => destroyPlatform()); describe('(basic use)', () => { it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1)); - it('should instantiate ng2 in ng1 template and project content', async(() => { - const ng1Module = angular.module('ng1', []); + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should instantiate ng2 in ng1 template and project content', async(() => { + const ng1Module = angular.module('ng1', []); - @Component({ - selector: 'ng2', - template: `{{ 'NG2' }}()`, - }) - class Ng2 { - } + @Component({ + selector: 'ng2', + template: `{{ 'NG2' }}()`, + }) + class Ng2 { + } - @NgModule({declarations: [Ng2], imports: [BrowserModule]}) - class Ng2Module { - } + @NgModule({declarations: [Ng2], imports: [BrowserModule]}) + class Ng2Module { + } - const element = - html('
{{ \'ng1[\' }}~{{ \'ng-content\' }}~{{ \']\' }}
'); + const element = + html('
{{ \'ng1[\' }}~{{ \'ng-content\' }}~{{ \']\' }}
'); - const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]'); - ref.dispose(); - }); - })); + const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]'); + ref.dispose(); + }); + })); - it('should instantiate ng1 in ng2 template and project content', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should instantiate ng1 in ng2 template and project content', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); - @Component({ - selector: 'ng2', - template: `{{ 'ng2(' }}{{'transclude'}}{{ ')' }}`, - }) - class Ng2 { - } + @Component({ + selector: 'ng2', + template: `{{ 'ng2(' }}{{'transclude'}}{{ ')' }}`, + }) + class Ng2 { + } - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } - ng1Module.directive('ng1', () => { - return {transclude: true, template: '{{ "ng1" }}()'}; - }); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + ng1Module.directive('ng1', () => { + return { + transclude: true, + template: '{{ "ng1" }}()' + }; + }); + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html('
{{\'ng1(\'}}{{\')\'}}
'); + const element = html('
{{\'ng1(\'}}{{\')\'}}
'); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))'); - ref.dispose(); - }); - })); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))'); + ref.dispose(); + }); + })); it('supports the compilerOptions argument', async(() => { const platformRef = platformBrowserDynamic(); @@ -142,127 +149,140 @@ withEachNg1Version(() => { adapter = new UpgradeAdapter(Ng2Module); }); - it('should throw an uncaught error', fakeAsync(() => { - const resolveSpy = jasmine.createSpy('resolveSpy'); - spyOn(console, 'error'); + fixmeIvy( + 'FW-682: JIT compilation occurs at component definition time rather than bootstrap') && + it('should throw an uncaught error', fakeAsync(() => { + const resolveSpy = jasmine.createSpy('resolveSpy'); + spyOn(console, 'error'); - expect(() => { - adapter.bootstrap(html(''), ['ng1']).ready(resolveSpy); - flushMicrotasks(); - }).toThrowError(); - expect(resolveSpy).not.toHaveBeenCalled(); - })); + expect(() => { + adapter.bootstrap(html(''), ['ng1']).ready(resolveSpy); + flushMicrotasks(); + }).toThrowError(); + expect(resolveSpy).not.toHaveBeenCalled(); + })); - it('should output an error message to the console and re-throw', fakeAsync(() => { - const consoleErrorSpy: jasmine.Spy = spyOn(console, 'error'); - expect(() => { - adapter.bootstrap(html(''), ['ng1']); - flushMicrotasks(); - }).toThrowError(); - const args: any[] = consoleErrorSpy.calls.mostRecent().args; - expect(consoleErrorSpy).toHaveBeenCalled(); - expect(args.length).toBeGreaterThan(0); - expect(args[0]).toEqual(jasmine.any(Error)); - })); + fixmeIvy( + 'FW-682: JIT compilation occurs at component definition time rather than bootstrap') && + it('should output an error message to the console and re-throw', fakeAsync(() => { + const consoleErrorSpy: jasmine.Spy = spyOn(console, 'error'); + expect(() => { + adapter.bootstrap(html(''), ['ng1']); + flushMicrotasks(); + }).toThrowError(); + const args: any[] = consoleErrorSpy.calls.mostRecent().args; + expect(consoleErrorSpy).toHaveBeenCalled(); + expect(args.length).toBeGreaterThan(0); + expect(args[0]).toEqual(jasmine.any(Error)); + })); }); describe('scope/component change-detection', () => { - it('should interleave scope and component expressions', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - const log: string[] = []; - const l = (value: string) => { - log.push(value); - return value + ';'; - }; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should interleave scope and component expressions', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + const log: string[] = []; + const l = (value: string) => { + log.push(value); + return value + ';'; + }; - ng1Module.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'})); - ng1Module.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'})); - ng1Module.run(($rootScope: any) => { - $rootScope.l = l; - $rootScope.reset = () => log.length = 0; - }); - - @Component({ - selector: 'ng2', - template: `{{l('2A')}}{{l('2B')}}{{l('2C')}}` - }) - class Ng2 { - l: any; - constructor() { this.l = l; } - } - - @NgModule({ - declarations: - [adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } - - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - - const element = - html('
{{reset(); l(\'1A\');}}{{l(\'1B\')}}{{l(\'1C\')}}
'); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;'); - // https://github.com/angular/angular.js/issues/12983 - expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']); - ref.dispose(); - }); - })); - - - it('should propagate changes to a downgraded component inside the ngZone', async(() => { - let appComponent: AppComponent; - let upgradeRef: UpgradeAdapterRef; - - @Component({selector: 'my-app', template: ''}) - class AppComponent { - // TODO(issue/24571): remove '!'. - value !: number; - constructor() { appComponent = this; } - } - - @Component({ - selector: 'my-child', - template: '
{{valueFromPromise}}', - }) - class ChildComponent { - // TODO(issue/24571): remove '!'. - valueFromPromise !: number; - @Input() - set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); } - - constructor(private zone: NgZone) {} - - ngOnChanges(changes: SimpleChanges) { - if (changes['value'].isFirstChange()) return; - - this.zone.onMicrotaskEmpty.subscribe(() => { - expect(element.textContent).toEqual('5'); - upgradeRef.dispose(); + ng1Module.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'})); + ng1Module.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'})); + ng1Module.run(($rootScope: any) => { + $rootScope.l = l; + $rootScope.reset = () => log.length = 0; }); - Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue); - } - } + @Component({ + selector: 'ng2', + template: `{{l('2A')}}{{l('2B')}}{{l('2C')}}` + }) + class Ng2 { + l: any; + constructor() { this.l = l; } + } - @NgModule({declarations: [AppComponent, ChildComponent], imports: [BrowserModule]}) - class Ng2Module { - } + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2 + ], + imports: [BrowserModule], + }) + class Ng2Module { + } - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []).directive( - 'myApp', adapter.downgradeNg2Component(AppComponent)); + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(''); + const element = + html('
{{reset(); l(\'1A\');}}{{l(\'1B\')}}{{l(\'1C\')}}
'); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;'); + // https://github.com/angular/angular.js/issues/12983 + expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']); + ref.dispose(); + }); + })); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - upgradeRef = ref; - appComponent.value = 5; - }); - })); + + fixmeIvy( + 'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger') && + it('should propagate changes to a downgraded component inside the ngZone', async(() => { + let appComponent: AppComponent; + let upgradeRef: UpgradeAdapterRef; + + @Component({selector: 'my-app', template: ''}) + class AppComponent { + value?: number; + constructor() { appComponent = this; } + } + + @Component({ + selector: 'my-child', + template: '
{{valueFromPromise}}', + }) + class ChildComponent { + valueFromPromise?: number; + @Input() + set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); } + + constructor(private zone: NgZone) {} + + ngOnChanges(changes: SimpleChanges) { + if (changes['value'].isFirstChange()) return; + + // HACK(ivy): Using setTimeout allows this test to pass but hides the ivy + // renderer timing BC. + // setTimeout(() => { + // expect(element.textContent).toEqual('5'); + // upgradeRef.dispose(); + // }, 0); + this.zone.onMicrotaskEmpty.subscribe(() => { + expect(element.textContent).toEqual('5'); + upgradeRef.dispose(); + }); + + Promise.resolve().then( + () => this.valueFromPromise = changes['value'].currentValue); + } + } + + @NgModule({declarations: [AppComponent, ChildComponent], imports: [BrowserModule]}) + class Ng2Module { + } + + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []).directive( + 'myApp', adapter.downgradeNg2Component(AppComponent)); + + const element = html(''); + + adapter.bootstrap(element, ['ng1']).ready((ref) => { + upgradeRef = ref; + appComponent.value = 5; + }); + })); // This test demonstrates https://github.com/angular/angular/issues/6385 // which was invalidly fixed by https://github.com/angular/angular/pull/6386 @@ -316,235 +336,239 @@ withEachNg1Version(() => { }); })); - it('should bind properties, events', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = - angular.module('ng1', []).value($EXCEPTION_HANDLER, (err: any) => { throw err; }); + fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && + it('should bind properties, events', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []).value( + $EXCEPTION_HANDLER, (err: any) => { throw err; }); - ng1Module.run(($rootScope: any) => { - $rootScope.name = 'world'; - $rootScope.dataA = 'A'; - $rootScope.dataB = 'B'; - $rootScope.modelA = 'initModelA'; - $rootScope.modelB = 'initModelB'; - $rootScope.eventA = '?'; - $rootScope.eventB = '?'; - }); - @Component({ - selector: 'ng2', - inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], - outputs: [ - 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange' - ], - template: 'ignore: {{ignore}}; ' + - 'literal: {{literal}}; interpolate: {{interpolate}}; ' + - 'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' + - 'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})' - }) - class Ng2 { - ngOnChangesCount = 0; - ignore = '-'; - literal = '?'; - interpolate = '?'; - oneWayA = '?'; - oneWayB = '?'; - twoWayA = '?'; - twoWayB = '?'; - eventA = new EventEmitter(); - eventB = new EventEmitter(); - twoWayAEmitter = new EventEmitter(); - twoWayBEmitter = new EventEmitter(); - ngOnChanges(changes: SimpleChanges) { - const assert = (prop: string, value: any) => { - if ((this as any)[prop] != value) { - throw new Error( - `Expected: '${prop}' to be '${value}' but was '${(this as any)[prop]}'`); + ng1Module.run(($rootScope: any) => { + $rootScope.name = 'world'; + $rootScope.dataA = 'A'; + $rootScope.dataB = 'B'; + $rootScope.modelA = 'initModelA'; + $rootScope.modelB = 'initModelB'; + $rootScope.eventA = '?'; + $rootScope.eventB = '?'; + }); + @Component({ + selector: 'ng2', + inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], + outputs: [ + 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', + 'twoWayBEmitter: twoWayBChange' + ], + template: 'ignore: {{ignore}}; ' + + 'literal: {{literal}}; interpolate: {{interpolate}}; ' + + 'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' + + 'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})' + }) + class Ng2 { + ngOnChangesCount = 0; + ignore = '-'; + literal = '?'; + interpolate = '?'; + oneWayA = '?'; + oneWayB = '?'; + twoWayA = '?'; + twoWayB = '?'; + eventA = new EventEmitter(); + eventB = new EventEmitter(); + twoWayAEmitter = new EventEmitter(); + twoWayBEmitter = new EventEmitter(); + ngOnChanges(changes: SimpleChanges) { + const assert = (prop: string, value: any) => { + if ((this as any)[prop] != value) { + throw new Error( + `Expected: '${prop}' to be '${value}' but was '${(this as any)[prop]}'`); + } + }; + + const assertChange = (prop: string, value: any) => { + assert(prop, value); + if (!changes[prop]) { + throw new Error(`Changes record for '${prop}' not found.`); + } + const actValue = changes[prop].currentValue; + if (actValue != value) { + throw new Error( + `Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`); + } + }; + + switch (this.ngOnChangesCount++) { + case 0: + assert('ignore', '-'); + assertChange('literal', 'Text'); + assertChange('interpolate', 'Hello world'); + assertChange('oneWayA', 'A'); + assertChange('oneWayB', 'B'); + assertChange('twoWayA', 'initModelA'); + assertChange('twoWayB', 'initModelB'); + + this.twoWayAEmitter.emit('newA'); + this.twoWayBEmitter.emit('newB'); + this.eventA.emit('aFired'); + this.eventB.emit('bFired'); + break; + case 1: + assertChange('twoWayA', 'newA'); + assertChange('twoWayB', 'newB'); + break; + case 2: + assertChange('interpolate', 'Hello everyone'); + break; + default: + throw new Error('Called too many times! ' + JSON.stringify(changes)); + } } - }; - - const assertChange = (prop: string, value: any) => { - assert(prop, value); - if (!changes[prop]) { - throw new Error(`Changes record for '${prop}' not found.`); - } - const actValue = changes[prop].currentValue; - if (actValue != value) { - throw new Error( - `Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`); - } - }; - - switch (this.ngOnChangesCount++) { - case 0: - assert('ignore', '-'); - assertChange('literal', 'Text'); - assertChange('interpolate', 'Hello world'); - assertChange('oneWayA', 'A'); - assertChange('oneWayB', 'B'); - assertChange('twoWayA', 'initModelA'); - assertChange('twoWayB', 'initModelB'); - - this.twoWayAEmitter.emit('newA'); - this.twoWayBEmitter.emit('newB'); - this.eventA.emit('aFired'); - this.eventB.emit('bFired'); - break; - case 1: - assertChange('twoWayA', 'newA'); - assertChange('twoWayB', 'newB'); - break; - case 2: - assertChange('interpolate', 'Hello everyone'); - break; - default: - throw new Error('Called too many times! ' + JSON.stringify(changes)); } - } - } - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - @NgModule({ - declarations: [Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } - const element = html(`
+ const element = html(`
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent !)) - .toEqual( - 'ignore: -; ' + - 'literal: Text; interpolate: Hello world; ' + - 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' + - 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent !)) + .toEqual( + 'ignore: -; ' + + 'literal: Text; interpolate: Hello world; ' + + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' + + 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); - ref.ng1RootScope.$apply('name = "everyone"'); - expect(multiTrim(document.body.textContent !)) - .toEqual( - 'ignore: -; ' + - 'literal: Text; interpolate: Hello everyone; ' + - 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' + - 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); + ref.ng1RootScope.$apply('name = "everyone"'); + expect(multiTrim(document.body.textContent !)) + .toEqual( + 'ignore: -; ' + + 'literal: Text; interpolate: Hello everyone; ' + + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' + + 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); - ref.dispose(); - }); + ref.dispose(); + }); - })); + })); - it('should support two-way binding and event listener', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const listenerSpy = jasmine.createSpy('$rootScope.listener'); - const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { - $rootScope['value'] = 'world'; - $rootScope['listener'] = listenerSpy; - }); + fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && + it('should support two-way binding and event listener', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const listenerSpy = jasmine.createSpy('$rootScope.listener'); + const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { + $rootScope['value'] = 'world'; + $rootScope['listener'] = listenerSpy; + }); - @Component({selector: 'ng2', template: `model: {{model}};`}) - class Ng2Component implements OnChanges { - ngOnChangesCount = 0; - @Input() model = '?'; - @Output() modelChange = new EventEmitter(); + @Component({selector: 'ng2', template: `model: {{model}};`}) + class Ng2Component implements OnChanges { + ngOnChangesCount = 0; + @Input() model = '?'; + @Output() modelChange = new EventEmitter(); - ngOnChanges(changes: SimpleChanges) { - switch (this.ngOnChangesCount++) { - case 0: - expect(changes.model.currentValue).toBe('world'); - this.modelChange.emit('newC'); - break; - case 1: - expect(changes.model.currentValue).toBe('newC'); - break; - default: - throw new Error('Called too many times! ' + JSON.stringify(changes)); + ngOnChanges(changes: SimpleChanges) { + switch (this.ngOnChangesCount++) { + case 0: + expect(changes.model.currentValue).toBe('world'); + this.modelChange.emit('newC'); + break; + case 1: + expect(changes.model.currentValue).toBe('newC'); + break; + default: + throw new Error('Called too many times! ' + JSON.stringify(changes)); + } + } } - } - } - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - @NgModule({declarations: [Ng2Component], imports: [BrowserModule]}) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({declarations: [Ng2Component], imports: [BrowserModule]}) + class Ng2Module { + ngDoBootstrap() {} + } - const element = html(` + const element = html(`
| value: {{value}}
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); - expect(listenerSpy).toHaveBeenCalledWith('newC'); - ref.dispose(); - }); - })); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); + expect(listenerSpy).toHaveBeenCalledWith('newC'); + ref.dispose(); + }); + })); - it('should initialize inputs in time for `ngOnChanges`', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && + it('should initialize inputs in time for `ngOnChanges`', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - @Component({ - selector: 'ng2', - template: ` + @Component({ + selector: 'ng2', + template: ` ngOnChangesCount: {{ ngOnChangesCount }} | firstChangesCount: {{ firstChangesCount }} | initialValue: {{ initialValue }}` - }) - class Ng2Component implements OnChanges { - ngOnChangesCount = 0; - firstChangesCount = 0; - // TODO(issue/24571): remove '!'. - initialValue !: string; - // TODO(issue/24571): remove '!'. - @Input() foo !: string; + }) + class Ng2Component implements OnChanges { + ngOnChangesCount = 0; + firstChangesCount = 0; + // TODO(issue/24571): remove '!'. + initialValue !: string; + // TODO(issue/24571): remove '!'. + @Input() foo !: string; - ngOnChanges(changes: SimpleChanges) { - this.ngOnChangesCount++; + ngOnChanges(changes: SimpleChanges) { + this.ngOnChangesCount++; - if (this.ngOnChangesCount === 1) { - this.initialValue = this.foo; + if (this.ngOnChangesCount === 1) { + this.initialValue = this.foo; + } + + if (changes['foo'] && changes['foo'].isFirstChange()) { + this.firstChangesCount++; + } + } } - if (changes['foo'] && changes['foo'].isFirstChange()) { - this.firstChangesCount++; + @NgModule({imports: [BrowserModule], declarations: [Ng2Component]}) + class Ng2Module { } - } - } - @NgModule({imports: [BrowserModule], declarations: [Ng2Component]}) - class Ng2Module { - } + const ng1Module = angular.module('ng1', []).directive( + 'ng2', adapter.downgradeNg2Component(Ng2Component)); - const ng1Module = angular.module('ng1', []).directive( - 'ng2', adapter.downgradeNg2Component(Ng2Component)); - - const element = html(` + const element = html(` `); - adapter.bootstrap(element, ['ng1']).ready(ref => { - const nodes = element.querySelectorAll('ng2'); - const expectedTextWith = (value: string) => - `ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`; + adapter.bootstrap(element, ['ng1']).ready(ref => { + const nodes = element.querySelectorAll('ng2'); + const expectedTextWith = (value: string) => + `ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`; - expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo')); - expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar')); - expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz')); - expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux')); + expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo')); + expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar')); + expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz')); + expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux')); - ref.dispose(); - }); - })); + ref.dispose(); + }); + })); it('should bind to ng-model', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); @@ -637,48 +661,49 @@ withEachNg1Version(() => { }); })); - it('should properly run cleanup with multiple levels of nesting', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let destroyed = false; + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should properly run cleanup with multiple levels of nesting', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let destroyed = false; - @Component( - {selector: 'ng2-outer', template: '
'}) - class Ng2OuterComponent { - @Input() destroyIt = false; - } + @Component( + {selector: 'ng2-outer', template: '
'}) + class Ng2OuterComponent { + @Input() destroyIt = false; + } - @Component({selector: 'ng2-inner', template: 'test'}) - class Ng2InnerComponent implements OnDestroy { - ngOnDestroy() { destroyed = true; } - } + @Component({selector: 'ng2-inner', template: 'test'}) + class Ng2InnerComponent implements OnDestroy { + ngOnDestroy() { destroyed = true; } + } - @NgModule({ - imports: [BrowserModule], - declarations: - [Ng2InnerComponent, Ng2OuterComponent, adapter.upgradeNg1Component('ng1')], - schemas: [NO_ERRORS_SCHEMA], - }) - class Ng2Module { - } + @NgModule({ + imports: [BrowserModule], + declarations: + [Ng2InnerComponent, Ng2OuterComponent, adapter.upgradeNg1Component('ng1')], + schemas: [NO_ERRORS_SCHEMA], + }) + class Ng2Module { + } - const ng1Module = - angular.module('ng1', []) - .directive('ng1', () => ({template: ''})) - .directive('ng2Inner', adapter.downgradeNg2Component(Ng2InnerComponent)) - .directive('ng2Outer', adapter.downgradeNg2Component(Ng2OuterComponent)); + const ng1Module = + angular.module('ng1', []) + .directive('ng1', () => ({template: ''})) + .directive('ng2Inner', adapter.downgradeNg2Component(Ng2InnerComponent)) + .directive('ng2Outer', adapter.downgradeNg2Component(Ng2OuterComponent)); - const element = html(''); + const element = html(''); - adapter.bootstrap(element, [ng1Module.name]).ready(ref => { - expect(element.textContent).toBe('test'); - expect(destroyed).toBe(false); + adapter.bootstrap(element, [ng1Module.name]).ready(ref => { + expect(element.textContent).toBe('test'); + expect(destroyed).toBe(false); - $apply(ref, 'destroyIt = true'); + $apply(ref, 'destroyIt = true'); - expect(element.textContent).toBe(''); - expect(destroyed).toBe(true); - }); - })); + expect(element.textContent).toBe(''); + expect(destroyed).toBe(true); + }); + })); it('should fallback to the root ng2.injector when compiled outside the dom', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); @@ -716,68 +741,72 @@ withEachNg1Version(() => { }); })); - it('should support multi-slot projection', async(() => { - const ng1Module = angular.module('ng1', []); + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support multi-slot projection', async(() => { + const ng1Module = angular.module('ng1', []); - @Component({ - selector: 'ng2', - template: '2a()' + - '2b()' - }) - class Ng2 { - } + @Component({ + selector: 'ng2', + template: '2a()' + + '2b()' + }) + class Ng2 { + } - @NgModule({declarations: [Ng2], imports: [BrowserModule]}) - class Ng2Module { - } + @NgModule({declarations: [Ng2], imports: [BrowserModule]}) + class Ng2Module { + } - // The ng-if on one of the projected children is here to make sure - // the correct slot is targeted even with structural directives in play. - const element = html( - '
1a
1b
'); + // The ng-if on one of the projected children is here to make sure + // the correct slot is targeted even with structural directives in play. + const element = html( + '
1a
1b
'); - const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); - ref.dispose(); - }); - })); + const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); + ref.dispose(); + }); + })); - it('should correctly project structural directives', async(() => { - @Component({selector: 'ng2', template: 'ng2-{{ itemId }}()'}) - class Ng2Component { - // TODO(issue/24571): remove '!'. - @Input() itemId !: string; - } + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should correctly project structural directives', async(() => { + @Component( + {selector: 'ng2', template: 'ng2-{{ itemId }}()'}) + class Ng2Component { + // TODO(issue/24571): remove '!'. + @Input() itemId !: string; + } - @NgModule({imports: [BrowserModule], declarations: [Ng2Component]}) - class Ng2Module { - } + @NgModule({imports: [BrowserModule], declarations: [Ng2Component]}) + class Ng2Module { + } - const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); - const ng1Module = angular.module('ng1', []) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)) - .run(($rootScope: angular.IRootScopeService) => { - $rootScope['items'] = [ - {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, - {id: 'c', subitems: [7, 8, 9]} - ]; - }); + const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); + const ng1Module = + angular.module('ng1', []) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)) + .run(($rootScope: angular.IRootScopeService) => { + $rootScope['items'] = [ + {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, + {id: 'c', subitems: [7, 8, 9]} + ]; + }); - const element = html(` + const element = html(`
{{ subitem }}
`); - adapter.bootstrap(element, [ng1Module.name]).ready(ref => { - expect(multiTrim(document.body.textContent)) - .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); - ref.dispose(); - }); - })); + adapter.bootstrap(element, [ng1Module.name]).ready(ref => { + expect(multiTrim(document.body.textContent)) + .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); + ref.dispose(); + }); + })); it('should allow attribute selectors for components in ng2', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module)); @@ -806,500 +835,519 @@ withEachNg1Version(() => { }); describe('upgrade ng1 component', () => { - it('should support `@` bindings', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support `@` bindings', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: 'Inside: {{ $ctrl.inputA }}, {{ $ctrl.inputB }}', - bindings: {inputA: '@inputAttrA', inputB: '@'} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: 'Inside: {{ $ctrl.inputA }}, {{ $ctrl.inputB }}', + bindings: {inputA: '@inputAttrA', inputB: '@'} + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` | Outside: {{ dataA }}, {{ dataB }} ` - }) - class Ng2Component { - dataA = 'foo'; - dataB = 'bar'; + }) + class Ng2Component { + dataA = 'foo'; + dataB = 'bar'; - constructor() { ng2ComponentInstance = this; } - } + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - imports: [BrowserModule] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + imports: [BrowserModule] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - const ng1 = element.querySelector('ng1') !; - const ng1Controller = angular.element(ng1).controller !('ng1'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + const ng1 = element.querySelector('ng1') !; + const ng1Controller = angular.element(ng1).controller !('ng1'); - expect(multiTrim(element.textContent)).toBe('Inside: foo, bar | Outside: foo, bar'); + expect(multiTrim(element.textContent)) + .toBe('Inside: foo, bar | Outside: foo, bar'); - ng1Controller.inputA = 'baz'; - ng1Controller.inputB = 'qux'; - $digest(ref); + ng1Controller.inputA = 'baz'; + ng1Controller.inputB = 'qux'; + $digest(ref); - expect(multiTrim(element.textContent)).toBe('Inside: baz, qux | Outside: foo, bar'); + expect(multiTrim(element.textContent)) + .toBe('Inside: baz, qux | Outside: foo, bar'); - ng2ComponentInstance.dataA = 'foo2'; - ng2ComponentInstance.dataB = 'bar2'; - $digest(ref); + ng2ComponentInstance.dataA = 'foo2'; + ng2ComponentInstance.dataB = 'bar2'; + $digest(ref); - expect(multiTrim(element.textContent)) - .toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); + expect(multiTrim(element.textContent)) + .toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); - ref.dispose(); - }); - })); + ref.dispose(); + }); + })); - it('should support `<` bindings', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support `<` bindings', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: 'Inside: {{ $ctrl.inputA.value }}, {{ $ctrl.inputB.value }}', - bindings: {inputA: ' | Outside: {{ dataA.value }}, {{ dataB.value }} ` - }) - class Ng2Component { - dataA = {value: 'foo'}; - dataB = {value: 'bar'}; + }) + class Ng2Component { + dataA = {value: 'foo'}; + dataB = {value: 'bar'}; - constructor() { ng2ComponentInstance = this; } - } + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - imports: [BrowserModule] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + imports: [BrowserModule] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - const ng1 = element.querySelector('ng1') !; - const ng1Controller = angular.element(ng1).controller !('ng1'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + const ng1 = element.querySelector('ng1') !; + const ng1Controller = angular.element(ng1).controller !('ng1'); - expect(multiTrim(element.textContent)).toBe('Inside: foo, bar | Outside: foo, bar'); + expect(multiTrim(element.textContent)) + .toBe('Inside: foo, bar | Outside: foo, bar'); - ng1Controller.inputA = {value: 'baz'}; - ng1Controller.inputB = {value: 'qux'}; - $digest(ref); + ng1Controller.inputA = {value: 'baz'}; + ng1Controller.inputB = {value: 'qux'}; + $digest(ref); - expect(multiTrim(element.textContent)).toBe('Inside: baz, qux | Outside: foo, bar'); + expect(multiTrim(element.textContent)) + .toBe('Inside: baz, qux | Outside: foo, bar'); - ng2ComponentInstance.dataA = {value: 'foo2'}; - ng2ComponentInstance.dataB = {value: 'bar2'}; - $digest(ref); + ng2ComponentInstance.dataA = {value: 'foo2'}; + ng2ComponentInstance.dataB = {value: 'bar2'}; + $digest(ref); - expect(multiTrim(element.textContent)) - .toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); + expect(multiTrim(element.textContent)) + .toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); - ref.dispose(); - }); - })); + ref.dispose(); + }); + })); - it('should support `=` bindings', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support `=` bindings', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: 'Inside: {{ $ctrl.inputA.value }}, {{ $ctrl.inputB.value }}', - bindings: {inputA: '=inputAttrA', inputB: '='} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: 'Inside: {{ $ctrl.inputA.value }}, {{ $ctrl.inputB.value }}', + bindings: {inputA: '=inputAttrA', inputB: '='} + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` | Outside: {{ dataA.value }}, {{ dataB.value }} ` - }) - class Ng2Component { - dataA = {value: 'foo'}; - dataB = {value: 'bar'}; + }) + class Ng2Component { + dataA = {value: 'foo'}; + dataB = {value: 'bar'}; - constructor() { ng2ComponentInstance = this; } - } + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - imports: [BrowserModule] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + imports: [BrowserModule] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - const ng1 = element.querySelector('ng1') !; - const ng1Controller = angular.element(ng1).controller !('ng1'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + const ng1 = element.querySelector('ng1') !; + const ng1Controller = angular.element(ng1).controller !('ng1'); - expect(multiTrim(element.textContent)).toBe('Inside: foo, bar | Outside: foo, bar'); + expect(multiTrim(element.textContent)) + .toBe('Inside: foo, bar | Outside: foo, bar'); - ng1Controller.inputA = {value: 'baz'}; - ng1Controller.inputB = {value: 'qux'}; - $digest(ref); + ng1Controller.inputA = {value: 'baz'}; + ng1Controller.inputB = {value: 'qux'}; + $digest(ref); - expect(multiTrim(element.textContent)).toBe('Inside: baz, qux | Outside: baz, qux'); + expect(multiTrim(element.textContent)) + .toBe('Inside: baz, qux | Outside: baz, qux'); - ng2ComponentInstance.dataA = {value: 'foo2'}; - ng2ComponentInstance.dataB = {value: 'bar2'}; - $digest(ref); + ng2ComponentInstance.dataA = {value: 'foo2'}; + ng2ComponentInstance.dataB = {value: 'bar2'}; + $digest(ref); - expect(multiTrim(element.textContent)) - .toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); + expect(multiTrim(element.textContent)) + .toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); - ref.dispose(); - }); - })); + ref.dispose(); + }); + })); - it('should support `&` bindings', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support `&` bindings', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: 'Inside: -', - bindings: {outputA: '&outputAttrA', outputB: '&'} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: 'Inside: -', + bindings: {outputA: '&outputAttrA', outputB: '&'} + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` | Outside: {{ dataA }}, {{ dataB }} ` - }) - class Ng2Component { - dataA = 'foo'; - dataB = 'bar'; - } - - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - - // Define `Ng2Module` - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - imports: [BrowserModule] - }) - class Ng2Module { - } - - // Bootstrap - const element = html(``); - - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - const ng1 = element.querySelector('ng1') !; - const ng1Controller = angular.element(ng1).controller !('ng1'); - - expect(multiTrim(element.textContent)).toBe('Inside: - | Outside: foo, bar'); - - ng1Controller.outputA('baz'); - ng1Controller.outputB('qux'); - $digest(ref); - - expect(multiTrim(element.textContent)).toBe('Inside: - | Outside: baz, qux'); - - ref.dispose(); - }); - })); - - it('should bind properties, events', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - - const ng1 = () => { - return { - template: 'Hello {{fullName}}; A: {{modelA}}; B: {{modelB}}; C: {{modelC}}; | ', - scope: {fullName: '@', modelA: '=dataA', modelB: '=dataB', modelC: '=', event: '&'}, - link: function(scope: any) { - scope.$watch('modelB', (v: string) => { - if (v == 'Savkin') { - scope.modelB = 'SAVKIN'; - scope.event('WORKS'); - - // Should not update because [model-a] is uni directional - scope.modelA = 'VICTOR'; - } - }); + }) + class Ng2Component { + dataA = 'foo'; + dataB = 'bar'; } - }; - }; - ng1Module.directive('ng1', ng1); - @Component({ - selector: 'ng2', - template: - '' + - '' + - '{{event}}-{{last}}, {{first}}, {{city}}' - }) - class Ng2 { - first = 'Victor'; - last = 'Savkin'; - city = 'SF'; - event = '?'; - } - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + // Define `ng1Module` + const ng1Module = angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule - // events, and so without this we would not see the events processed. - setTimeout(() => { - expect(multiTrim(document.body.textContent)) - .toEqual( - 'Hello SAVKIN, Victor, SF; A: VICTOR; B: SAVKIN; C: SF; | Hello TEST; A: First; B: Last; C: City; | WORKS-SAVKIN, Victor, SF'); - ref.dispose(); - }, 0); - }); - })); + // Define `Ng2Module` + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + imports: [BrowserModule] + }) + class Ng2Module { + } - it('should bind optional properties', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); + // Bootstrap + const element = html(``); - const ng1 = () => { - return { - template: 'Hello; A: {{modelA}}; B: {{modelB}}; | ', - scope: {modelA: '=?dataA', modelB: '=?'} - }; - }; - ng1Module.directive('ng1', ng1); - @Component({ - selector: 'ng2', - template: '' + - '' + - '' + - '' - }) - class Ng2 { - first = 'Victor'; - last = 'Savkin'; - } + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + const ng1 = element.querySelector('ng1') !; + const ng1Controller = angular.element(ng1).controller !('ng1'); - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + expect(multiTrim(element.textContent)).toBe('Inside: - | Outside: foo, bar'); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule - // events, and so without this we would not see the events processed. - setTimeout(() => { - expect(multiTrim(document.body.textContent)) - .toEqual( - 'Hello; A: Victor; B: Savkin; | Hello; A: First; B: Last; | Hello; A: ; B: ; | Hello; A: ; B: ; |'); - ref.dispose(); - }, 0); - }); - })); + ng1Controller.outputA('baz'); + ng1Controller.outputB('qux'); + $digest(ref); - it('should bind properties, events in controller when bindToController is not used', - async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); + expect(multiTrim(element.textContent)).toBe('Inside: - | Outside: baz, qux'); - const ng1 = () => { - return { - restrict: 'E', - template: '{{someText}} - Length: {{data.length}}', - scope: {data: '='}, - controller: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; } - }; - }; - - ng1Module.directive('ng1', ng1); - @Component({ - selector: 'ng2', - template: - '{{someText}} - Length: {{dataList.length}} | ' - }) - class Ng2 { - dataList = [1, 2, 3]; - someText = 'ng2'; - } - - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } - - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule - // events, and so without this we would not see the events processed. - setTimeout(() => { - expect(multiTrim(document.body.textContent)) - .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3'); - ref.dispose(); - }, 0); - }); - })); - - it('should bind properties, events in link function', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - - const ng1 = () => { - return { - restrict: 'E', - template: '{{someText}} - Length: {{data.length}}', - scope: {data: '='}, - link: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; } - }; - }; - - ng1Module.directive('ng1', ng1); - @Component({ - selector: 'ng2', - template: - '{{someText}} - Length: {{dataList.length}} | ' - }) - class Ng2 { - dataList = [1, 2, 3]; - someText = 'ng2'; - } - - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } - - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule - // events, and so without this we would not see the events processed. - setTimeout(() => { - expect(multiTrim(document.body.textContent)) - .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3'); - ref.dispose(); - }, 0); - }); - })); - - it('should support templateUrl fetched from $httpBackend', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - ng1Module.value( - '$httpBackend', (method: string, url: string, post: any, cbFn: Function) => { - cbFn(200, `${method}:${url}`); + ref.dispose(); }); + })); - const ng1 = () => { return {templateUrl: 'url.html'}; }; - ng1Module.directive('ng1', ng1); - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should bind properties, events', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + const ng1 = () => { + return { + template: 'Hello {{fullName}}; A: {{modelA}}; B: {{modelB}}; C: {{modelC}}; | ', + scope: + {fullName: '@', modelA: '=dataA', modelB: '=dataB', modelC: '=', event: '&'}, + link: function(scope: any) { + scope.$watch('modelB', (v: string) => { + if (v == 'Savkin') { + scope.modelB = 'SAVKIN'; + scope.event('WORKS'); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('GET:url.html'); - ref.dispose(); - }); - })); + // Should not update because [model-a] is uni directional + scope.modelA = 'VICTOR'; + } + }); + } + }; + }; + ng1Module.directive('ng1', ng1); + @Component({ + selector: 'ng2', + template: + '' + + '' + + '{{event}}-{{last}}, {{first}}, {{city}}' + }) + class Ng2 { + first = 'Victor'; + last = 'Savkin'; + city = 'SF'; + event = '?'; + } - it('should support templateUrl as a function', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - ng1Module.value( - '$httpBackend', (method: string, url: string, post: any, cbFn: Function) => { - cbFn(200, `${method}:${url}`); + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule + // events, and so without this we would not see the events processed. + setTimeout(() => { + expect(multiTrim(document.body.textContent)) + .toEqual( + 'Hello SAVKIN, Victor, SF; A: VICTOR; B: SAVKIN; C: SF; | Hello TEST; A: First; B: Last; C: City; | WORKS-SAVKIN, Victor, SF'); + ref.dispose(); + }, 0); }); + })); - const ng1 = () => { return {templateUrl() { return 'url.html'; }}; }; - ng1Module.directive('ng1', ng1); - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should bind optional properties', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + const ng1 = () => { + return { + template: 'Hello; A: {{modelA}}; B: {{modelB}}; | ', + scope: {modelA: '=?dataA', modelB: '=?'} + }; + }; + ng1Module.directive('ng1', ng1); + @Component({ + selector: 'ng2', + template: '' + + '' + + '' + + '' + }) + class Ng2 { + first = 'Victor'; + last = 'Savkin'; + } - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('GET:url.html'); - ref.dispose(); - }); - })); + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule + // events, and so without this we would not see the events processed. + setTimeout(() => { + expect(multiTrim(document.body.textContent)) + .toEqual( + 'Hello; A: Victor; B: Savkin; | Hello; A: First; B: Last; | Hello; A: ; B: ; | Hello; A: ; B: ; |'); + ref.dispose(); + }, 0); + }); + })); + + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should bind properties, events in controller when bindToController is not used', + async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + + const ng1 = () => { + return { + restrict: 'E', + template: '{{someText}} - Length: {{data.length}}', + scope: {data: '='}, + controller: function($scope: any) { + $scope.someText = 'ng1 - Data: ' + $scope.data; + } + }; + }; + + ng1Module.directive('ng1', ng1); + @Component({ + selector: 'ng2', + template: + '{{someText}} - Length: {{dataList.length}} | ' + }) + class Ng2 { + dataList = [1, 2, 3]; + someText = 'ng2'; + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule + // events, and so without this we would not see the events processed. + setTimeout(() => { + expect(multiTrim(document.body.textContent)) + .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3'); + ref.dispose(); + }, 0); + }); + })); + + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should bind properties, events in link function', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + + const ng1 = () => { + return { + restrict: 'E', + template: '{{someText}} - Length: {{data.length}}', + scope: {data: '='}, + link: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; } + }; + }; + + ng1Module.directive('ng1', ng1); + @Component({ + selector: 'ng2', + template: + '{{someText}} - Length: {{dataList.length}} | ' + }) + class Ng2 { + dataList = [1, 2, 3]; + someText = 'ng2'; + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule + // events, and so without this we would not see the events processed. + setTimeout(() => { + expect(multiTrim(document.body.textContent)) + .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3'); + ref.dispose(); + }, 0); + }); + })); + + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support templateUrl fetched from $httpBackend', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + ng1Module.value( + '$httpBackend', (method: string, url: string, post: any, cbFn: Function) => { + cbFn(200, `${method}:${url}`); + }); + + const ng1 = () => { return {templateUrl: 'url.html'}; }; + ng1Module.directive('ng1', ng1); + @Component({selector: 'ng2', template: ''}) + class Ng2 { + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('GET:url.html'); + ref.dispose(); + }); + })); + + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support templateUrl as a function', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + ng1Module.value( + '$httpBackend', (method: string, url: string, post: any, cbFn: Function) => { + cbFn(200, `${method}:${url}`); + }); + + const ng1 = () => { return {templateUrl() { return 'url.html'; }}; }; + ng1Module.directive('ng1', ng1); + @Component({selector: 'ng2', template: ''}) + class Ng2 { + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('GET:url.html'); + ref.dispose(); + }); + })); it('should support empty template', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); @@ -1353,1293 +1401,1335 @@ withEachNg1Version(() => { }); })); - it('should support templateUrl fetched from $templateCache', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - ng1Module.run(($templateCache: any) => $templateCache.put('url.html', 'WORKS')); + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support templateUrl fetched from $templateCache', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + ng1Module.run(($templateCache: any) => $templateCache.put('url.html', 'WORKS')); - const ng1 = () => { return {templateUrl: 'url.html'}; }; - ng1Module.directive('ng1', ng1); + const ng1 = () => { return {templateUrl: 'url.html'}; }; + ng1Module.directive('ng1', ng1); - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } - - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } - - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('WORKS'); - ref.dispose(); - }); - })); - - it('should support controller with controllerAs', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - - const ng1 = () => { - return { - scope: true, - template: - '{{ctl.scope}}; {{ctl.isClass}}; {{ctl.hasElement}}; {{ctl.isPublished()}}', - controllerAs: 'ctl', - controller: class { - scope: any; hasElement: string; $element: any; isClass: any; - constructor($scope: any, $element: any) { - this.verifyIAmAClass(); - this.scope = $scope.$parent.$parent == $scope.$root ? 'scope' : 'wrong-scope'; - this.hasElement = $element[0].nodeName; - this.$element = $element; - } verifyIAmAClass() { this.isClass = 'isClass'; } isPublished() { - return this.$element.controller('ng1') == this ? 'published' : 'not-published'; - } + @Component({selector: 'ng2', template: ''}) + class Ng2 { } - }; - }; - ng1Module.directive('ng1', ng1); - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } - - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } - - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('scope; isClass; NG1; published'); - ref.dispose(); - }); - })); - - it('should support bindToController', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - - const ng1 = () => { - return { - scope: {title: '@'}, - bindToController: true, - template: '{{ctl.title}}', - controllerAs: 'ctl', - controller: class {} - }; - }; - ng1Module.directive('ng1', ng1); - - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } - - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } - - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('WORKS'); - ref.dispose(); - }); - })); - - it('should support bindToController with bindings', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - - const ng1 = () => { - return { - scope: {}, - bindToController: {title: '@'}, - template: '{{ctl.title}}', - controllerAs: 'ctl', - controller: class {} - }; - }; - ng1Module.directive('ng1', ng1); - - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } - - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } - - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('WORKS'); - ref.dispose(); - }); - })); - - it('should support single require in linking fn', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - - const ng1 = ($rootScope: any) => { - return { - scope: {title: '@'}, - bindToController: true, - template: '{{ctl.status}}', - require: 'ng1', - controllerAs: 'ctrl', - controller: class {status = 'WORKS';}, - link: function(scope: any, element: any, attrs: any, linkController: any) { - expect(scope.$root).toEqual($rootScope); - expect(element[0].nodeName).toEqual('NG1'); - expect(linkController.status).toEqual('WORKS'); - scope.ctl = linkController; + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { } - }; - }; - ng1Module.directive('ng1', ng1); - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('WORKS'); + ref.dispose(); + }); + })); - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support controller with controllerAs', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('WORKS'); - ref.dispose(); - }); - })); + const ng1 = () => { + return { + scope: true, + template: + '{{ctl.scope}}; {{ctl.isClass}}; {{ctl.hasElement}}; {{ctl.isPublished()}}', + controllerAs: 'ctl', + controller: class { + scope: any; hasElement: string; $element: any; isClass: any; + constructor($scope: any, $element: any) { + this.verifyIAmAClass(); + this.scope = + $scope.$parent.$parent == $scope.$root ? 'scope' : 'wrong-scope'; + this.hasElement = $element[0].nodeName; + this.$element = $element; + } verifyIAmAClass() { this.isClass = 'isClass'; } isPublished() { + return this.$element.controller('ng1') == this ? 'published' : + 'not-published'; + } + } + }; + }; + ng1Module.directive('ng1', ng1); - it('should support array require in linking fn', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); - - const parent = () => { return {controller: class {parent = 'PARENT';}}; }; - const ng1 = () => { - return { - scope: {title: '@'}, - bindToController: true, - template: '{{parent.parent}}:{{ng1.status}}', - require: ['ng1', '^parent', '?^^notFound'], - controllerAs: 'ctrl', - controller: class {status = 'WORKS';}, - link: function(scope: any, element: any, attrs: any, linkControllers: any) { - expect(linkControllers[0].status).toEqual('WORKS'); - expect(linkControllers[1].parent).toEqual('PARENT'); - expect(linkControllers[2]).toBe(undefined); - scope.ng1 = linkControllers[0]; - scope.parent = linkControllers[1]; + @Component({selector: 'ng2', template: ''}) + class Ng2 { } - }; - }; - ng1Module.directive('parent', parent); - ng1Module.directive('ng1', ng1); - @Component({selector: 'ng2', template: ''}) - class Ng2 { - } + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)) + .toEqual('scope; isClass; NG1; published'); + ref.dispose(); + }); + })); - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('PARENT:WORKS'); - ref.dispose(); - }); - })); + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support bindToController', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + + const ng1 = () => { + return { + scope: {title: '@'}, + bindToController: true, + template: '{{ctl.title}}', + controllerAs: 'ctl', + controller: class {} + }; + }; + ng1Module.directive('ng1', ng1); + + @Component({selector: 'ng2', template: ''}) + class Ng2 { + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('WORKS'); + ref.dispose(); + }); + })); + + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support bindToController with bindings', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + + const ng1 = () => { + return { + scope: {}, + bindToController: {title: '@'}, + template: '{{ctl.title}}', + controllerAs: 'ctl', + controller: class {} + }; + }; + ng1Module.directive('ng1', ng1); + + @Component({selector: 'ng2', template: ''}) + class Ng2 { + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('WORKS'); + ref.dispose(); + }); + })); + + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support single require in linking fn', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + + const ng1 = ($rootScope: any) => { + return { + scope: {title: '@'}, + bindToController: true, + template: '{{ctl.status}}', + require: 'ng1', + controllerAs: 'ctrl', + controller: class {status = 'WORKS';}, + link: function(scope: any, element: any, attrs: any, linkController: any) { + expect(scope.$root).toEqual($rootScope); + expect(element[0].nodeName).toEqual('NG1'); + expect(linkController.status).toEqual('WORKS'); + scope.ctl = linkController; + } + }; + }; + ng1Module.directive('ng1', ng1); + + @Component({selector: 'ng2', template: ''}) + class Ng2 { + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('WORKS'); + ref.dispose(); + }); + })); + + fixmeIvy('FW-724: upgraded ng1 components are not being rendered') && + it('should support array require in linking fn', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + + const parent = () => { return {controller: class {parent = 'PARENT';}}; }; + const ng1 = () => { + return { + scope: {title: '@'}, + bindToController: true, + template: '{{parent.parent}}:{{ng1.status}}', + require: ['ng1', '^parent', '?^^notFound'], + controllerAs: 'ctrl', + controller: class {status = 'WORKS';}, + link: function(scope: any, element: any, attrs: any, linkControllers: any) { + expect(linkControllers[0].status).toEqual('WORKS'); + expect(linkControllers[1].parent).toEqual('PARENT'); + expect(linkControllers[2]).toBe(undefined); + scope.ng1 = linkControllers[0]; + scope.parent = linkControllers[1]; + } + }; + }; + ng1Module.directive('parent', parent); + ng1Module.directive('ng1', ng1); + + @Component({selector: 'ng2', template: ''}) + class Ng2 { + } + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('PARENT:WORKS'); + ref.dispose(); + }); + })); describe('with lifecycle hooks', () => { - it('should call `$onInit()` on controller', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $onInitSpyA = jasmine.createSpy('$onInitA'); - const $onInitSpyB = jasmine.createSpy('$onInitB'); + fixmeIvy('unknown') && + it('should call `$onInit()` on controller', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $onInitSpyA = jasmine.createSpy('$onInitA'); + const $onInitSpyB = jasmine.createSpy('$onInitB'); - @Component({selector: 'ng2', template: ' | '}) - class Ng2Component { - } + @Component({selector: 'ng2', template: ' | '}) + class Ng2Component { + } - angular.module('ng1', []) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: class {$onInit() { $onInitSpyA(); }} - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function() { this.$onInit = $onInitSpyB; } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + angular.module('ng1', []) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: class {$onInit() { $onInitSpyA(); }} + })) + .directive('ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function() { this.$onInit = $onInitSpyB; } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect($onInitSpyA).toHaveBeenCalled(); - expect($onInitSpyB).toHaveBeenCalled(); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect($onInitSpyA).toHaveBeenCalled(); + expect($onInitSpyB).toHaveBeenCalled(); - ref.dispose(); - }); - })); - - it('should not call `$onInit()` on scope', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $onInitSpy = jasmine.createSpy('$onInit'); - - @Component({selector: 'ng2', template: ' | '}) - class Ng2Component { - } - - angular.module('ng1', []) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - Object.getPrototypeOf($scope).$onInit = $onInitSpy; - } - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - $scope['$onInit'] = $onInitSpy; - } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } - - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect($onInitSpy).not.toHaveBeenCalled(); - ref.dispose(); - }); - })); - - it('should call `$doCheck()` on controller', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $doCheckSpyA = jasmine.createSpy('$doCheckA'); - const $doCheckSpyB = jasmine.createSpy('$doCheckB'); - let changeDetector: ChangeDetectorRef; - - @Component({selector: 'ng2', template: ' | '}) - class Ng2Component { - constructor(cd: ChangeDetectorRef) { changeDetector = cd; } - } - - angular.module('ng1', []) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: class {$doCheck() { $doCheckSpyA(); }} - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function() { this.$doCheck = $doCheckSpyB; } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } - - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect($doCheckSpyA).toHaveBeenCalled(); - expect($doCheckSpyB).toHaveBeenCalled(); - - $doCheckSpyA.calls.reset(); - $doCheckSpyB.calls.reset(); - changeDetector.detectChanges(); - - expect($doCheckSpyA).toHaveBeenCalled(); - expect($doCheckSpyB).toHaveBeenCalled(); - - ref.dispose(); - }); - })); - - it('should not call `$doCheck()` on scope', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $doCheckSpyA = jasmine.createSpy('$doCheckA'); - const $doCheckSpyB = jasmine.createSpy('$doCheckB'); - let changeDetector: ChangeDetectorRef; - - @Component({selector: 'ng2', template: ' | '}) - class Ng2Component { - constructor(cd: ChangeDetectorRef) { changeDetector = cd; } - } - - angular.module('ng1', []) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - Object.getPrototypeOf($scope).$doCheck = $doCheckSpyA; - } - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - $scope['$doCheck'] = $doCheckSpyB; - } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } - - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - $doCheckSpyA.calls.reset(); - $doCheckSpyB.calls.reset(); - changeDetector.detectChanges(); - - expect($doCheckSpyA).not.toHaveBeenCalled(); - expect($doCheckSpyB).not.toHaveBeenCalled(); - - ref.dispose(); - }); - })); - - it('should call `$postLink()` on controller', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $postLinkSpyA = jasmine.createSpy('$postLinkA'); - const $postLinkSpyB = jasmine.createSpy('$postLinkB'); - - @Component({selector: 'ng2', template: ' | '}) - class Ng2Component { - } - - angular.module('ng1', []) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: class {$postLink() { $postLinkSpyA(); }} - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function() { this.$postLink = $postLinkSpyB; } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } - - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect($postLinkSpyA).toHaveBeenCalled(); - expect($postLinkSpyB).toHaveBeenCalled(); - - ref.dispose(); - }); - })); - - it('should not call `$postLink()` on scope', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $postLinkSpy = jasmine.createSpy('$postLink'); - - @Component({selector: 'ng2', template: ' | '}) - class Ng2Component { - } - - angular.module('ng1', []) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - Object.getPrototypeOf($scope).$postLink = $postLinkSpy; - } - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - $scope['$postLink'] = $postLinkSpy; - } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } - - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect($postLinkSpy).not.toHaveBeenCalled(); - ref.dispose(); - }); - })); - - it('should call `$onChanges()` on binding destination', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $onChangesControllerSpyA = jasmine.createSpy('$onChangesControllerA'); - const $onChangesControllerSpyB = jasmine.createSpy('$onChangesControllerB'); - const $onChangesScopeSpy = jasmine.createSpy('$onChangesScope'); - let ng2Instance: any; - - @Component({ - selector: 'ng2', - template: ' | ' - }) - class Ng2Component { - constructor() { ng2Instance = this; } - } - - angular.module('ng1', []) - .directive('ng1A', () => ({ - template: '', - scope: {valA: '<'}, - bindToController: true, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - this.$onChanges = $onChangesControllerSpyA; - } - })) - .directive( - 'ng1B', - () => ({ - template: '', - scope: {valB: '<'}, - bindToController: false, - controllerAs: '$ctrl', - controller: class { - $onChanges(changes: SimpleChanges) { $onChangesControllerSpyB(changes); } - } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)) - .run(($rootScope: angular.IRootScopeService) => { - Object.getPrototypeOf($rootScope).$onChanges = $onChangesScopeSpy; + ref.dispose(); }); + })); + + fixmeIvy('unknown') && + it('should not call `$onInit()` on scope', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $onInitSpy = jasmine.createSpy('$onInit'); + + @Component({selector: 'ng2', template: ' | '}) + class Ng2Component { + } + + angular.module('ng1', []) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + Object.getPrototypeOf($scope).$onInit = $onInitSpy; + } + })) + .directive('ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + $scope['$onInit'] = $onInitSpy; + } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } + + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect($onInitSpy).not.toHaveBeenCalled(); + ref.dispose(); + }); + })); + + fixmeIvy('unknown') && + it('should call `$doCheck()` on controller', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $doCheckSpyA = jasmine.createSpy('$doCheckA'); + const $doCheckSpyB = jasmine.createSpy('$doCheckB'); + let changeDetector: ChangeDetectorRef; + + @Component({selector: 'ng2', template: ' | '}) + class Ng2Component { + constructor(cd: ChangeDetectorRef) { changeDetector = cd; } + } + + angular.module('ng1', []) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: class {$doCheck() { $doCheckSpyA(); }} + })) + .directive('ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function() { this.$doCheck = $doCheckSpyB; } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } + + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect($doCheckSpyA).toHaveBeenCalled(); + expect($doCheckSpyB).toHaveBeenCalled(); + + $doCheckSpyA.calls.reset(); + $doCheckSpyB.calls.reset(); + changeDetector.detectChanges(); + + expect($doCheckSpyA).toHaveBeenCalled(); + expect($doCheckSpyB).toHaveBeenCalled(); + + ref.dispose(); + }); + })); + + fixmeIvy('unknown') && + it('should not call `$doCheck()` on scope', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $doCheckSpyA = jasmine.createSpy('$doCheckA'); + const $doCheckSpyB = jasmine.createSpy('$doCheckB'); + let changeDetector: ChangeDetectorRef; + + @Component({selector: 'ng2', template: ' | '}) + class Ng2Component { + constructor(cd: ChangeDetectorRef) { changeDetector = cd; } + } + + angular.module('ng1', []) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + Object.getPrototypeOf($scope).$doCheck = $doCheckSpyA; + } + })) + .directive('ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + $scope['$doCheck'] = $doCheckSpyB; + } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } + + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + $doCheckSpyA.calls.reset(); + $doCheckSpyB.calls.reset(); + changeDetector.detectChanges(); + + expect($doCheckSpyA).not.toHaveBeenCalled(); + expect($doCheckSpyB).not.toHaveBeenCalled(); + + ref.dispose(); + }); + })); + + fixmeIvy('unknown') && + it('should call `$postLink()` on controller', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $postLinkSpyA = jasmine.createSpy('$postLinkA'); + const $postLinkSpyB = jasmine.createSpy('$postLinkB'); + + @Component({selector: 'ng2', template: ' | '}) + class Ng2Component { + } + + angular.module('ng1', []) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: class {$postLink() { $postLinkSpyA(); }} + })) + .directive('ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function() { this.$postLink = $postLinkSpyB; } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } + + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect($postLinkSpyA).toHaveBeenCalled(); + expect($postLinkSpyB).toHaveBeenCalled(); + + ref.dispose(); + }); + })); + + fixmeIvy('unknown') && + it('should not call `$postLink()` on scope', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $postLinkSpy = jasmine.createSpy('$postLink'); + + @Component({selector: 'ng2', template: ' | '}) + class Ng2Component { + } + + angular.module('ng1', []) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + Object.getPrototypeOf($scope).$postLink = $postLinkSpy; + } + })) + .directive('ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + $scope['$postLink'] = $postLinkSpy; + } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } + + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect($postLinkSpy).not.toHaveBeenCalled(); + ref.dispose(); + }); + })); + + fixmeIvy('unknown') && + it('should call `$onChanges()` on binding destination', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $onChangesControllerSpyA = jasmine.createSpy('$onChangesControllerA'); + const $onChangesControllerSpyB = jasmine.createSpy('$onChangesControllerB'); + const $onChangesScopeSpy = jasmine.createSpy('$onChangesScope'); + let ng2Instance: any; + + @Component({ + selector: 'ng2', + template: ' | ' + }) + class Ng2Component { + constructor() { ng2Instance = this; } + } + + angular.module('ng1', []) + .directive('ng1A', () => ({ + template: '', + scope: {valA: '<'}, + bindToController: true, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + this.$onChanges = $onChangesControllerSpyA; + } + })) + .directive('ng1B', () => ({ + template: '', + scope: {valB: '<'}, + bindToController: false, + controllerAs: '$ctrl', + controller: class { + $onChanges(changes: SimpleChanges) { + $onChangesControllerSpyB(changes); + } + } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)) + .run(($rootScope: angular.IRootScopeService) => { + Object.getPrototypeOf($rootScope).$onChanges = $onChangesScopeSpy; + }); - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - // Initial `$onChanges()` call - tick(); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + // Initial `$onChanges()` call + tick(); - expect($onChangesControllerSpyA.calls.count()).toBe(1); - expect($onChangesControllerSpyA.calls.argsFor(0)[0]).toEqual({ - valA: jasmine.any(SimpleChange) - }); + expect($onChangesControllerSpyA.calls.count()).toBe(1); + expect($onChangesControllerSpyA.calls.argsFor(0)[0]).toEqual({ + valA: jasmine.any(SimpleChange) + }); - expect($onChangesControllerSpyB).not.toHaveBeenCalled(); + expect($onChangesControllerSpyB).not.toHaveBeenCalled(); - expect($onChangesScopeSpy.calls.count()).toBe(1); - expect($onChangesScopeSpy.calls.argsFor(0)[0]).toEqual({ - valB: jasmine.any(SimpleChange) - }); + expect($onChangesScopeSpy.calls.count()).toBe(1); + expect($onChangesScopeSpy.calls.argsFor(0)[0]).toEqual({ + valB: jasmine.any(SimpleChange) + }); - $onChangesControllerSpyA.calls.reset(); - $onChangesControllerSpyB.calls.reset(); - $onChangesScopeSpy.calls.reset(); + $onChangesControllerSpyA.calls.reset(); + $onChangesControllerSpyB.calls.reset(); + $onChangesScopeSpy.calls.reset(); - // `$onChanges()` call after a change - ng2Instance.val = 'new value'; - tick(); - ref.ng1RootScope.$digest(); + // `$onChanges()` call after a change + ng2Instance.val = 'new value'; + tick(); + ref.ng1RootScope.$digest(); - expect($onChangesControllerSpyA.calls.count()).toBe(1); - expect($onChangesControllerSpyA.calls.argsFor(0)[0]).toEqual({ - valA: jasmine.objectContaining({currentValue: 'new value'}) - }); + expect($onChangesControllerSpyA.calls.count()).toBe(1); + expect($onChangesControllerSpyA.calls.argsFor(0)[0]).toEqual({ + valA: jasmine.objectContaining({currentValue: 'new value'}) + }); - expect($onChangesControllerSpyB).not.toHaveBeenCalled(); + expect($onChangesControllerSpyB).not.toHaveBeenCalled(); - expect($onChangesScopeSpy.calls.count()).toBe(1); - expect($onChangesScopeSpy.calls.argsFor(0)[0]).toEqual({ - valB: jasmine.objectContaining({currentValue: 'new value'}) - }); + expect($onChangesScopeSpy.calls.count()).toBe(1); + expect($onChangesScopeSpy.calls.argsFor(0)[0]).toEqual({ + valB: jasmine.objectContaining({currentValue: 'new value'}) + }); - ref.dispose(); - }); - })); + ref.dispose(); + }); + })); - it('should call `$onDestroy()` on controller', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $onDestroySpyA = jasmine.createSpy('$onDestroyA'); - const $onDestroySpyB = jasmine.createSpy('$onDestroyB'); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('unknown') && + it('should call `$onDestroy()` on controller', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $onDestroySpyA = jasmine.createSpy('$onDestroyA'); + const $onDestroySpyB = jasmine.createSpy('$onDestroyB'); + let ng2ComponentInstance: Ng2Component; - @Component({ - selector: 'ng2', - template: ` + @Component({ + selector: 'ng2', + template: `
|
` - }) - class Ng2Component { - ng2Destroy: boolean = false; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + ng2Destroy: boolean = false; + constructor() { ng2ComponentInstance = this; } + } - // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), - // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be - // on - // the queue at the end of the test, causing it to fail. - // Mocking animations (via `ngAnimateMock`) avoids the issue. - angular.module('ng1', ['ngAnimateMock']) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: class {$onDestroy() { $onDestroySpyA(); }} - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function() { this.$onDestroy = $onDestroySpyB; } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), + // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be + // on + // the queue at the end of the test, causing it to fail. + // Mocking animations (via `ngAnimateMock`) avoids the issue. + angular.module('ng1', ['ngAnimateMock']) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: class {$onDestroy() { $onDestroySpyA(); }} + })) + .directive( + 'ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function() { this.$onDestroy = $onDestroySpyB; } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - const $rootScope = ref.ng1RootScope as any; + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + const $rootScope = ref.ng1RootScope as any; - $rootScope.ng1Destroy = false; - tick(); - $rootScope.$digest(); + $rootScope.ng1Destroy = false; + tick(); + $rootScope.$digest(); - expect($onDestroySpyA).not.toHaveBeenCalled(); - expect($onDestroySpyB).not.toHaveBeenCalled(); + expect($onDestroySpyA).not.toHaveBeenCalled(); + expect($onDestroySpyB).not.toHaveBeenCalled(); - $rootScope.ng1Destroy = true; - tick(); - $rootScope.$digest(); + $rootScope.ng1Destroy = true; + tick(); + $rootScope.$digest(); - expect($onDestroySpyA).toHaveBeenCalled(); - expect($onDestroySpyB).toHaveBeenCalled(); + expect($onDestroySpyA).toHaveBeenCalled(); + expect($onDestroySpyB).toHaveBeenCalled(); - $onDestroySpyA.calls.reset(); - $onDestroySpyB.calls.reset(); + $onDestroySpyA.calls.reset(); + $onDestroySpyB.calls.reset(); - $rootScope.ng1Destroy = false; - tick(); - $rootScope.$digest(); + $rootScope.ng1Destroy = false; + tick(); + $rootScope.$digest(); - expect($onDestroySpyA).not.toHaveBeenCalled(); - expect($onDestroySpyB).not.toHaveBeenCalled(); + expect($onDestroySpyA).not.toHaveBeenCalled(); + expect($onDestroySpyB).not.toHaveBeenCalled(); - ng2ComponentInstance.ng2Destroy = true; - tick(); - $rootScope.$digest(); + ng2ComponentInstance.ng2Destroy = true; + tick(); + $rootScope.$digest(); - expect($onDestroySpyA).toHaveBeenCalled(); - expect($onDestroySpyB).toHaveBeenCalled(); + expect($onDestroySpyA).toHaveBeenCalled(); + expect($onDestroySpyB).toHaveBeenCalled(); - ref.dispose(); - }); - })); + ref.dispose(); + }); + })); - it('should not call `$onDestroy()` on scope', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const $onDestroySpy = jasmine.createSpy('$onDestroy'); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('unknown') && + it('should not call `$onDestroy()` on scope', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const $onDestroySpy = jasmine.createSpy('$onDestroy'); + let ng2ComponentInstance: Ng2Component; - @Component({ - selector: 'ng2', - template: ` + @Component({ + selector: 'ng2', + template: `
|
` - }) - class Ng2Component { - ng2Destroy: boolean = false; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + ng2Destroy: boolean = false; + constructor() { ng2ComponentInstance = this; } + } - // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), - // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be - // on - // the queue at the end of the test, causing it to fail. - // Mocking animations (via `ngAnimateMock`) avoids the issue. - angular.module('ng1', ['ngAnimateMock']) - .directive('ng1A', () => ({ - template: '', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - Object.getPrototypeOf($scope).$onDestroy = $onDestroySpy; - } - })) - .directive('ng1B', () => ({ - template: '', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: function($scope: angular.IScope) { - $scope['$onDestroy'] = $onDestroySpy; - } - })) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), + // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be + // on + // the queue at the end of the test, causing it to fail. + // Mocking animations (via `ngAnimateMock`) avoids the issue. + angular.module('ng1', ['ngAnimateMock']) + .directive('ng1A', () => ({ + template: '', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + Object.getPrototypeOf($scope).$onDestroy = + $onDestroySpy; + } + })) + .directive('ng1B', () => ({ + template: '', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: function($scope: angular.IScope) { + $scope['$onDestroy'] = $onDestroySpy; + } + })) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - @NgModule({ - declarations: [ - adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), - Ng2Component - ], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [ + adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), + Ng2Component + ], + imports: [BrowserModule], + }) + class Ng2Module { + } - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - const $rootScope = ref.ng1RootScope as any; + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + const $rootScope = ref.ng1RootScope as any; - $rootScope.ng1Destroy = false; - tick(); - $rootScope.$digest(); + $rootScope.ng1Destroy = false; + tick(); + $rootScope.$digest(); - $rootScope.ng1Destroy = true; - tick(); - $rootScope.$digest(); + $rootScope.ng1Destroy = true; + tick(); + $rootScope.$digest(); - $rootScope.ng1Destroy = false; - tick(); - $rootScope.$digest(); + $rootScope.ng1Destroy = false; + tick(); + $rootScope.$digest(); - ng2ComponentInstance.ng2Destroy = true; - tick(); - $rootScope.$digest(); + ng2ComponentInstance.ng2Destroy = true; + tick(); + $rootScope.$digest(); - expect($onDestroySpy).not.toHaveBeenCalled(); + expect($onDestroySpy).not.toHaveBeenCalled(); - ref.dispose(); - }); - })); + ref.dispose(); + }); + })); }); describe('destroying the upgraded component', () => { - it('should destroy `componentScope`', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const scopeDestroyListener = jasmine.createSpy('scopeDestroyListener'); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should destroy `componentScope`', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const scopeDestroyListener = jasmine.createSpy('scopeDestroyListener'); + let ng2ComponentInstance: Ng2Component; - @Component({selector: 'ng2', template: '
'}) - class Ng2Component { - ng2Destroy: boolean = false; - constructor() { ng2ComponentInstance = this; } - } + @Component( + {selector: 'ng2', template: '
'}) + class Ng2Component { + ng2Destroy: boolean = false; + constructor() { ng2ComponentInstance = this; } + } - // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), - // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be on - // the queue at the end of the test, causing it to fail. - // Mocking animations (via `ngAnimateMock`) avoids the issue. - angular.module('ng1', ['ngAnimateMock']) - .component('ng1', { - controller: function($scope: angular.IScope) { - $scope.$on('$destroy', scopeDestroyListener); - }, + // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), + // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be + // on + // the queue at the end of the test, causing it to fail. + // Mocking animations (via `ngAnimateMock`) avoids the issue. + angular.module('ng1', ['ngAnimateMock']) + .component('ng1', { + controller: function($scope: angular.IScope) { + $scope.$on('$destroy', scopeDestroyListener); + }, + }) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + imports: [BrowserModule], }) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + class Ng2Module { + } - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - imports: [BrowserModule], - }) - class Ng2Module { - } + const element = html(''); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + const $rootScope = ref.ng1RootScope as any; - const element = html(''); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - const $rootScope = ref.ng1RootScope as any; + expect(scopeDestroyListener).not.toHaveBeenCalled(); - expect(scopeDestroyListener).not.toHaveBeenCalled(); + ng2ComponentInstance.ng2Destroy = true; + tick(); + $rootScope.$digest(); - ng2ComponentInstance.ng2Destroy = true; - tick(); - $rootScope.$digest(); + expect(scopeDestroyListener).toHaveBeenCalledTimes(1); + }); + })); - expect(scopeDestroyListener).toHaveBeenCalledTimes(1); - }); - })); + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should emit `$destroy` on `$element` and descendants', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const elementDestroyListener = jasmine.createSpy('elementDestroyListener'); + const descendantDestroyListener = jasmine.createSpy('descendantDestroyListener'); + let ng2ComponentInstance: Ng2Component; - it('should emit `$destroy` on `$element` and descendants', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const elementDestroyListener = jasmine.createSpy('elementDestroyListener'); - const descendantDestroyListener = jasmine.createSpy('descendantDestroyListener'); - let ng2ComponentInstance: Ng2Component; + @Component( + {selector: 'ng2', template: '
'}) + class Ng2Component { + ng2Destroy: boolean = false; + constructor() { ng2ComponentInstance = this; } + } - @Component({selector: 'ng2', template: '
'}) - class Ng2Component { - ng2Destroy: boolean = false; - constructor() { ng2ComponentInstance = this; } - } + // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), + // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be + // on + // the queue at the end of the test, causing it to fail. + // Mocking animations (via `ngAnimateMock`) avoids the issue. + angular.module('ng1', ['ngAnimateMock']) + .component('ng1', { + controller: class { + constructor(private $element: angular.IAugmentedJQuery) {} $onInit() { + this.$element.on !('$destroy', elementDestroyListener); + this.$element.contents !().on !('$destroy', descendantDestroyListener); + } + }, + template: '
' + }) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3), - // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be on - // the queue at the end of the test, causing it to fail. - // Mocking animations (via `ngAnimateMock`) avoids the issue. - angular.module('ng1', ['ngAnimateMock']) - .component('ng1', { + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + imports: [BrowserModule], + }) + class Ng2Module { + } + + const element = html(''); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + const $rootScope = ref.ng1RootScope as any; + tick(); + $rootScope.$digest(); + + expect(elementDestroyListener).not.toHaveBeenCalled(); + expect(descendantDestroyListener).not.toHaveBeenCalled(); + + ng2ComponentInstance.ng2Destroy = true; + tick(); + $rootScope.$digest(); + + expect(elementDestroyListener).toHaveBeenCalledTimes(1); + expect(descendantDestroyListener).toHaveBeenCalledTimes(1); + }); + })); + + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should clear data on `$element` and descendants', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng1ComponentElement: angular.IAugmentedJQuery; + let ng2ComponentAInstance: Ng2ComponentA; + + // Define `ng1Component` + const ng1Component: angular.IComponent = { controller: class { constructor(private $element: angular.IAugmentedJQuery) {} $onInit() { - this.$element.on !('$destroy', elementDestroyListener); - this.$element.contents !().on !('$destroy', descendantDestroyListener); + this.$element.data !('test', 1); + this.$element.contents !().data !('test', 2); + + ng1ComponentElement = this.$element; } }, template: '
' + }; + + // Define `Ng2Component` + @Component({selector: 'ng2A', template: ''}) + class Ng2ComponentA { + destroyIt = false; + + constructor() { ng2ComponentAInstance = this; } + } + + @Component({selector: 'ng2B', template: ''}) + class Ng2ComponentB { + } + + // Define `ng1Module` + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA)); + + // Define `Ng2Module` + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB], + entryComponents: [Ng2ComponentA], + imports: [BrowserModule] }) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - imports: [BrowserModule], - }) - class Ng2Module { - } - - const element = html(''); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - const $rootScope = ref.ng1RootScope as any; - tick(); - $rootScope.$digest(); - - expect(elementDestroyListener).not.toHaveBeenCalled(); - expect(descendantDestroyListener).not.toHaveBeenCalled(); - - ng2ComponentInstance.ng2Destroy = true; - tick(); - $rootScope.$digest(); - - expect(elementDestroyListener).toHaveBeenCalledTimes(1); - expect(descendantDestroyListener).toHaveBeenCalledTimes(1); - }); - })); - - it('should clear data on `$element` and descendants', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng1ComponentElement: angular.IAugmentedJQuery; - let ng2ComponentAInstance: Ng2ComponentA; - - // Define `ng1Component` - const ng1Component: angular.IComponent = { - controller: class { - constructor(private $element: angular.IAugmentedJQuery) {} $onInit() { - this.$element.data !('test', 1); - this.$element.contents !().data !('test', 2); - - ng1ComponentElement = this.$element; + class Ng2Module { + ngDoBootstrap() {} } - }, - template: '
' - }; - // Define `Ng2Component` - @Component({selector: 'ng2A', template: ''}) - class Ng2ComponentA { - destroyIt = false; + // Bootstrap + const element = html(``); - constructor() { ng2ComponentAInstance = this; } - } + adapter.bootstrap(element, ['ng1Module']).ready((ref) => { + const $rootScope = ref.ng1RootScope as any; + tick(); + $rootScope.$digest(); + expect(ng1ComponentElement.data !('test')).toBe(1); + expect(ng1ComponentElement.contents !().data !('test')).toBe(2); - @Component({selector: 'ng2B', template: ''}) - class Ng2ComponentB { - } + ng2ComponentAInstance.destroyIt = true; + tick(); + $rootScope.$digest(); - // Define `ng1Module` - angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA)); + expect(ng1ComponentElement.data !('test')).toBeUndefined(); + expect(ng1ComponentElement.contents !().data !('test')).toBeUndefined(); + }); + })); - // Define `Ng2Module` - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB], - entryComponents: [Ng2ComponentA], - imports: [BrowserModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should clear dom listeners on `$element` and descendants`', fakeAsync(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const elementClickListener = jasmine.createSpy('elementClickListener'); + const descendantClickListener = jasmine.createSpy('descendantClickListener'); + let ng1DescendantElement: angular.IAugmentedJQuery; + let ng2ComponentAInstance: Ng2ComponentA; - // Bootstrap - const element = html(``); + // Define `ng1Component` + const ng1Component: angular.IComponent = { + controller: class { + constructor(private $element: angular.IAugmentedJQuery) {} $onInit() { + ng1DescendantElement = this.$element.contents !(); - adapter.bootstrap(element, ['ng1Module']).ready((ref) => { - const $rootScope = ref.ng1RootScope as any; - tick(); - $rootScope.$digest(); - expect(ng1ComponentElement.data !('test')).toBe(1); - expect(ng1ComponentElement.contents !().data !('test')).toBe(2); + this.$element.on !('click', elementClickListener); + ng1DescendantElement.on !('click', descendantClickListener); + } + }, + template: '
' + }; - ng2ComponentAInstance.destroyIt = true; - tick(); - $rootScope.$digest(); + // Define `Ng2Component` + @Component({selector: 'ng2A', template: ''}) + class Ng2ComponentA { + destroyIt = false; - expect(ng1ComponentElement.data !('test')).toBeUndefined(); - expect(ng1ComponentElement.contents !().data !('test')).toBeUndefined(); - }); - })); - - it('should clear dom listeners on `$element` and descendants`', fakeAsync(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const elementClickListener = jasmine.createSpy('elementClickListener'); - const descendantClickListener = jasmine.createSpy('descendantClickListener'); - let ng1DescendantElement: angular.IAugmentedJQuery; - let ng2ComponentAInstance: Ng2ComponentA; - - // Define `ng1Component` - const ng1Component: angular.IComponent = { - controller: class { - constructor(private $element: angular.IAugmentedJQuery) {} $onInit() { - ng1DescendantElement = this.$element.contents !(); - - this.$element.on !('click', elementClickListener); - ng1DescendantElement.on !('click', descendantClickListener); + constructor() { ng2ComponentAInstance = this; } } - }, - template: '
' - }; - // Define `Ng2Component` - @Component({selector: 'ng2A', template: ''}) - class Ng2ComponentA { - destroyIt = false; + @Component({selector: 'ng2B', template: ''}) + class Ng2ComponentB { + } - constructor() { ng2ComponentAInstance = this; } - } + // Define `ng1Module` + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA)); - @Component({selector: 'ng2B', template: ''}) - class Ng2ComponentB { - } + // Define `Ng2Module` + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB], + entryComponents: [Ng2ComponentA], + imports: [BrowserModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - // Define `ng1Module` - angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA)); + // Bootstrap + const element = html(``); - // Define `Ng2Module` - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB], - entryComponents: [Ng2ComponentA], - imports: [BrowserModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + adapter.bootstrap(element, ['ng1Module']).ready((ref) => { + const $rootScope = ref.ng1RootScope as any; + tick(); + $rootScope.$digest(); + (ng1DescendantElement[0] as HTMLElement).click(); + expect(elementClickListener).toHaveBeenCalledTimes(1); + expect(descendantClickListener).toHaveBeenCalledTimes(1); - // Bootstrap - const element = html(``); + ng2ComponentAInstance.destroyIt = true; + tick(); + $rootScope.$digest(); - adapter.bootstrap(element, ['ng1Module']).ready((ref) => { - const $rootScope = ref.ng1RootScope as any; - tick(); - $rootScope.$digest(); - (ng1DescendantElement[0] as HTMLElement).click(); - expect(elementClickListener).toHaveBeenCalledTimes(1); - expect(descendantClickListener).toHaveBeenCalledTimes(1); - - ng2ComponentAInstance.destroyIt = true; - tick(); - $rootScope.$digest(); - - (ng1DescendantElement[0] as HTMLElement).click(); - expect(elementClickListener).toHaveBeenCalledTimes(1); - expect(descendantClickListener).toHaveBeenCalledTimes(1); - }); - })); + (ng1DescendantElement[0] as HTMLElement).click(); + expect(elementClickListener).toHaveBeenCalledTimes(1); + expect(descendantClickListener).toHaveBeenCalledTimes(1); + }); + })); }); describe('linking', () => { - it('should run the pre-linking after instantiating the controller', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const log: string[] = []; + fixmeIvy('unknown') && + it('should run the pre-linking after instantiating the controller', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const log: string[] = []; - // Define `ng1Directive` - const ng1Directive: angular.IDirective = { - template: '', - link: {pre: () => log.push('ng1-pre')}, - controller: class {constructor() { log.push('ng1-ctrl'); }} - }; + // Define `ng1Directive` + const ng1Directive: angular.IDirective = { + template: '', + link: {pre: () => log.push('ng1-pre')}, + controller: class {constructor() { log.push('ng1-ctrl'); }} + }; - // Define `Ng2Component` - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - } + // Define `Ng2Component` + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + } - // Define `ng1Module` - const ng1Module = angular.module('ng1', []) - .directive('ng1', () => ng1Directive) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1', []) + .directive('ng1', () => ng1Directive) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1']).ready(() => { - expect(log).toEqual(['ng1-ctrl', 'ng1-pre']); - }); - })); + adapter.bootstrap(element, ['ng1']).ready(() => { + setTimeout(() => expect(log).toEqual(['ng1-ctrl', 'ng1-pre']), 1000); + }); + })); - it('should run the pre-linking function before linking', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const log: string[] = []; + fixmeIvy('unknown') && + it('should run the pre-linking function before linking', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const log: string[] = []; - // Define `ng1Directive` - const ng1DirectiveA: angular.IDirective = { - template: '', - link: {pre: () => log.push('ng1A-pre')} - }; + // Define `ng1Directive` + const ng1DirectiveA: angular.IDirective = { + template: '', + link: {pre: () => log.push('ng1A-pre')} + }; - const ng1DirectiveB: angular.IDirective = {link: () => log.push('ng1B-post')}; + const ng1DirectiveB: angular.IDirective = {link: () => log.push('ng1B-post')}; - // Define `Ng2Component` - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - } + // Define `Ng2Component` + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + } - // Define `ng1Module` - const ng1Module = angular.module('ng1', []) - .directive('ng1A', () => ng1DirectiveA) - .directive('ng1B', () => ng1DirectiveB) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1', []) + .directive('ng1A', () => ng1DirectiveA) + .directive('ng1B', () => ng1DirectiveB) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1A'), Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1A'), Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1']).ready(() => { - expect(log).toEqual(['ng1A-pre', 'ng1B-post']); - }); - })); + adapter.bootstrap(element, ['ng1']).ready(() => { + expect(log).toEqual(['ng1A-pre', 'ng1B-post']); + }); + })); - it('should run the post-linking function after linking (link: object)', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const log: string[] = []; + fixmeIvy('unknown') && + it('should run the post-linking function after linking (link: object)', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const log: string[] = []; - // Define `ng1Directive` - const ng1DirectiveA: angular.IDirective = { - template: '', - link: {post: () => log.push('ng1A-post')} - }; + // Define `ng1Directive` + const ng1DirectiveA: angular.IDirective = { + template: '', + link: {post: () => log.push('ng1A-post')} + }; - const ng1DirectiveB: angular.IDirective = {link: () => log.push('ng1B-post')}; + const ng1DirectiveB: angular.IDirective = {link: () => log.push('ng1B-post')}; - // Define `Ng2Component` - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - } + // Define `Ng2Component` + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + } - // Define `ng1Module` - const ng1Module = angular.module('ng1', []) - .directive('ng1A', () => ng1DirectiveA) - .directive('ng1B', () => ng1DirectiveB) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1', []) + .directive('ng1A', () => ng1DirectiveA) + .directive('ng1B', () => ng1DirectiveB) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1A'), Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1A'), Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1']).ready(() => { - expect(log).toEqual(['ng1B-post', 'ng1A-post']); - }); - })); + adapter.bootstrap(element, ['ng1']).ready(() => { + expect(log).toEqual(['ng1B-post', 'ng1A-post']); + }); + })); - it('should run the post-linking function after linking (link: function)', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const log: string[] = []; + fixmeIvy('unknown') && + it('should run the post-linking function after linking (link: function)', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const log: string[] = []; - // Define `ng1Directive` - const ng1DirectiveA: angular.IDirective = { - template: '', - link: () => log.push('ng1A-post') - }; + // Define `ng1Directive` + const ng1DirectiveA: angular.IDirective = { + template: '', + link: () => log.push('ng1A-post') + }; - const ng1DirectiveB: angular.IDirective = {link: () => log.push('ng1B-post')}; + const ng1DirectiveB: angular.IDirective = {link: () => log.push('ng1B-post')}; - // Define `Ng2Component` - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - } + // Define `Ng2Component` + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + } - // Define `ng1Module` - const ng1Module = angular.module('ng1', []) - .directive('ng1A', () => ng1DirectiveA) - .directive('ng1B', () => ng1DirectiveB) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1', []) + .directive('ng1A', () => ng1DirectiveA) + .directive('ng1B', () => ng1DirectiveB) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1A'), Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1A'), Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1']).ready(() => { - expect(log).toEqual(['ng1B-post', 'ng1A-post']); - }); - })); + adapter.bootstrap(element, ['ng1']).ready(() => { + expect(log).toEqual(['ng1B-post', 'ng1A-post']); + }); + })); - it('should run the post-linking function before `$postLink`', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const log: string[] = []; + fixmeIvy('unknown') && + it('should run the post-linking function before `$postLink`', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const log: string[] = []; - // Define `ng1Directive` - const ng1Directive: angular.IDirective = { - template: '', - link: () => log.push('ng1-post'), - controller: class {$postLink() { log.push('ng1-$post'); }} - }; + // Define `ng1Directive` + const ng1Directive: angular.IDirective = { + template: '', + link: () => log.push('ng1-post'), + controller: class {$postLink() { log.push('ng1-$post'); }} + }; - // Define `Ng2Component` - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - } + // Define `Ng2Component` + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + } - // Define `ng1Module` - const ng1Module = angular.module('ng1', []) - .directive('ng1', () => ng1Directive) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1', []) + .directive('ng1', () => ng1Directive) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1']).ready(() => { - expect(log).toEqual(['ng1-post', 'ng1-$post']); - }); - })); + adapter.bootstrap(element, ['ng1']).ready(() => { + expect(log).toEqual(['ng1-post', 'ng1-$post']); + }); + })); }); describe('transclusion', () => { - it('should support single-slot transclusion', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng2ComponentAInstance: Ng2ComponentA; - let ng2ComponentBInstance: Ng2ComponentB; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support single-slot transclusion', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng2ComponentAInstance: Ng2ComponentA; + let ng2ComponentBInstance: Ng2ComponentB; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: 'ng1(
)', - transclude: true - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: 'ng1(
)', + transclude: true + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2A', - template: 'ng2A({{ value }} | )' - }) - class Ng2ComponentA { - value = 'foo'; - showB = false; - constructor() { ng2ComponentAInstance = this; } - } + // Define `Ng2Component` + @Component({ + selector: 'ng2A', + template: 'ng2A({{ value }} | )' + }) + class Ng2ComponentA { + value = 'foo'; + showB = false; + constructor() { ng2ComponentAInstance = this; } + } - @Component({selector: 'ng2B', template: 'ng2B({{ value }})'}) - class Ng2ComponentB { - value = 'bar'; - constructor() { ng2ComponentBInstance = this; } - } + @Component({selector: 'ng2B', template: 'ng2B({{ value }})'}) + class Ng2ComponentB { + value = 'bar'; + constructor() { ng2ComponentBInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2A', adapter.downgradeNg2Component(Ng2ComponentA)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: + [adapter.upgradeNg1Component('ng1'), Ng2ComponentA, Ng2ComponentB] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready((ref) => { - expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))'); + adapter.bootstrap(element, ['ng1Module']).ready((ref) => { + expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))'); - ng2ComponentAInstance.value = 'baz'; - ng2ComponentAInstance.showB = true; - $digest(ref); + ng2ComponentAInstance.value = 'baz'; + ng2ComponentAInstance.showB = true; + $digest(ref); - expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(bar)))'); + expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(bar)))'); - ng2ComponentBInstance.value = 'qux'; - $digest(ref); + ng2ComponentBInstance.value = 'qux'; + $digest(ref); - expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(qux)))'); - }); - })); + expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(qux)))'); + }); + })); - it('should support single-slot transclusion with fallback content', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng1ControllerInstances: any[] = []; - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support single-slot transclusion with fallback content', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng1ControllerInstances: any[] = []; + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: 'ng1(
{{ $ctrl.value }}
)', - transclude: true, - controller: class { - value = 'from-ng1'; constructor() { ng1ControllerInstances.push(this); } - } - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: 'ng1(
{{ $ctrl.value }}
)', + transclude: true, + controller: class { + value = 'from-ng1'; constructor() { ng1ControllerInstances.push(this); } + } + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` ng2(
{{ value }}
| @@ -2648,56 +2738,58 @@ withEachNg1Version(() => { )` - }) - class Ng2Component { - value = 'from-ng2'; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + value = 'from-ng2'; + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - expect(multiTrim(element.textContent, true)) - .toBe('ng2(ng1(from-ng2)|ng1(from-ng2)|ng1(from-ng1))'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(from-ng2)|ng1(from-ng2)|ng1(from-ng1))'); - ng1ControllerInstances.forEach(ctrl => ctrl.value = 'ng1-foo'); - ng2ComponentInstance.value = 'ng2-bar'; - $digest(ref); + ng1ControllerInstances.forEach(ctrl => ctrl.value = 'ng1-foo'); + ng2ComponentInstance.value = 'ng2-bar'; + $digest(ref); - expect(multiTrim(element.textContent, true)) - .toBe('ng2(ng1(ng2-bar)|ng1(ng2-bar)|ng1(ng1-foo))'); - }); - })); + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(ng2-bar)|ng1(ng2-bar)|ng1(ng1-foo))'); + }); + })); - it('should support multi-slot transclusion', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support multi-slot transclusion', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: - 'ng1(x(
) | y(
))', - transclude: {slotX: 'contentX', slotY: 'contentY'} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: + 'ng1(x(
) | y(
))', + transclude: {slotX: 'contentX', slotY: 'contentY'} + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` ng2( {{ x }}1 @@ -2706,60 +2798,62 @@ withEachNg1Version(() => { {{ y }}2 )` - }) - class Ng2Component { - x = 'foo'; - y = 'bar'; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + x = 'foo'; + y = 'bar'; + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - expect(multiTrim(element.textContent, true)) - .toBe('ng2(ng1(x(foo1foo2)|y(bar1bar2)))'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(foo1foo2)|y(bar1bar2)))'); - ng2ComponentInstance.x = 'baz'; - ng2ComponentInstance.y = 'qux'; - $digest(ref); + ng2ComponentInstance.x = 'baz'; + ng2ComponentInstance.y = 'qux'; + $digest(ref); - expect(multiTrim(element.textContent, true)) - .toBe('ng2(ng1(x(baz1baz2)|y(qux1qux2)))'); - }); - })); + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(baz1baz2)|y(qux1qux2)))'); + }); + })); - it('should support default slot (with fallback content)', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng1ControllerInstances: any[] = []; - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support default slot (with fallback content)', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng1ControllerInstances: any[] = []; + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: 'ng1(default(
fallback-{{ $ctrl.value }}
))', - transclude: {slotX: 'contentX', slotY: 'contentY'}, - controller: - class {value = 'ng1'; constructor() { ng1ControllerInstances.push(this); }} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: 'ng1(default(
fallback-{{ $ctrl.value }}
))', + transclude: {slotX: 'contentX', slotY: 'contentY'}, + controller: + class {value = 'ng1'; constructor() { ng1ControllerInstances.push(this); }} + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` ng2( ({{ x }}) @@ -2780,167 +2874,172 @@ withEachNg1Version(() => { --> {{ x }}ignored x{{ y + x }}ignored y{{ y }} )` - }) - class Ng2Component { - x = 'foo'; - y = 'bar'; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + x = 'foo'; + y = 'bar'; + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - expect(multiTrim(element.textContent, true)) - .toBe( - 'ng2(ng1(default((foo)foo-bar(bar)))|ng1(default(fallback-ng1))|ng1(default(foobarfoobar)))'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + expect(multiTrim(element.textContent, true)) + .toBe( + 'ng2(ng1(default((foo)foo-bar(bar)))|ng1(default(fallback-ng1))|ng1(default(foobarfoobar)))'); - ng1ControllerInstances.forEach(ctrl => ctrl.value = 'ng1-plus'); - ng2ComponentInstance.x = 'baz'; - ng2ComponentInstance.y = 'qux'; - $digest(ref); + ng1ControllerInstances.forEach(ctrl => ctrl.value = 'ng1-plus'); + ng2ComponentInstance.x = 'baz'; + ng2ComponentInstance.y = 'qux'; + $digest(ref); - expect(multiTrim(element.textContent, true)) - .toBe( - 'ng2(ng1(default((baz)baz-qux(qux)))|ng1(default(fallback-ng1-plus))|ng1(default(bazquxbazqux)))'); - }); - })); + expect(multiTrim(element.textContent, true)) + .toBe( + 'ng2(ng1(default((baz)baz-qux(qux)))|ng1(default(fallback-ng1-plus))|ng1(default(bazquxbazqux)))'); + }); + })); - it('should support optional transclusion slots (with fallback content)', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng1ControllerInstances: any[] = []; - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support optional transclusion slots (with fallback content)', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng1ControllerInstances: any[] = []; + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: ` + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: ` ng1( x(
{{ $ctrl.x }}
) | y(
{{ $ctrl.y }}
) )`, - transclude: {slotX: '?contentX', slotY: '?contentY'}, - controller: class { - x = 'ng1X'; y = 'ng1Y'; constructor() { ng1ControllerInstances.push(this); } - } - }; + transclude: {slotX: '?contentX', slotY: '?contentY'}, + controller: class { + x = 'ng1X'; y = 'ng1Y'; constructor() { ng1ControllerInstances.push(this); } + } + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` ng2( {{ x }} | {{ y }} )` - }) - class Ng2Component { - x = 'ng2X'; - y = 'ng2Y'; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + x = 'ng2X'; + y = 'ng2Y'; + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - expect(multiTrim(element.textContent, true)) - .toBe('ng2(ng1(x(ng2X)|y(ng1Y))|ng1(x(ng1X)|y(ng2Y)))'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(ng2X)|y(ng1Y))|ng1(x(ng1X)|y(ng2Y)))'); - ng1ControllerInstances.forEach(ctrl => { - ctrl.x = 'ng1X-foo'; - ctrl.y = 'ng1Y-bar'; - }); - ng2ComponentInstance.x = 'ng2X-baz'; - ng2ComponentInstance.y = 'ng2Y-qux'; - $digest(ref); + ng1ControllerInstances.forEach(ctrl => { + ctrl.x = 'ng1X-foo'; + ctrl.y = 'ng1Y-bar'; + }); + ng2ComponentInstance.x = 'ng2X-baz'; + ng2ComponentInstance.y = 'ng2Y-qux'; + $digest(ref); - expect(multiTrim(element.textContent, true)) - .toBe('ng2(ng1(x(ng2X-baz)|y(ng1Y-bar))|ng1(x(ng1X-foo)|y(ng2Y-qux)))'); - }); - })); + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(ng2X-baz)|y(ng1Y-bar))|ng1(x(ng1X-foo)|y(ng2Y-qux)))'); + }); + })); - it('should throw if a non-optional slot is not filled', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let errorMessage: string; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should throw if a non-optional slot is not filled', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let errorMessage: string; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: '', - transclude: {slotX: '?contentX', slotY: 'contentY'} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: '', + transclude: {slotX: '?contentX', slotY: 'contentY'} + }; - // Define `Ng2Component` - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - } + // Define `Ng2Component` + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + } - // Define `ng1Module` - const ng1Module = - angular.module('ng1Module', []) - .value($EXCEPTION_HANDLER, (error: Error) => errorMessage = error.message) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .value($EXCEPTION_HANDLER, (error: Error) => errorMessage = error.message) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - expect(errorMessage) - .toContain('Required transclusion slot \'slotY\' on directive: ng1'); - }); - })); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + expect(errorMessage) + .toContain('Required transclusion slot \'slotY\' on directive: ng1'); + }); + })); - it('should support structural directives in transcluded content', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - let ng2ComponentInstance: Ng2Component; + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support structural directives in transcluded content', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: - 'ng1(x(
) | default(
))', - transclude: {slotX: 'contentX'} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: + 'ng1(x(
) | default(
))', + transclude: {slotX: 'contentX'} + }; - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` ng2(
{{ x }}1
@@ -2949,112 +3048,119 @@ withEachNg1Version(() => {
{{ y }}2
)` - }) - class Ng2Component { - x = 'foo'; - y = 'bar'; - show = true; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + x = 'foo'; + y = 'bar'; + show = true; + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule], - declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule], + declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - adapter.bootstrap(element, ['ng1Module']).ready(ref => { - expect(multiTrim(element.textContent, true)).toBe('ng2(ng1(x(foo1)|default(bar2)))'); + adapter.bootstrap(element, ['ng1Module']).ready(ref => { + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(foo1)|default(bar2)))'); - ng2ComponentInstance.x = 'baz'; - ng2ComponentInstance.y = 'qux'; - ng2ComponentInstance.show = false; - $digest(ref); + ng2ComponentInstance.x = 'baz'; + ng2ComponentInstance.y = 'qux'; + ng2ComponentInstance.show = false; + $digest(ref); - expect(multiTrim(element.textContent, true)).toBe('ng2(ng1(x(baz2)|default(qux1)))'); + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(baz2)|default(qux1)))'); - ng2ComponentInstance.show = true; - $digest(ref); + ng2ComponentInstance.show = true; + $digest(ref); - expect(multiTrim(element.textContent, true)).toBe('ng2(ng1(x(baz1)|default(qux2)))'); - }); - })); + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(baz1)|default(qux2)))'); + }); + })); }); - it('should bind input properties (<) of components', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); + fixmeIvy('unknown') && + it('should bind input properties (<) of components', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); - const ng1 = { - bindings: {personProfile: '<'}, - template: 'Hello {{$ctrl.personProfile.firstName}} {{$ctrl.personProfile.lastName}}', - controller: class {} - }; - ng1Module.component('ng1', ng1); + const ng1 = { + bindings: {personProfile: '<'}, + template: + 'Hello {{$ctrl.personProfile.firstName}} {{$ctrl.personProfile.lastName}}', + controller: class {} + }; + ng1Module.component('ng1', ng1); - @Component({selector: 'ng2', template: ''}) - class Ng2 { - goku = {firstName: 'GOKU', lastName: 'SAN'}; - } + @Component({selector: 'ng2', template: ''}) + class Ng2 { + goku = {firstName: 'GOKU', lastName: 'SAN'}; + } - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } - ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual(`Hello GOKU SAN`); - ref.dispose(); - }); - })); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual(`Hello GOKU SAN`); + ref.dispose(); + }); + })); - it('should support ng2 > ng1 > ng2', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const ng1Module = angular.module('ng1', []); + fixmeIvy('unknown') && + it('should support ng2 > ng1 > ng2', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); - const ng1 = { - template: 'ng1()', - }; - ng1Module.component('ng1', ng1); + const ng1 = { + template: 'ng1()', + }; + ng1Module.component('ng1', ng1); - @Component({selector: 'ng2a', template: 'ng2a()'}) - class Ng2a { - } - ng1Module.directive('ng2a', adapter.downgradeNg2Component(Ng2a)); + @Component({selector: 'ng2a', template: 'ng2a()'}) + class Ng2a { + } + ng1Module.directive('ng2a', adapter.downgradeNg2Component(Ng2a)); - @Component({selector: 'ng2b', template: 'ng2b'}) - class Ng2b { - } - ng1Module.directive('ng2b', adapter.downgradeNg2Component(Ng2b)); + @Component({selector: 'ng2b', template: 'ng2b'}) + class Ng2b { + } + ng1Module.directive('ng2b', adapter.downgradeNg2Component(Ng2b)); - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2a, Ng2b], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2a, Ng2b], + imports: [BrowserModule], + }) + class Ng2Module { + } - const element = html(`
`); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(multiTrim(document.body.textContent)).toEqual('ng2a(ng1(ng2b))'); - }); - })); + const element = html(`
`); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toEqual('ng2a(ng1(ng2b))'); + }); + })); }); describe('injection', () => { @@ -3096,31 +3202,33 @@ withEachNg1Version(() => { }); })); - it('should respect hierarchical dependency injection for ng2', async(() => { - const ng1Module = angular.module('ng1', []); + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should respect hierarchical dependency injection for ng2', async(() => { + const ng1Module = angular.module('ng1', []); - @Component({selector: 'ng2-parent', template: `ng2-parent()`}) - class Ng2Parent { - } - @Component({selector: 'ng2-child', template: `ng2-child`}) - class Ng2Child { - constructor(parent: Ng2Parent) {} - } + @Component( + {selector: 'ng2-parent', template: `ng2-parent()`}) + class Ng2Parent { + } + @Component({selector: 'ng2-child', template: `ng2-child`}) + class Ng2Child { + constructor(parent: Ng2Parent) {} + } - @NgModule({declarations: [Ng2Parent, Ng2Child], imports: [BrowserModule]}) - class Ng2Module { - } + @NgModule({declarations: [Ng2Parent, Ng2Child], imports: [BrowserModule]}) + class Ng2Module { + } - const element = html(''); + const element = html(''); - const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); - ng1Module.directive('ng2Parent', adapter.downgradeNg2Component(Ng2Parent)) - .directive('ng2Child', adapter.downgradeNg2Component(Ng2Child)); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(document.body.textContent).toEqual('ng2-parent(ng2-child)'); - ref.dispose(); - }); - })); + const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); + ng1Module.directive('ng2Parent', adapter.downgradeNg2Component(Ng2Parent)) + .directive('ng2Child', adapter.downgradeNg2Component(Ng2Child)); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(document.body.textContent).toEqual('ng2-parent(ng2-child)'); + ref.dispose(); + }); + })); }); describe('testability', () => { @@ -3197,44 +3305,45 @@ withEachNg1Version(() => { }); describe('examples', () => { - it('should verify UpgradeAdapter example', async(() => { - const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - const module = angular.module('myExample', []); + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should verify UpgradeAdapter example', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const module = angular.module('myExample', []); - const ng1 = () => { - return { - scope: {title: '='}, - transclude: true, - template: 'ng1[Hello {{title}}!]()' - }; - }; - module.directive('ng1', ng1); + const ng1 = () => { + return { + scope: {title: '='}, + transclude: true, + template: 'ng1[Hello {{title}}!]()' + }; + }; + module.directive('ng1', ng1); - @Component({ - selector: 'ng2', - inputs: ['name'], - template: 'ng2[transclude]()' - }) - class Ng2 { - } + @Component({ + selector: 'ng2', + inputs: ['name'], + template: 'ng2[transclude]()' + }) + class Ng2 { + } - @NgModule({ - declarations: [adapter.upgradeNg1Component('ng1'), Ng2], - imports: [BrowserModule], - }) - class Ng2Module { - } + @NgModule({ + declarations: [adapter.upgradeNg1Component('ng1'), Ng2], + imports: [BrowserModule], + }) + class Ng2Module { + } - module.directive('ng2', adapter.downgradeNg2Component(Ng2)); + module.directive('ng2', adapter.downgradeNg2Component(Ng2)); - document.body.innerHTML = 'project'; + document.body.innerHTML = 'project'; - adapter.bootstrap(document.body.firstElementChild !, ['myExample']).ready((ref) => { - expect(multiTrim(document.body.textContent)) - .toEqual('ng2[ng1[Hello World!](transclude)](project)'); - ref.dispose(); - }); - })); + adapter.bootstrap(document.body.firstElementChild !, ['myExample']).ready((ref) => { + expect(multiTrim(document.body.textContent)) + .toEqual('ng2[ng1[Hello World!](transclude)](project)'); + ref.dispose(); + }); + })); }); describe('registerForNg1Tests', () => { diff --git a/packages/upgrade/test/static/integration/change_detection_spec.ts b/packages/upgrade/test/static/integration/change_detection_spec.ts index b01649f92c..d2f472eaa7 100644 --- a/packages/upgrade/test/static/integration/change_detection_spec.ts +++ b/packages/upgrade/test/static/integration/change_detection_spec.ts @@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, Injector, Input, NgModule, NgZone, Sim import {async} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {fixmeIvy} from '@angular/private/testing'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; @@ -75,57 +76,60 @@ withEachNg1Version(() => { }); })); - it('should propagate changes to a downgraded component inside the ngZone', async(() => { - let appComponent: AppComponent; + fixmeIvy( + 'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger') && + it('should propagate changes to a downgraded component inside the ngZone', async(() => { + const element = html(''); + let appComponent: AppComponent; - @Component({selector: 'my-app', template: ''}) - class AppComponent { - // TODO(issue/24571): remove '!'. - value !: number; - constructor() { appComponent = this; } - } + @Component({selector: 'my-app', template: ''}) + class AppComponent { + value?: number; + constructor() { appComponent = this; } + } - @Component({ - selector: 'my-child', - template: '
{{ valueFromPromise }}
', - }) - class ChildComponent { - // TODO(issue/24571): remove '!'. - valueFromPromise !: number; - @Input() - set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); } + @Component({ + selector: 'my-child', + template: '
{{ valueFromPromise }}
', + }) + class ChildComponent { + valueFromPromise?: number; + @Input() + set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); } - constructor(private zone: NgZone) {} + constructor(private zone: NgZone) {} - ngOnChanges(changes: SimpleChanges) { - if (changes['value'].isFirstChange()) return; + ngOnChanges(changes: SimpleChanges) { + if (changes['value'].isFirstChange()) return; - this.zone.onMicrotaskEmpty.subscribe( - () => { expect(element.textContent).toEqual('5'); }); + // HACK(ivy): Using setTimeout allows this test to pass but hides the ivy renderer + // timing BC. + // setTimeout(() => expect(element.textContent).toEqual('5'), 0); + this.zone.onMicrotaskEmpty.subscribe( + () => { expect(element.textContent).toEqual('5'); }); - Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue); - } - } + // Create a micro-task to update the value to be rendered asynchronously. + Promise.resolve().then( + () => this.valueFromPromise = changes['value'].currentValue); + } + } - @NgModule({ - declarations: [AppComponent, ChildComponent], - entryComponents: [AppComponent], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [AppComponent, ChildComponent], + entryComponents: [AppComponent], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const ng1Module = angular.module('ng1', []).directive( - 'myApp', downgradeComponent({component: AppComponent})); + const ng1Module = angular.module('ng1', []).directive( + 'myApp', downgradeComponent({component: AppComponent})); - - const element = html(''); - - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { - appComponent.value = 5; - }); - })); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + appComponent.value = 5; + }); + })); // This test demonstrates https://github.com/angular/angular/issues/6385 // which was invalidly fixed by https://github.com/angular/angular/pull/6386 diff --git a/packages/upgrade/test/static/integration/content_projection_spec.ts b/packages/upgrade/test/static/integration/content_projection_spec.ts index 00b21ad536..b5d0d31a24 100644 --- a/packages/upgrade/test/static/integration/content_projection_spec.ts +++ b/packages/upgrade/test/static/integration/content_projection_spec.ts @@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, Injector, Input, NgModule, destroyPlat import {async} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {fixmeIvy} from '@angular/private/testing'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; @@ -21,78 +22,82 @@ withEachNg1Version(() => { beforeEach(() => destroyPlatform()); afterEach(() => destroyPlatform()); - it('should instantiate ng2 in ng1 template and project content', async(() => { + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should instantiate ng2 in ng1 template and project content', async(() => { - // the ng2 component that will be used in ng1 (downgraded) - @Component({selector: 'ng2', template: `{{ prop }}()`}) - class Ng2Component { - prop = 'NG2'; - ngContent = 'ng2-content'; - } + // the ng2 component that will be used in ng1 (downgraded) + @Component({selector: 'ng2', template: `{{ prop }}()`}) + class Ng2Component { + prop = 'NG2'; + ngContent = 'ng2-content'; + } - // our upgrade module to host the component to downgrade - @NgModule({ - imports: [BrowserModule, UpgradeModule], - declarations: [Ng2Component], - entryComponents: [Ng2Component] - }) - class Ng2Module { - ngDoBootstrap() {} - } + // our upgrade module to host the component to downgrade + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng2Component], + entryComponents: [Ng2Component] + }) + class Ng2Module { + ngDoBootstrap() {} + } - // the ng1 app module that will consume the downgraded component - const ng1Module = angular - .module('ng1', []) - // create an ng1 facade of the ng2 component - .directive('ng2', downgradeComponent({component: Ng2Component})) - .run(($rootScope: angular.IRootScopeService) => { - $rootScope['prop'] = 'NG1'; - $rootScope['ngContent'] = 'ng1-content'; - }); + // the ng1 app module that will consume the downgraded component + const ng1Module = angular + .module('ng1', []) + // create an ng1 facade of the ng2 component + .directive('ng2', downgradeComponent({component: Ng2Component})) + .run(($rootScope: angular.IRootScopeService) => { + $rootScope['prop'] = 'NG1'; + $rootScope['ngContent'] = 'ng1-content'; + }); - const element = html('
{{ \'ng1[\' }}~{{ ngContent }}~{{ \']\' }}
'); + const element = + html('
{{ \'ng1[\' }}~{{ ngContent }}~{{ \']\' }}
'); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { - expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]'); - }); - })); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]'); + }); + })); - it('should correctly project structural directives', async(() => { - @Component({selector: 'ng2', template: 'ng2-{{ itemId }}()'}) - class Ng2Component { - // TODO(issue/24571): remove '!'. - @Input() itemId !: string; - } + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should correctly project structural directives', async(() => { + @Component({selector: 'ng2', template: 'ng2-{{ itemId }}()'}) + class Ng2Component { + // TODO(issue/24571): remove '!'. + @Input() itemId !: string; + } - @NgModule({ - imports: [BrowserModule, UpgradeModule], - declarations: [Ng2Component], - entryComponents: [Ng2Component] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng2Component], + entryComponents: [Ng2Component] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const ng1Module = angular.module('ng1', []) - .directive('ng2', downgradeComponent({component: Ng2Component})) - .run(($rootScope: angular.IRootScopeService) => { - $rootScope['items'] = [ - {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, - {id: 'c', subitems: [7, 8, 9]} - ]; - }); + const ng1Module = + angular.module('ng1', []) + .directive('ng2', downgradeComponent({component: Ng2Component})) + .run(($rootScope: angular.IRootScopeService) => { + $rootScope['items'] = [ + {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, + {id: 'c', subitems: [7, 8, 9]} + ]; + }); - const element = html(` + const element = html(`
{{ subitem }}
`); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { - expect(multiTrim(document.body.textContent)) - .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); - }); - })); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + expect(multiTrim(document.body.textContent)) + .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); + }); + })); it('should instantiate ng1 in ng2 template and project content', async(() => { @@ -140,38 +145,39 @@ withEachNg1Version(() => { }); })); - it('should support multi-slot projection', async(() => { + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should support multi-slot projection', async(() => { - @Component({ - selector: 'ng2', - template: '2a()' + - '2b()' - }) - class Ng2Component { - constructor() {} - } + @Component({ + selector: 'ng2', + template: '2a()' + + '2b()' + }) + class Ng2Component { + constructor() {} + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const ng1Module = angular.module('ng1', []).directive( - 'ng2', downgradeComponent({component: Ng2Component})); + const ng1Module = angular.module('ng1', []).directive( + 'ng2', downgradeComponent({component: Ng2Component})); - // The ng-if on one of the projected children is here to make sure - // the correct slot is targeted even with structural directives in play. - const element = html( - '
1a
1b
'); + // The ng-if on one of the projected children is here to make sure + // the correct slot is targeted even with structural directives in play. + const element = html( + '
1a
1b
'); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { - expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); - }); - })); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); + }); + })); }); }); diff --git a/packages/upgrade/test/static/integration/downgrade_component_spec.ts b/packages/upgrade/test/static/integration/downgrade_component_spec.ts index b9a51aa96e..6cb9636c1b 100644 --- a/packages/upgrade/test/static/integration/downgrade_component_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_component_spec.ts @@ -10,6 +10,7 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Compiler, Component, Compone import {async, fakeAsync, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {fixmeIvy} from '@angular/private/testing'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; @@ -21,104 +22,106 @@ withEachNg1Version(() => { beforeEach(() => destroyPlatform()); afterEach(() => destroyPlatform()); - it('should bind properties, events', async(() => { - const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { - $rootScope['name'] = 'world'; - $rootScope['dataA'] = 'A'; - $rootScope['dataB'] = 'B'; - $rootScope['modelA'] = 'initModelA'; - $rootScope['modelB'] = 'initModelB'; - $rootScope['eventA'] = '?'; - $rootScope['eventB'] = '?'; - }); + fixmeIvy('FW-716: Error: [$rootScope:inprog] $digest already in progress') && + it('should bind properties, events', async(() => { + const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { + $rootScope['name'] = 'world'; + $rootScope['dataA'] = 'A'; + $rootScope['dataB'] = 'B'; + $rootScope['modelA'] = 'initModelA'; + $rootScope['modelB'] = 'initModelB'; + $rootScope['eventA'] = '?'; + $rootScope['eventB'] = '?'; + }); - @Component({ - selector: 'ng2', - inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], - outputs: [ - 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange' - ], - template: 'ignore: {{ignore}}; ' + - 'literal: {{literal}}; interpolate: {{interpolate}}; ' + - 'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' + - 'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})' - }) - class Ng2Component implements OnChanges { - ngOnChangesCount = 0; - ignore = '-'; - literal = '?'; - interpolate = '?'; - oneWayA = '?'; - oneWayB = '?'; - twoWayA = '?'; - twoWayB = '?'; - eventA = new EventEmitter(); - eventB = new EventEmitter(); - twoWayAEmitter = new EventEmitter(); - twoWayBEmitter = new EventEmitter(); + @Component({ + selector: 'ng2', + inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], + outputs: [ + 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', + 'twoWayBEmitter: twoWayBChange' + ], + template: 'ignore: {{ignore}}; ' + + 'literal: {{literal}}; interpolate: {{interpolate}}; ' + + 'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' + + 'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})' + }) + class Ng2Component implements OnChanges { + ngOnChangesCount = 0; + ignore = '-'; + literal = '?'; + interpolate = '?'; + oneWayA = '?'; + oneWayB = '?'; + twoWayA = '?'; + twoWayB = '?'; + eventA = new EventEmitter(); + eventB = new EventEmitter(); + twoWayAEmitter = new EventEmitter(); + twoWayBEmitter = new EventEmitter(); - ngOnChanges(changes: SimpleChanges) { - const assert = (prop: string, value: any) => { - const propVal = (this as any)[prop]; - if (propVal != value) { - throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`); + ngOnChanges(changes: SimpleChanges) { + const assert = (prop: string, value: any) => { + const propVal = (this as any)[prop]; + if (propVal != value) { + throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`); + } + }; + + const assertChange = (prop: string, value: any) => { + assert(prop, value); + if (!changes[prop]) { + throw new Error(`Changes record for '${prop}' not found.`); + } + const actualValue = changes[prop].currentValue; + if (actualValue != value) { + throw new Error( + `Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`); + } + }; + + switch (this.ngOnChangesCount++) { + case 0: + assert('ignore', '-'); + assertChange('literal', 'Text'); + assertChange('interpolate', 'Hello world'); + assertChange('oneWayA', 'A'); + assertChange('oneWayB', 'B'); + assertChange('twoWayA', 'initModelA'); + assertChange('twoWayB', 'initModelB'); + + this.twoWayAEmitter.emit('newA'); + this.twoWayBEmitter.emit('newB'); + this.eventA.emit('aFired'); + this.eventB.emit('bFired'); + break; + case 1: + assertChange('twoWayA', 'newA'); + assertChange('twoWayB', 'newB'); + break; + case 2: + assertChange('interpolate', 'Hello everyone'); + break; + default: + throw new Error('Called too many times! ' + JSON.stringify(changes)); + } } - }; - - const assertChange = (prop: string, value: any) => { - assert(prop, value); - if (!changes[prop]) { - throw new Error(`Changes record for '${prop}' not found.`); - } - const actualValue = changes[prop].currentValue; - if (actualValue != value) { - throw new Error( - `Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`); - } - }; - - switch (this.ngOnChangesCount++) { - case 0: - assert('ignore', '-'); - assertChange('literal', 'Text'); - assertChange('interpolate', 'Hello world'); - assertChange('oneWayA', 'A'); - assertChange('oneWayB', 'B'); - assertChange('twoWayA', 'initModelA'); - assertChange('twoWayB', 'initModelB'); - - this.twoWayAEmitter.emit('newA'); - this.twoWayBEmitter.emit('newB'); - this.eventA.emit('aFired'); - this.eventB.emit('bFired'); - break; - case 1: - assertChange('twoWayA', 'newA'); - assertChange('twoWayB', 'newB'); - break; - case 2: - assertChange('interpolate', 'Hello everyone'); - break; - default: - throw new Error('Called too many times! ' + JSON.stringify(changes)); } - } - } - ng1Module.directive('ng2', downgradeComponent({ - component: Ng2Component, - })); + ng1Module.directive('ng2', downgradeComponent({ + component: Ng2Component, + })); - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const element = html(` + const element = html(`
{ | modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
`); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { - expect(multiTrim(document.body.textContent)) - .toEqual( - 'ignore: -; ' + - 'literal: Text; interpolate: Hello world; ' + - 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' + - 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + expect(multiTrim(document.body.textContent)) + .toEqual( + 'ignore: -; ' + + 'literal: Text; interpolate: Hello world; ' + + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' + + 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); - $apply(upgrade, 'name = "everyone"'); - expect(multiTrim(document.body.textContent)) - .toEqual( - 'ignore: -; ' + - 'literal: Text; interpolate: Hello everyone; ' + - 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' + - 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); - }); - })); + $apply(upgrade, 'name = "everyone"'); + expect(multiTrim(document.body.textContent)) + .toEqual( + 'ignore: -; ' + + 'literal: Text; interpolate: Hello everyone; ' + + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' + + 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); + }); + })); it('should bind properties to onpush components', async(() => { const ng1Module = angular.module('ng1', []).run( @@ -186,57 +189,58 @@ withEachNg1Version(() => { }); })); - it('should support two-way binding and event listener', async(() => { - const listenerSpy = jasmine.createSpy('$rootScope.listener'); - const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { - $rootScope['value'] = 'world'; - $rootScope['listener'] = listenerSpy; - }); + fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && + it('should support two-way binding and event listener', async(() => { + const listenerSpy = jasmine.createSpy('$rootScope.listener'); + const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { + $rootScope['value'] = 'world'; + $rootScope['listener'] = listenerSpy; + }); - @Component({selector: 'ng2', template: `model: {{model}};`}) - class Ng2Component implements OnChanges { - ngOnChangesCount = 0; - @Input() model = '?'; - @Output() modelChange = new EventEmitter(); + @Component({selector: 'ng2', template: `model: {{model}};`}) + class Ng2Component implements OnChanges { + ngOnChangesCount = 0; + @Input() model = '?'; + @Output() modelChange = new EventEmitter(); - ngOnChanges(changes: SimpleChanges) { - switch (this.ngOnChangesCount++) { - case 0: - expect(changes.model.currentValue).toBe('world'); - this.modelChange.emit('newC'); - break; - case 1: - expect(changes.model.currentValue).toBe('newC'); - break; - default: - throw new Error('Called too many times! ' + JSON.stringify(changes)); + ngOnChanges(changes: SimpleChanges) { + switch (this.ngOnChangesCount++) { + case 0: + expect(changes.model.currentValue).toBe('world'); + this.modelChange.emit('newC'); + break; + case 1: + expect(changes.model.currentValue).toBe('newC'); + break; + default: + throw new Error('Called too many times! ' + JSON.stringify(changes)); + } + } } - } - } - ng1Module.directive('ng2', downgradeComponent({component: Ng2Component})); + ng1Module.directive('ng2', downgradeComponent({component: Ng2Component})); - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const element = html(` + const element = html(`
| value: {{value}}
`); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { - expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); - expect(listenerSpy).toHaveBeenCalledWith('newC'); - }); - })); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); + expect(listenerSpy).toHaveBeenCalledWith('newC'); + }); + })); it('should run change-detection on every digest (by default)', async(() => { let ng2Component: Ng2Component; @@ -400,65 +404,66 @@ withEachNg1Version(() => { }); })); - it('should initialize inputs in time for `ngOnChanges`', async(() => { - @Component({ - selector: 'ng2', - template: ` + fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && + it('should initialize inputs in time for `ngOnChanges`', async(() => { + @Component({ + selector: 'ng2', + template: ` ngOnChangesCount: {{ ngOnChangesCount }} | firstChangesCount: {{ firstChangesCount }} | initialValue: {{ initialValue }}` - }) - class Ng2Component implements OnChanges { - ngOnChangesCount = 0; - firstChangesCount = 0; - // TODO(issue/24571): remove '!'. - initialValue !: string; - // TODO(issue/24571): remove '!'. - @Input() foo !: string; + }) + class Ng2Component implements OnChanges { + ngOnChangesCount = 0; + firstChangesCount = 0; + // TODO(issue/24571): remove '!'. + initialValue !: string; + // TODO(issue/24571): remove '!'. + @Input() foo !: string; - ngOnChanges(changes: SimpleChanges) { - this.ngOnChangesCount++; + ngOnChanges(changes: SimpleChanges) { + this.ngOnChangesCount++; - if (this.ngOnChangesCount === 1) { - this.initialValue = this.foo; + if (this.ngOnChangesCount === 1) { + this.initialValue = this.foo; + } + + if (changes['foo'] && changes['foo'].isFirstChange()) { + this.firstChangesCount++; + } + } } - if (changes['foo'] && changes['foo'].isFirstChange()) { - this.firstChangesCount++; + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng2Component], + entryComponents: [Ng2Component] + }) + class Ng2Module { + ngDoBootstrap() {} } - } - } - @NgModule({ - imports: [BrowserModule, UpgradeModule], - declarations: [Ng2Component], - entryComponents: [Ng2Component] - }) - class Ng2Module { - ngDoBootstrap() {} - } + const ng1Module = angular.module('ng1', []).directive( + 'ng2', downgradeComponent({component: Ng2Component})); - const ng1Module = angular.module('ng1', []).directive( - 'ng2', downgradeComponent({component: Ng2Component})); - - const element = html(` + const element = html(` `); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { - const nodes = element.querySelectorAll('ng2'); - const expectedTextWith = (value: string) => - `ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`; + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + const nodes = element.querySelectorAll('ng2'); + const expectedTextWith = (value: string) => + `ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`; - expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo')); - expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar')); - expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz')); - expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux')); - }); - })); + expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo')); + expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar')); + expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz')); + expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux')); + }); + })); it('should bind to ng-model', async(() => { const ng1Module = angular.module('ng1', []).run( @@ -515,94 +520,96 @@ withEachNg1Version(() => { }); })); - it('should properly run cleanup when ng1 directive is destroyed', async(() => { + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should properly run cleanup when ng1 directive is destroyed', async(() => { - let destroyed = false; - @Component({selector: 'ng2', template: 'test'}) - class Ng2Component implements OnDestroy { - ngOnDestroy() { destroyed = true; } - } + let destroyed = false; + @Component({selector: 'ng2', template: 'test'}) + class Ng2Component implements OnDestroy { + ngOnDestroy() { destroyed = true; } + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const ng1Module = - angular.module('ng1', []) - .directive( - 'ng1', - () => { return {template: '
'}; }) - .directive('ng2', downgradeComponent({component: Ng2Component})); - const element = html(''); - platformBrowserDynamic().bootstrapModule(Ng2Module).then((ref) => { - const adapter = ref.injector.get(UpgradeModule) as UpgradeModule; - adapter.bootstrap(element, [ng1Module.name]); - expect(element.textContent).toContain('test'); - expect(destroyed).toBe(false); + const ng1Module = + angular.module('ng1', []) + .directive( + 'ng1', + () => { return {template: '
'}; }) + .directive('ng2', downgradeComponent({component: Ng2Component})); + const element = html(''); + platformBrowserDynamic().bootstrapModule(Ng2Module).then((ref) => { + const adapter = ref.injector.get(UpgradeModule) as UpgradeModule; + adapter.bootstrap(element, [ng1Module.name]); + expect(element.textContent).toContain('test'); + expect(destroyed).toBe(false); - const $rootScope = adapter.$injector.get('$rootScope'); - $rootScope.$apply('destroyIt = true'); + const $rootScope = adapter.$injector.get('$rootScope'); + $rootScope.$apply('destroyIt = true'); - expect(element.textContent).not.toContain('test'); - expect(destroyed).toBe(true); - }); - })); + expect(element.textContent).not.toContain('test'); + expect(destroyed).toBe(true); + }); + })); - it('should properly run cleanup with multiple levels of nesting', async(() => { - let destroyed = false; + fixmeIvy('FW-642: ASSERTION ERROR: Slot should have been initialized to NO_CHANGE') && + it('should properly run cleanup with multiple levels of nesting', async(() => { + let destroyed = false; - @Component({ - selector: 'ng2-outer', - template: '
', - }) - class Ng2OuterComponent { - @Input() destroyIt = false; - } + @Component({ + selector: 'ng2-outer', + template: '
', + }) + class Ng2OuterComponent { + @Input() destroyIt = false; + } - @Component({selector: 'ng2-inner', template: 'test'}) - class Ng2InnerComponent implements OnDestroy { - ngOnDestroy() { destroyed = true; } - } + @Component({selector: 'ng2-inner', template: 'test'}) + class Ng2InnerComponent implements OnDestroy { + ngOnDestroy() { destroyed = true; } + } - @Directive({selector: 'ng1'}) - class Ng1ComponentFacade extends UpgradeComponent { - constructor(elementRef: ElementRef, injector: Injector) { - super('ng1', elementRef, injector); - } - } + @Directive({selector: 'ng1'}) + class Ng1ComponentFacade extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1', elementRef, injector); + } + } - @NgModule({ - imports: [BrowserModule, UpgradeModule], - declarations: [Ng1ComponentFacade, Ng2InnerComponent, Ng2OuterComponent], - entryComponents: [Ng2InnerComponent, Ng2OuterComponent], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng1ComponentFacade, Ng2InnerComponent, Ng2OuterComponent], + entryComponents: [Ng2InnerComponent, Ng2OuterComponent], + }) + class Ng2Module { + ngDoBootstrap() {} + } - const ng1Module = - angular.module('ng1', []) - .directive('ng1', () => ({template: ''})) - .directive('ng2Inner', downgradeComponent({component: Ng2InnerComponent})) - .directive('ng2Outer', downgradeComponent({component: Ng2OuterComponent})); + const ng1Module = + angular.module('ng1', []) + .directive('ng1', () => ({template: ''})) + .directive('ng2Inner', downgradeComponent({component: Ng2InnerComponent})) + .directive('ng2Outer', downgradeComponent({component: Ng2OuterComponent})); - const element = html(''); + const element = html(''); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { - expect(element.textContent).toBe('test'); - expect(destroyed).toBe(false); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + expect(element.textContent).toBe('test'); + expect(destroyed).toBe(false); - $apply(upgrade, 'destroyIt = true'); + $apply(upgrade, 'destroyIt = true'); - expect(element.textContent).toBe(''); - expect(destroyed).toBe(true); - }); - })); + expect(element.textContent).toBe(''); + expect(destroyed).toBe(true); + }); + })); it('should work when compiled outside the dom (by fallback to the root ng2.injector)', async(() => { @@ -704,86 +711,88 @@ withEachNg1Version(() => { }); })); - it('should respect hierarchical dependency injection for ng2', async(() => { - @Component({selector: 'parent', template: 'parent()'}) - class ParentComponent { - } + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should respect hierarchical dependency injection for ng2', async(() => { + @Component({selector: 'parent', template: 'parent()'}) + class ParentComponent { + } - @Component({selector: 'child', template: 'child'}) - class ChildComponent { - constructor(parent: ParentComponent) {} - } + @Component({selector: 'child', template: 'child'}) + class ChildComponent { + constructor(parent: ParentComponent) {} + } - @NgModule({ - declarations: [ParentComponent, ChildComponent], - entryComponents: [ParentComponent, ChildComponent], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [ParentComponent, ChildComponent], + entryComponents: [ParentComponent, ChildComponent], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const ng1Module = - angular.module('ng1', []) - .directive('parent', downgradeComponent({component: ParentComponent})) - .directive('child', downgradeComponent({component: ChildComponent})); + const ng1Module = + angular.module('ng1', []) + .directive('parent', downgradeComponent({component: ParentComponent})) + .directive('child', downgradeComponent({component: ChildComponent})); - const element = html(''); + const element = html(''); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { - expect(multiTrim(document.body.textContent)).toBe('parent(child)'); - }); - })); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + expect(multiTrim(document.body.textContent)).toBe('parent(child)'); + }); + })); - it('should work with ng2 lazy loaded components', async(() => { + fixmeIvy('FW-561: Runtime compiler is not loaded') && + it('should work with ng2 lazy loaded components', async(() => { - let componentInjector: Injector; + let componentInjector: Injector; - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - constructor(injector: Injector) { componentInjector = injector; } - } + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + constructor(injector: Injector) { componentInjector = injector; } + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule, UpgradeModule], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule], + }) + class Ng2Module { + ngDoBootstrap() {} + } - @Component({template: ''}) - class LazyLoadedComponent { - constructor(public module: NgModuleRef) {} - } + @Component({template: ''}) + class LazyLoadedComponent { + constructor(public module: NgModuleRef) {} + } - @NgModule({ - declarations: [LazyLoadedComponent], - entryComponents: [LazyLoadedComponent], - }) - class LazyLoadedModule { - } + @NgModule({ + declarations: [LazyLoadedComponent], + entryComponents: [LazyLoadedComponent], + }) + class LazyLoadedModule { + } - const ng1Module = angular.module('ng1', []).directive( - 'ng2', downgradeComponent({component: Ng2Component})); + const ng1Module = angular.module('ng1', []).directive( + 'ng2', downgradeComponent({component: Ng2Component})); - const element = html(''); + const element = html(''); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { - const modInjector = upgrade.injector; - // Emulate the router lazy loading a module and creating a component - const compiler = modInjector.get(Compiler); - const modFactory = compiler.compileModuleSync(LazyLoadedModule); - const childMod = modFactory.create(modInjector); - const cmpFactory = - childMod.componentFactoryResolver.resolveComponentFactory(LazyLoadedComponent) !; - const lazyCmp = cmpFactory.create(componentInjector); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + const modInjector = upgrade.injector; + // Emulate the router lazy loading a module and creating a component + const compiler = modInjector.get(Compiler); + const modFactory = compiler.compileModuleSync(LazyLoadedModule); + const childMod = modFactory.create(modInjector); + const cmpFactory = + childMod.componentFactoryResolver.resolveComponentFactory(LazyLoadedComponent) !; + const lazyCmp = cmpFactory.create(componentInjector); - expect(lazyCmp.instance.module.injector).toBe(childMod.injector); - }); + expect(lazyCmp.instance.module.injector).toBe(childMod.injector); + }); - })); + })); it('should throw if `downgradedModule` is specified', async(() => { @Component({selector: 'ng2', template: ''}) diff --git a/packages/upgrade/test/static/integration/downgrade_module_spec.ts b/packages/upgrade/test/static/integration/downgrade_module_spec.ts index ac93be22d5..9214607e76 100644 --- a/packages/upgrade/test/static/integration/downgrade_module_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_module_spec.ts @@ -11,6 +11,7 @@ import {async, fakeAsync, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; +import {fixmeIvy} from '@angular/private/testing'; import {downgradeComponent, downgradeModule} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; import {$EXCEPTION_HANDLER, $ROOT_SCOPE, INJECTOR_KEY, LAZY_MODULE_REF} from '@angular/upgrade/static/src/common/constants'; @@ -131,62 +132,64 @@ withEachNg1Version(() => { }); })); - it('should support using an upgraded service', async(() => { - class Ng2Service { - constructor(@Inject('ng1Value') private ng1Value: string) {} - getValue = () => `${this.ng1Value}-bar`; - } + fixmeIvy('FW-718: upgraded service not being initialized correctly on the injector') && + it('should support using an upgraded service', async(() => { + class Ng2Service { + constructor(@Inject('ng1Value') private ng1Value: string) {} + getValue = () => `${this.ng1Value}-bar`; + } - @Component({selector: 'ng2', template: '{{ value }}'}) - class Ng2Component { - value: string; - constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); } - } + @Component({selector: 'ng2', template: '{{ value }}'}) + class Ng2Component { + value: string; + constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); } + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule], - providers: [ - Ng2Service, - { - provide: 'ng1Value', - useFactory: (i: angular.IInjectorService) => i.get('ng1Value'), - deps: ['$injector'], - }, - ], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule], + providers: [ + Ng2Service, + { + provide: 'ng1Value', + useFactory: (i: angular.IInjectorService) => i.get('ng1Value'), + deps: ['$injector'], + }, + ], + }) + class Ng2Module { + ngDoBootstrap() {} + } - const bootstrapFn = (extraProviders: StaticProvider[]) => - platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); - const lazyModuleName = downgradeModule(bootstrapFn); - const ng1Module = - angular.module('ng1', [lazyModuleName]) - .directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest})) - .value('ng1Value', 'foo'); + const bootstrapFn = (extraProviders: StaticProvider[]) => + platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); + const lazyModuleName = downgradeModule(bootstrapFn); + const ng1Module = + angular.module('ng1', [lazyModuleName]) + .directive( + 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})) + .value('ng1Value', 'foo'); - const element = html('
'); - const $injector = angular.bootstrap(element, [ng1Module.name]); - const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; + const element = html('
'); + const $injector = angular.bootstrap(element, [ng1Module.name]); + const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; - expect(element.textContent).toBe(''); - expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); + expect(element.textContent).toBe(''); + expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); - $rootScope.$apply('loadNg2 = true'); - expect(element.textContent).toBe(''); - expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); + $rootScope.$apply('loadNg2 = true'); + expect(element.textContent).toBe(''); + expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); - // Wait for the module to be bootstrapped. - setTimeout(() => { - expect(() => $injector.get(INJECTOR_KEY)).not.toThrow(); + // Wait for the module to be bootstrapped. + setTimeout(() => { + expect(() => $injector.get(INJECTOR_KEY)).not.toThrow(); - // Wait for `$evalAsync()` to propagate inputs. - setTimeout(() => expect(element.textContent).toBe('foo-bar')); - }); - })); + // Wait for `$evalAsync()` to propagate inputs. + setTimeout(() => expect(element.textContent).toBe('foo-bar')); + }); + })); it('should create components inside the Angular zone', async(() => { @Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'}) @@ -222,99 +225,103 @@ withEachNg1Version(() => { }); })); - it('should destroy components inside the Angular zone', async(() => { - let destroyedInTheZone = false; + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should destroy components inside the Angular zone', async(() => { + let destroyedInTheZone = false; - @Component({selector: 'ng2', template: ''}) - class Ng2Component implements OnDestroy { - ngOnDestroy() { destroyedInTheZone = NgZone.isInAngularZone(); } - } + @Component({selector: 'ng2', template: ''}) + class Ng2Component implements OnDestroy { + ngOnDestroy() { destroyedInTheZone = NgZone.isInAngularZone(); } + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule], + }) + class Ng2Module { + ngDoBootstrap() {} + } - const bootstrapFn = (extraProviders: StaticProvider[]) => - platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); - const lazyModuleName = downgradeModule(bootstrapFn); - const ng1Module = - angular.module('ng1', [lazyModuleName]) - .directive( - 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); + const bootstrapFn = (extraProviders: StaticProvider[]) => + platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); + const lazyModuleName = downgradeModule(bootstrapFn); + const ng1Module = + angular.module('ng1', [lazyModuleName]) + .directive( + 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); - const element = html(''); - const $injector = angular.bootstrap(element, [ng1Module.name]); - const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; + const element = html(''); + const $injector = angular.bootstrap(element, [ng1Module.name]); + const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; - // Wait for the module to be bootstrapped. - setTimeout(() => { - $rootScope.$apply('hideNg2 = true'); - expect(destroyedInTheZone).toBe(true); - }); - })); + // Wait for the module to be bootstrapped. + setTimeout(() => { + $rootScope.$apply('hideNg2 = true'); + expect(destroyedInTheZone).toBe(true); + }); + })); - it('should propagate input changes inside the Angular zone', async(() => { - let ng2Component: Ng2Component; + fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && + it('should propagate input changes inside the Angular zone', async(() => { + let ng2Component: Ng2Component; - @Component({selector: 'ng2', template: ''}) - class Ng2Component implements OnChanges { - @Input() attrInput = 'foo'; - @Input() propInput = 'foo'; + @Component({selector: 'ng2', template: ''}) + class Ng2Component implements OnChanges { + @Input() attrInput = 'foo'; + @Input() propInput = 'foo'; - constructor() { ng2Component = this; } - ngOnChanges() {} - } + constructor() { ng2Component = this; } + ngOnChanges() {} + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule], + }) + class Ng2Module { + ngDoBootstrap() {} + } - const bootstrapFn = (extraProviders: StaticProvider[]) => - platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); - const lazyModuleName = downgradeModule(bootstrapFn); - const ng1Module = - angular.module('ng1', [lazyModuleName]) - .directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest})) - .run(($rootScope: angular.IRootScopeService) => { - $rootScope.attrVal = 'bar'; - $rootScope.propVal = 'bar'; - }); + const bootstrapFn = (extraProviders: StaticProvider[]) => + platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); + const lazyModuleName = downgradeModule(bootstrapFn); + const ng1Module = + angular.module('ng1', [lazyModuleName]) + .directive( + 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})) + .run(($rootScope: angular.IRootScopeService) => { + $rootScope.attrVal = 'bar'; + $rootScope.propVal = 'bar'; + }); - const element = html(''); - const $injector = angular.bootstrap(element, [ng1Module.name]); - const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; + const element = + html(''); + const $injector = angular.bootstrap(element, [ng1Module.name]); + const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; - setTimeout(() => { // Wait for the module to be bootstrapped. - setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. - const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true); - const changesSpy = - spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone); + setTimeout(() => { // Wait for the module to be bootstrapped. + setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. + const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true); + const changesSpy = + spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone); - expect(ng2Component.attrInput).toBe('bar'); - expect(ng2Component.propInput).toBe('bar'); + expect(ng2Component.attrInput).toBe('bar'); + expect(ng2Component.propInput).toBe('bar'); - $rootScope.$apply('attrVal = "baz"'); - expect(ng2Component.attrInput).toBe('baz'); - expect(ng2Component.propInput).toBe('bar'); - expect(changesSpy).toHaveBeenCalledTimes(1); + $rootScope.$apply('attrVal = "baz"'); + expect(ng2Component.attrInput).toBe('baz'); + expect(ng2Component.propInput).toBe('bar'); + expect(changesSpy).toHaveBeenCalledTimes(1); - $rootScope.$apply('propVal = "qux"'); - expect(ng2Component.attrInput).toBe('baz'); - expect(ng2Component.propInput).toBe('qux'); - expect(changesSpy).toHaveBeenCalledTimes(2); - }); - }); - })); + $rootScope.$apply('propVal = "qux"'); + expect(ng2Component.attrInput).toBe('baz'); + expect(ng2Component.propInput).toBe('qux'); + expect(changesSpy).toHaveBeenCalledTimes(2); + }); + }); + })); it('should wire up the component for change detection', async(() => { @Component( @@ -358,209 +365,215 @@ withEachNg1Version(() => { }); })); - it('should run the lifecycle hooks in the correct order', async(() => { - const logs: string[] = []; - let rootScope: angular.IRootScopeService; + fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should run the lifecycle hooks in the correct order', async(() => { + const logs: string[] = []; + let rootScope: angular.IRootScopeService; - @Component({ - selector: 'ng2', - template: ` + @Component({ + selector: 'ng2', + template: ` {{ value }} ` - }) - class Ng2Component implements AfterContentChecked, - AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, - OnInit { - @Input() value = 'foo'; + }) + class Ng2Component implements AfterContentChecked, + AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, + OnInit { + @Input() value = 'foo'; - ngAfterContentChecked() { this.log('AfterContentChecked'); } - ngAfterContentInit() { this.log('AfterContentInit'); } - ngAfterViewChecked() { this.log('AfterViewChecked'); } - ngAfterViewInit() { this.log('AfterViewInit'); } - ngDoCheck() { this.log('DoCheck'); } - ngOnChanges() { this.log('OnChanges'); } - ngOnDestroy() { this.log('OnDestroy'); } - ngOnInit() { this.log('OnInit'); } + ngAfterContentChecked() { this.log('AfterContentChecked'); } + ngAfterContentInit() { this.log('AfterContentInit'); } + ngAfterViewChecked() { this.log('AfterViewChecked'); } + ngAfterViewInit() { this.log('AfterViewInit'); } + ngDoCheck() { this.log('DoCheck'); } + ngOnChanges() { this.log('OnChanges'); } + ngOnDestroy() { this.log('OnDestroy'); } + ngOnInit() { this.log('OnInit'); } - private log(hook: string) { logs.push(`${hook}(${this.value})`); } - } + private log(hook: string) { logs.push(`${hook}(${this.value})`); } + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule], + }) + class Ng2Module { + ngDoBootstrap() {} + } - const bootstrapFn = (extraProviders: StaticProvider[]) => - platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); - const lazyModuleName = downgradeModule(bootstrapFn); - const ng1Module = - angular.module('ng1', [lazyModuleName]) - .directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest})) - .run(($rootScope: angular.IRootScopeService) => { - rootScope = $rootScope; - rootScope.value = 'bar'; - }); + const bootstrapFn = (extraProviders: StaticProvider[]) => + platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); + const lazyModuleName = downgradeModule(bootstrapFn); + const ng1Module = + angular.module('ng1', [lazyModuleName]) + .directive( + 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})) + .run(($rootScope: angular.IRootScopeService) => { + rootScope = $rootScope; + rootScope.value = 'bar'; + }); - const element = - html('
Content
'); - angular.bootstrap(element, [ng1Module.name]); + const element = + html('
Content
'); + angular.bootstrap(element, [ng1Module.name]); - setTimeout(() => { // Wait for the module to be bootstrapped. - setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. - const button = element.querySelector('button') !; + setTimeout(() => { // Wait for the module to be bootstrapped. + setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. + const button = element.querySelector('button') !; - // Once initialized. - expect(multiTrim(element.textContent)).toBe('bar Content'); - expect(logs).toEqual([ - // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. - 'OnChanges(bar)', - // Initial CD triggered directly through the `detectChanges()` or `inputChanges` - // $watcher (for `propagateDigest` true/false respectively). - 'OnInit(bar)', - 'DoCheck(bar)', - 'AfterContentInit(bar)', - 'AfterContentChecked(bar)', - 'AfterViewInit(bar)', - 'AfterViewChecked(bar)', - ...(propagateDigest ? - [ - // CD triggered directly through the `detectChanges()` $watcher (2nd - // $digest). - 'DoCheck(bar)', - 'AfterContentChecked(bar)', - 'AfterViewChecked(bar)', - ] : - []), - // CD triggered due to entering/leaving the NgZone (in `downgradeFn()`). - 'DoCheck(bar)', - 'AfterContentChecked(bar)', - 'AfterViewChecked(bar)', - ]); - logs.length = 0; + // Once initialized. + expect(multiTrim(element.textContent)).toBe('bar Content'); + expect(logs).toEqual([ + // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. + 'OnChanges(bar)', + // Initial CD triggered directly through the `detectChanges()` or + // `inputChanges` + // $watcher (for `propagateDigest` true/false respectively). + 'OnInit(bar)', + 'DoCheck(bar)', + 'AfterContentInit(bar)', + 'AfterContentChecked(bar)', + 'AfterViewInit(bar)', + 'AfterViewChecked(bar)', + ...(propagateDigest ? + [ + // CD triggered directly through the `detectChanges()` $watcher (2nd + // $digest). + 'DoCheck(bar)', + 'AfterContentChecked(bar)', + 'AfterViewChecked(bar)', + ] : + []), + // CD triggered due to entering/leaving the NgZone (in `downgradeFn()`). + 'DoCheck(bar)', + 'AfterContentChecked(bar)', + 'AfterViewChecked(bar)', + ]); + logs.length = 0; - // Change inputs and run `$digest`. - rootScope.$apply('value = "baz"'); - expect(multiTrim(element.textContent)).toBe('baz Content'); - expect(logs).toEqual([ - // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. - 'OnChanges(baz)', - // `propagateDigest: true` (3 CD runs): - // - CD triggered due to entering/leaving the NgZone (in `inputChanges` - // $watcher). - // - CD triggered directly through the `detectChanges()` $watcher. - // - CD triggered due to entering/leaving the NgZone (in `detectChanges` - // $watcher). - // `propagateDigest: false` (2 CD runs): - // - CD triggered directly through the `inputChanges` $watcher. - // - CD triggered due to entering/leaving the NgZone (in `inputChanges` - // $watcher). - 'DoCheck(baz)', - 'AfterContentChecked(baz)', - 'AfterViewChecked(baz)', - 'DoCheck(baz)', - 'AfterContentChecked(baz)', - 'AfterViewChecked(baz)', - ...(propagateDigest ? - [ - 'DoCheck(baz)', - 'AfterContentChecked(baz)', - 'AfterViewChecked(baz)', - ] : - []), - ]); - logs.length = 0; + // Change inputs and run `$digest`. + rootScope.$apply('value = "baz"'); + expect(multiTrim(element.textContent)).toBe('baz Content'); + expect(logs).toEqual([ + // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. + 'OnChanges(baz)', + // `propagateDigest: true` (3 CD runs): + // - CD triggered due to entering/leaving the NgZone (in `inputChanges` + // $watcher). + // - CD triggered directly through the `detectChanges()` $watcher. + // - CD triggered due to entering/leaving the NgZone (in `detectChanges` + // $watcher). + // `propagateDigest: false` (2 CD runs): + // - CD triggered directly through the `inputChanges` $watcher. + // - CD triggered due to entering/leaving the NgZone (in `inputChanges` + // $watcher). + 'DoCheck(baz)', + 'AfterContentChecked(baz)', + 'AfterViewChecked(baz)', + 'DoCheck(baz)', + 'AfterContentChecked(baz)', + 'AfterViewChecked(baz)', + ...(propagateDigest ? + [ + 'DoCheck(baz)', + 'AfterContentChecked(baz)', + 'AfterViewChecked(baz)', + ] : + []), + ]); + logs.length = 0; - // Run `$digest` (without changing inputs). - rootScope.$digest(); - expect(multiTrim(element.textContent)).toBe('baz Content'); - expect(logs).toEqual( - propagateDigest ? - [ - // CD triggered directly through the `detectChanges()` $watcher. - 'DoCheck(baz)', - 'AfterContentChecked(baz)', - 'AfterViewChecked(baz)', - // CD triggered due to entering/leaving the NgZone (in the above $watcher). - 'DoCheck(baz)', - 'AfterContentChecked(baz)', - 'AfterViewChecked(baz)', - ] : - []); - logs.length = 0; + // Run `$digest` (without changing inputs). + rootScope.$digest(); + expect(multiTrim(element.textContent)).toBe('baz Content'); + expect(logs).toEqual( + propagateDigest ? + [ + // CD triggered directly through the `detectChanges()` $watcher. + 'DoCheck(baz)', + 'AfterContentChecked(baz)', + 'AfterViewChecked(baz)', + // CD triggered due to entering/leaving the NgZone (in the above + // $watcher). + 'DoCheck(baz)', + 'AfterContentChecked(baz)', + 'AfterViewChecked(baz)', + ] : + []); + logs.length = 0; - // Trigger change detection (without changing inputs). - button.click(); - expect(multiTrim(element.textContent)).toBe('qux Content'); - expect(logs).toEqual([ - 'DoCheck(qux)', - 'AfterContentChecked(qux)', - 'AfterViewChecked(qux)', - ]); - logs.length = 0; + // Trigger change detection (without changing inputs). + button.click(); + expect(multiTrim(element.textContent)).toBe('qux Content'); + expect(logs).toEqual([ + 'DoCheck(qux)', + 'AfterContentChecked(qux)', + 'AfterViewChecked(qux)', + ]); + logs.length = 0; - // Destroy the component. - rootScope.$apply('hideNg2 = true'); - expect(logs).toEqual([ - 'OnDestroy(qux)', - ]); - logs.length = 0; - }); - }); - })); + // Destroy the component. + rootScope.$apply('hideNg2 = true'); + expect(logs).toEqual([ + 'OnDestroy(qux)', + ]); + logs.length = 0; + }); + }); + })); - it('should detach hostViews from the ApplicationRef once destroyed', async(() => { - let ng2Component: Ng2Component; + fixmeIvy('FW-717: Browser locks up and disconnects') && + it('should detach hostViews from the ApplicationRef once destroyed', async(() => { + let ng2Component: Ng2Component; - @Component({selector: 'ng2', template: ''}) - class Ng2Component { - constructor(public appRef: ApplicationRef) { - ng2Component = this; - spyOn(appRef, 'attachView').and.callThrough(); - spyOn(appRef, 'detachView').and.callThrough(); - } - } + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + constructor(public appRef: ApplicationRef) { + ng2Component = this; + spyOn(appRef, 'attachView').and.callThrough(); + spyOn(appRef, 'detachView').and.callThrough(); + } + } - @NgModule({ - declarations: [Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule], + }) + class Ng2Module { + ngDoBootstrap() {} + } - const bootstrapFn = (extraProviders: StaticProvider[]) => - platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); - const lazyModuleName = downgradeModule(bootstrapFn); - const ng1Module = - angular.module('ng1', [lazyModuleName]) - .directive( - 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); + const bootstrapFn = (extraProviders: StaticProvider[]) => + platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); + const lazyModuleName = downgradeModule(bootstrapFn); + const ng1Module = + angular.module('ng1', [lazyModuleName]) + .directive( + 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); - const element = html(''); - const $injector = angular.bootstrap(element, [ng1Module.name]); - const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; + const element = html(''); + const $injector = angular.bootstrap(element, [ng1Module.name]); + const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; - setTimeout(() => { // Wait for the module to be bootstrapped. - setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`). - const hostView: ViewRef = - (ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0]; + setTimeout(() => { // Wait for the module to be bootstrapped. + setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`). + const hostView: ViewRef = + (ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0]; - expect(hostView.destroyed).toBe(false); + expect(hostView.destroyed).toBe(false); - $rootScope.$apply('hideNg2 = true'); + $rootScope.$apply('hideNg2 = true'); - expect(hostView.destroyed).toBe(true); - expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView); - }); - }); - })); + expect(hostView.destroyed).toBe(true); + expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView); + }); + }); + })); it('should only retrieve the Angular zone once (and cache it for later use)', fakeAsync(() => { diff --git a/packages/upgrade/test/static/integration/examples_spec.ts b/packages/upgrade/test/static/integration/examples_spec.ts index 3a0989432c..dc3244a228 100644 --- a/packages/upgrade/test/static/integration/examples_spec.ts +++ b/packages/upgrade/test/static/integration/examples_spec.ts @@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, Injector, Input, NgModule, destroyPlat import {async} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {fixmeIvy} from '@angular/private/testing'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; @@ -23,67 +24,70 @@ withEachNg1Version(() => { it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1)); - it('should verify UpgradeAdapter example', async(() => { + fixmeIvy('FW-714: ng1 projected content is not being rendered') && + it('should verify UpgradeAdapter example', async(() => { - // This is wrapping (upgrading) an AngularJS component to be used in an Angular - // component - @Directive({selector: 'ng1'}) - class Ng1Component extends UpgradeComponent { - // TODO(issue/24571): remove '!'. - @Input() title !: string; + // This is wrapping (upgrading) an AngularJS component to be used in an Angular + // component + @Directive({selector: 'ng1'}) + class Ng1Component extends UpgradeComponent { + // TODO(issue/24571): remove '!'. + @Input() title !: string; - constructor(elementRef: ElementRef, injector: Injector) { - super('ng1', elementRef, injector); - } - } + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1', elementRef, injector); + } + } - // This is an Angular component that will be downgraded - @Component({ - selector: 'ng2', - template: 'ng2[transclude]()' - }) - class Ng2Component { - // TODO(issue/24571): remove '!'. - @Input('name') nameProp !: string; - } + // This is an Angular component that will be downgraded + @Component({ + selector: 'ng2', + template: 'ng2[transclude]()' + }) + class Ng2Component { + // TODO(issue/24571): remove '!'. + @Input('name') nameProp !: string; + } - // This module represents the Angular pieces of the application - @NgModule({ - declarations: [Ng1Component, Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from complaining */ - } - } + // This module represents the Angular pieces of the application + @NgModule({ + declarations: [Ng1Component, Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from + complaining */ + } + } - // This module represents the AngularJS pieces of the application - const ng1Module = - angular - .module('myExample', []) - // This is an AngularJS component that will be upgraded - .directive( - 'ng1', - () => { - return { - scope: {title: '='}, - transclude: true, - template: 'ng1[Hello {{title}}!]()' - }; - }) - // This is wrapping (downgrading) an Angular component to be used in AngularJS - .directive('ng2', downgradeComponent({component: Ng2Component})); + // This module represents the AngularJS pieces of the application + const ng1Module = + angular + .module('myExample', []) + // This is an AngularJS component that will be upgraded + .directive( + 'ng1', + () => { + return { + scope: {title: '='}, + transclude: true, + template: 'ng1[Hello {{title}}!]()' + }; + }) + // This is wrapping (downgrading) an Angular component to be used in + // AngularJS + .directive('ng2', downgradeComponent({component: Ng2Component})); - // This is the (AngularJS) application bootstrap element - // Notice that it is actually a downgraded Angular component - const element = html('project'); + // This is the (AngularJS) application bootstrap element + // Notice that it is actually a downgraded Angular component + const element = html('project'); - // Let's use a helper function to make this simpler - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { - expect(multiTrim(element.textContent)) - .toBe('ng2[ng1[Hello World!](transclude)](project)'); - }); - })); + // Let's use a helper function to make this simpler + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + expect(multiTrim(element.textContent)) + .toBe('ng2[ng1[Hello World!](transclude)](project)'); + }); + })); }); }); diff --git a/packages/upgrade/test/static/integration/upgrade_component_spec.ts b/packages/upgrade/test/static/integration/upgrade_component_spec.ts index d318b2f3f7..31e8f143ae 100644 --- a/packages/upgrade/test/static/integration/upgrade_component_spec.ts +++ b/packages/upgrade/test/static/integration/upgrade_component_spec.ts @@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, ErrorHandler, EventEmitter, Inject, In import {async, fakeAsync, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {fixmeIvy} from '@angular/private/testing'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; import {$EXCEPTION_HANDLER, $SCOPE} from '@angular/upgrade/static/src/common/constants'; @@ -2133,72 +2134,77 @@ withEachNg1Version(() => { }); describe('transclusion', () => { - it('should support single-slot transclusion', async(() => { - let ng2ComponentAInstance: Ng2ComponentA; - let ng2ComponentBInstance: Ng2ComponentB; + fixmeIvy( + `Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`) && + it('should support single-slot transclusion', async(() => { + let ng2ComponentAInstance: Ng2ComponentA; + let ng2ComponentBInstance: Ng2ComponentB; - // Define `ng1Component` - const ng1Component: - angular.IComponent = {template: 'ng1(
)', transclude: true}; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: 'ng1(
)', + transclude: true + }; - // Define `Ng1ComponentFacade` - @Directive({selector: 'ng1'}) - class Ng1ComponentFacade extends UpgradeComponent { - constructor(elementRef: ElementRef, injector: Injector) { - super('ng1', elementRef, injector); - } - } + // Define `Ng1ComponentFacade` + @Directive({selector: 'ng1'}) + class Ng1ComponentFacade extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1', elementRef, injector); + } + } - // Define `Ng2Component` - @Component({ - selector: 'ng2A', - template: 'ng2A({{ value }} | )' - }) - class Ng2ComponentA { - value = 'foo'; - showB = false; - constructor() { ng2ComponentAInstance = this; } - } + // Define `Ng2Component` + @Component({ + selector: 'ng2A', + template: 'ng2A({{ value }} | )' + }) + class Ng2ComponentA { + value = 'foo'; + showB = false; + constructor() { ng2ComponentAInstance = this; } + } - @Component({selector: 'ng2B', template: 'ng2B({{ value }})'}) - class Ng2ComponentB { - value = 'bar'; - constructor() { ng2ComponentBInstance = this; } - } + @Component({selector: 'ng2B', template: 'ng2B({{ value }})'}) + class Ng2ComponentB { + value = 'bar'; + constructor() { ng2ComponentBInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2A', downgradeComponent({component: Ng2ComponentA})); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2A', downgradeComponent({component: Ng2ComponentA})); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule, UpgradeModule], - declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB], - entryComponents: [Ng2ComponentA] - }) - class Ng2Module { - ngDoBootstrap() {} - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB], + entryComponents: [Ng2ComponentA] + }) + class Ng2Module { + ngDoBootstrap() {} + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { - expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))'); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { + expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))'); - ng2ComponentAInstance.value = 'baz'; - ng2ComponentAInstance.showB = true; - $digest(adapter); + ng2ComponentAInstance.value = 'baz'; + ng2ComponentAInstance.showB = true; + $digest(adapter); - expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(bar)))'); + expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(bar)))'); - ng2ComponentBInstance.value = 'qux'; - $digest(adapter); + ng2ComponentBInstance.value = 'qux'; + $digest(adapter); - expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(qux)))'); - }); - })); + expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(qux)))'); + }); + })); it('should support single-slot transclusion with fallback content', async(() => { let ng1ControllerInstances: any[] = []; @@ -2525,28 +2531,30 @@ withEachNg1Version(() => { }); })); - it('should support structural directives in transcluded content', async(() => { - let ng2ComponentInstance: Ng2Component; + fixmeIvy( + `Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`) && + it('should support structural directives in transcluded content', async(() => { + let ng2ComponentInstance: Ng2Component; - // Define `ng1Component` - const ng1Component: angular.IComponent = { - template: - 'ng1(x(
) | default(
))', - transclude: {slotX: 'contentX'} - }; + // Define `ng1Component` + const ng1Component: angular.IComponent = { + template: + 'ng1(x(
) | default(
))', + transclude: {slotX: 'contentX'} + }; - // Define `Ng1ComponentFacade` - @Directive({selector: 'ng1'}) - class Ng1ComponentFacade extends UpgradeComponent { - constructor(elementRef: ElementRef, injector: Injector) { - super('ng1', elementRef, injector); - } - } + // Define `Ng1ComponentFacade` + @Directive({selector: 'ng1'}) + class Ng1ComponentFacade extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1', elementRef, injector); + } + } - // Define `Ng2Component` - @Component({ - selector: 'ng2', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: ` ng2(
{{ x }}1
@@ -2555,49 +2563,53 @@ withEachNg1Version(() => {
{{ y }}2
)` - }) - class Ng2Component { - x = 'foo'; - y = 'bar'; - show = true; - constructor() { ng2ComponentInstance = this; } - } + }) + class Ng2Component { + x = 'foo'; + y = 'bar'; + show = true; + constructor() { ng2ComponentInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .component('ng1', ng1Component) - .directive('ng2', downgradeComponent({component: Ng2Component})); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .component('ng1', ng1Component) + .directive('ng2', downgradeComponent({component: Ng2Component})); - // Define `Ng2Module` - @NgModule({ - imports: [BrowserModule, UpgradeModule], - declarations: [Ng1ComponentFacade, Ng2Component], - entryComponents: [Ng2Component], - schemas: [NO_ERRORS_SCHEMA] - }) - class Ng2Module { - ngDoBootstrap() {} - } + // Define `Ng2Module` + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng1ComponentFacade, Ng2Component], + entryComponents: [Ng2Component], + schemas: [NO_ERRORS_SCHEMA] + }) + class Ng2Module { + ngDoBootstrap() {} + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { - expect(multiTrim(element.textContent, true)).toBe('ng2(ng1(x(foo1)|default(bar2)))'); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(foo1)|default(bar2)))'); - ng2ComponentInstance.x = 'baz'; - ng2ComponentInstance.y = 'qux'; - ng2ComponentInstance.show = false; - $digest(adapter); + ng2ComponentInstance.x = 'baz'; + ng2ComponentInstance.y = 'qux'; + ng2ComponentInstance.show = false; + $digest(adapter); - expect(multiTrim(element.textContent, true)).toBe('ng2(ng1(x(baz2)|default(qux1)))'); + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(baz2)|default(qux1)))'); - ng2ComponentInstance.show = true; - $digest(adapter); + ng2ComponentInstance.show = true; + $digest(adapter); - expect(multiTrim(element.textContent, true)).toBe('ng2(ng1(x(baz1)|default(qux2)))'); - }); - })); + expect(multiTrim(element.textContent, true)) + .toBe('ng2(ng1(x(baz1)|default(qux2)))'); + }); + })); }); describe('lifecycle hooks', () => { @@ -3324,98 +3336,103 @@ withEachNg1Version(() => { })); - it('should call `$onDestroy()` on controller', async(() => { - const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA'); - const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB'); + fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && + it('should call `$onDestroy()` on controller', async(() => { + const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA'); + const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB'); - // Define `ng1Directive` - const ng1DirectiveA: angular.IDirective = { - template: 'ng1A', - scope: {}, - bindToController: false, - controllerAs: '$ctrl', - controller: class {$onDestroy() { controllerOnDestroyA(); }} - }; + // Define `ng1Directive` + const ng1DirectiveA: angular.IDirective = { + template: 'ng1A', + scope: {}, + bindToController: false, + controllerAs: '$ctrl', + controller: class {$onDestroy() { controllerOnDestroyA(); }} + }; - const ng1DirectiveB: angular.IDirective = { - template: 'ng1B', - scope: {}, - bindToController: true, - controllerAs: '$ctrl', - controller: - class {constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; }} - }; + const ng1DirectiveB: angular.IDirective = { + template: 'ng1B', + scope: {}, + bindToController: true, + controllerAs: '$ctrl', + controller: + class {constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; }} + }; - // Define `Ng1ComponentFacade` - @Directive({selector: 'ng1A'}) - class Ng1ComponentAFacade extends UpgradeComponent { - constructor(elementRef: ElementRef, injector: Injector) { - super('ng1A', elementRef, injector); - } - } + // Define `Ng1ComponentFacade` + @Directive({selector: 'ng1A'}) + class Ng1ComponentAFacade extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1A', elementRef, injector); + } + } - @Directive({selector: 'ng1B'}) - class Ng1ComponentBFacade extends UpgradeComponent { - constructor(elementRef: ElementRef, injector: Injector) { - super('ng1B', elementRef, injector); - } - } + @Directive({selector: 'ng1B'}) + class Ng1ComponentBFacade extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1B', elementRef, injector); + } + } - // Define `Ng2Component` - @Component( - {selector: 'ng2', template: '
|
'}) - class Ng2Component { - // TODO(issue/24571): remove '!'. - @Input() show !: boolean; - } + // Define `Ng2Component` + @Component({ + selector: 'ng2', + template: '
|
' + }) + class Ng2Component { + // TODO(issue/24571): remove '!'. + @Input() show !: boolean; + } - // Define `ng1Module` - const ng1Module = angular.module('ng1Module', []) - .directive('ng1A', () => ng1DirectiveA) - .directive('ng1B', () => ng1DirectiveB) - .directive('ng2', downgradeComponent({component: Ng2Component})); + // Define `ng1Module` + const ng1Module = + angular.module('ng1Module', []) + .directive('ng1A', () => ng1DirectiveA) + .directive('ng1B', () => ng1DirectiveB) + .directive('ng2', downgradeComponent({component: Ng2Component})); - // Define `Ng2Module` - @NgModule({ - declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component], - entryComponents: [Ng2Component], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + // Define `Ng2Module` + @NgModule({ + declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - // Bootstrap - const element = html(''); + // Bootstrap + const element = html(''); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { - const $rootScope = adapter.$injector.get('$rootScope') as angular.IRootScopeService; + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { + const $rootScope = + adapter.$injector.get('$rootScope') as angular.IRootScopeService; - expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); - expect(controllerOnDestroyA).not.toHaveBeenCalled(); - expect(controllerOnDestroyB).not.toHaveBeenCalled(); + expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); + expect(controllerOnDestroyA).not.toHaveBeenCalled(); + expect(controllerOnDestroyB).not.toHaveBeenCalled(); - $rootScope.$apply('destroyFromNg1 = true'); + $rootScope.$apply('destroyFromNg1 = true'); - expect(multiTrim(document.body.textContent)).toBe(''); - expect(controllerOnDestroyA).toHaveBeenCalled(); - expect(controllerOnDestroyB).toHaveBeenCalled(); + expect(multiTrim(document.body.textContent)).toBe(''); + expect(controllerOnDestroyA).toHaveBeenCalled(); + expect(controllerOnDestroyB).toHaveBeenCalled(); - controllerOnDestroyA.calls.reset(); - controllerOnDestroyB.calls.reset(); - $rootScope.$apply('destroyFromNg1 = false'); + controllerOnDestroyA.calls.reset(); + controllerOnDestroyB.calls.reset(); + $rootScope.$apply('destroyFromNg1 = false'); - expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); - expect(controllerOnDestroyA).not.toHaveBeenCalled(); - expect(controllerOnDestroyB).not.toHaveBeenCalled(); + expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); + expect(controllerOnDestroyA).not.toHaveBeenCalled(); + expect(controllerOnDestroyB).not.toHaveBeenCalled(); - $rootScope.$apply('destroyFromNg2 = true'); + $rootScope.$apply('destroyFromNg2 = true'); - expect(multiTrim(document.body.textContent)).toBe(''); - expect(controllerOnDestroyA).toHaveBeenCalled(); - expect(controllerOnDestroyB).toHaveBeenCalled(); - }); - })); + expect(multiTrim(document.body.textContent)).toBe(''); + expect(controllerOnDestroyA).toHaveBeenCalled(); + expect(controllerOnDestroyB).toHaveBeenCalled(); + }); + })); it('should not call `$onDestroy()` on scope', async(() => { const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy'); @@ -3948,22 +3965,23 @@ withEachNg1Version(() => { }); })); - it('should support ng2 > ng1 > ng2 (with inputs/outputs)', fakeAsync(() => { - let ng2ComponentAInstance: Ng2ComponentA; - let ng2ComponentBInstance: Ng2ComponentB; - let ng1ControllerXInstance: Ng1ControllerX; + fixmeIvy('FW-642: ASSERTION ERROR: Slot should have been initialized to NO_CHANGE') && + it('should support ng2 > ng1 > ng2 (with inputs/outputs)', fakeAsync(() => { + let ng2ComponentAInstance: Ng2ComponentA; + let ng2ComponentBInstance: Ng2ComponentB; + let ng1ControllerXInstance: Ng1ControllerX; - // Define `ng1Component` - class Ng1ControllerX { - // TODO(issue/24571): remove '!'. - ng1XInputA !: string; - ng1XInputB: any; - ng1XInputC: any; + // Define `ng1Component` + class Ng1ControllerX { + // TODO(issue/24571): remove '!'. + ng1XInputA !: string; + ng1XInputB: any; + ng1XInputC: any; - constructor() { ng1ControllerXInstance = this; } - } - const ng1Component: angular.IComponent = { - template: ` + constructor() { ng1ControllerXInstance = this; } + } + const ng1Component: angular.IComponent = { + template: ` ng1X({{ $ctrl.ng1XInputA }}, {{ $ctrl.ng1XInputB.value }}, {{ $ctrl.ng1XInputC.value }}) | { (ng2-b-output-c)="$ctrl.ng1XInputC = {value: $event}"> `, - bindings: { - ng1XInputA: '@', - ng1XInputB: '<', - ng1XInputC: '=', - ng1XOutputA: '&', - ng1XOutputB: '&' - }, - controller: Ng1ControllerX - }; + bindings: { + ng1XInputA: '@', + ng1XInputB: '<', + ng1XInputC: '=', + ng1XOutputA: '&', + ng1XOutputB: '&' + }, + controller: Ng1ControllerX + }; - // Define `Ng1ComponentFacade` - @Directive({selector: 'ng1X'}) - class Ng1ComponentXFacade extends UpgradeComponent { - // TODO(issue/24571): remove '!'. - @Input() ng1XInputA !: string; - @Input() ng1XInputB: any; - @Input() ng1XInputC: any; - // TODO(issue/24571): remove '!'. - @Output() ng1XInputCChange !: EventEmitter; - // TODO(issue/24571): remove '!'. - @Output() ng1XOutputA !: EventEmitter; - // TODO(issue/24571): remove '!'. - @Output() ng1XOutputB !: EventEmitter; + // Define `Ng1ComponentFacade` + @Directive({selector: 'ng1X'}) + class Ng1ComponentXFacade extends UpgradeComponent { + // TODO(issue/24571): remove '!'. + @Input() ng1XInputA !: string; + @Input() ng1XInputB: any; + @Input() ng1XInputC: any; + // TODO(issue/24571): remove '!'. + @Output() ng1XInputCChange !: EventEmitter; + // TODO(issue/24571): remove '!'. + @Output() ng1XOutputA !: EventEmitter; + // TODO(issue/24571): remove '!'. + @Output() ng1XOutputB !: EventEmitter; - constructor(elementRef: ElementRef, injector: Injector) { - super('ng1X', elementRef, injector); - } - } + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1X', elementRef, injector); + } + } - // Define `Ng2Component` - @Component({ - selector: 'ng2-a', - template: ` + // Define `Ng2Component` + @Component({ + selector: 'ng2-a', + template: ` ng2A({{ ng2ADataA.value }}, {{ ng2ADataB.value }}, {{ ng2ADataC.value }}) | { on-ng1XOutputB="ng2ADataB.value = $event"> ` - }) - class Ng2ComponentA { - ng2ADataA = {value: 'foo'}; - ng2ADataB = {value: 'bar'}; - ng2ADataC = {value: 'baz'}; + }) + class Ng2ComponentA { + ng2ADataA = {value: 'foo'}; + ng2ADataB = {value: 'bar'}; + ng2ADataC = {value: 'baz'}; - constructor() { ng2ComponentAInstance = this; } - } + constructor() { ng2ComponentAInstance = this; } + } - @Component({selector: 'ng2-b', template: 'ng2B({{ ng2BInputA }}, {{ ng2BInputC }})'}) - class Ng2ComponentB { - @Input('ng2BInput1') ng2BInputA: any; - @Input() ng2BInputC: any; - @Output() ng2BOutputC = new EventEmitter(); + @Component({selector: 'ng2-b', template: 'ng2B({{ ng2BInputA }}, {{ ng2BInputC }})'}) + class Ng2ComponentB { + @Input('ng2BInput1') ng2BInputA: any; + @Input() ng2BInputC: any; + @Output() ng2BOutputC = new EventEmitter(); - constructor() { ng2ComponentBInstance = this; } - } + constructor() { ng2ComponentBInstance = this; } + } - // Define `ng1Module` - const ng1Module = angular.module('ng1', []) - .component('ng1X', ng1Component) - .directive('ng2A', downgradeComponent({component: Ng2ComponentA})) - .directive('ng2B', downgradeComponent({component: Ng2ComponentB})); + // Define `ng1Module` + const ng1Module = + angular.module('ng1', []) + .component('ng1X', ng1Component) + .directive('ng2A', downgradeComponent({component: Ng2ComponentA})) + .directive('ng2B', downgradeComponent({component: Ng2ComponentB})); - // Define `Ng2Module` - @NgModule({ - declarations: [Ng1ComponentXFacade, Ng2ComponentA, Ng2ComponentB], - entryComponents: [Ng2ComponentA, Ng2ComponentB], - imports: [BrowserModule, UpgradeModule], - schemas: [NO_ERRORS_SCHEMA], - }) - class Ng2Module { - ngDoBootstrap() {} - } + // Define `Ng2Module` + @NgModule({ + declarations: [Ng1ComponentXFacade, Ng2ComponentA, Ng2ComponentB], + entryComponents: [Ng2ComponentA, Ng2ComponentB], + imports: [BrowserModule, UpgradeModule], + schemas: [NO_ERRORS_SCHEMA], + }) + class Ng2Module { + ngDoBootstrap() {} + } - // Bootstrap - const element = html(``); + // Bootstrap + const element = html(``); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { - // Initial value propagation. - // (ng2A > ng1X > ng2B) - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo, baz)'); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { + // Initial value propagation. + // (ng2A > ng1X > ng2B) + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo, baz)'); - // Update `ng2BInputA`/`ng2BInputC`. - // (Should not propagate upwards.) - ng2ComponentBInstance.ng2BInputA = 'foo2'; - ng2ComponentBInstance.ng2BInputC = 'baz2'; - $digest(adapter); - tick(); + // Update `ng2BInputA`/`ng2BInputC`. + // (Should not propagate upwards.) + ng2ComponentBInstance.ng2BInputA = 'foo2'; + ng2ComponentBInstance.ng2BInputC = 'baz2'; + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo2, baz2)'); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo2, baz2)'); - // Emit from `ng2BOutputC`. - // (Should propagate all the way up to `ng1ADataC` and back all the way down to - // `ng2BInputC`.) - ng2ComponentBInstance.ng2BOutputC.emit('baz3'); - $digest(adapter); - tick(); + // Emit from `ng2BOutputC`. + // (Should propagate all the way up to `ng1ADataC` and back all the way down to + // `ng2BInputC`.) + ng2ComponentBInstance.ng2BOutputC.emit('baz3'); + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo, bar, baz3) | ng1X(foo, bar, baz3) | ng2B(foo2, baz3)'); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo, bar, baz3) | ng1X(foo, bar, baz3) | ng2B(foo2, baz3)'); - // Update `ng1XInputA`/`ng1XInputB`. - // (Should not propagate upwards, only downwards.) - ng1ControllerXInstance.ng1XInputA = 'foo4'; - ng1ControllerXInstance.ng1XInputB = {value: 'bar4'}; - $digest(adapter); - tick(); + // Update `ng1XInputA`/`ng1XInputB`. + // (Should not propagate upwards, only downwards.) + ng1ControllerXInstance.ng1XInputA = 'foo4'; + ng1ControllerXInstance.ng1XInputB = {value: 'bar4'}; + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo, bar, baz3) | ng1X(foo4, bar4, baz3) | ng2B(foo4, baz3)'); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo, bar, baz3) | ng1X(foo4, bar4, baz3) | ng2B(foo4, baz3)'); - // Update `ng1XInputC`. - // (Should propagate upwards and downwards.) - ng1ControllerXInstance.ng1XInputC = {value: 'baz5'}; - $digest(adapter); - tick(); + // Update `ng1XInputC`. + // (Should propagate upwards and downwards.) + ng1ControllerXInstance.ng1XInputC = {value: 'baz5'}; + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo, bar, baz5) | ng1X(foo4, bar4, baz5) | ng2B(foo4, baz5)'); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo, bar, baz5) | ng1X(foo4, bar4, baz5) | ng2B(foo4, baz5)'); - // Update a property on `ng1XInputC`. - // (Should propagate upwards and downwards.) - ng1ControllerXInstance.ng1XInputC.value = 'baz6'; - $digest(adapter); - tick(); + // Update a property on `ng1XInputC`. + // (Should propagate upwards and downwards.) + ng1ControllerXInstance.ng1XInputC.value = 'baz6'; + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo, bar, baz6) | ng1X(foo4, bar4, baz6) | ng2B(foo4, baz6)'); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo, bar, baz6) | ng1X(foo4, bar4, baz6) | ng2B(foo4, baz6)'); - // Emit from `ng1XOutputA`. - // (Should propagate upwards to `ng1ADataA` and back all the way down to `ng2BInputA`.) - (ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'}); - $digest(adapter); - tick(); + // Emit from `ng1XOutputA`. + // (Should propagate upwards to `ng1ADataA` and back all the way down to + // `ng2BInputA`.) + (ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'}); + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo7, bar, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)'); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo7, bar, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)'); - // Emit from `ng1XOutputB`. - // (Should propagate upwards to `ng1ADataB`, but not downwards, - // since `ng1XInputB` has been re-assigned (i.e. `ng2ADataB !== ng1XInputB`).) - (ng1ControllerXInstance as any).ng1XOutputB('bar8'); - $digest(adapter); - tick(); + // Emit from `ng1XOutputB`. + // (Should propagate upwards to `ng1ADataB`, but not downwards, + // since `ng1XInputB` has been re-assigned (i.e. `ng2ADataB !== ng1XInputB`).) + (ng1ControllerXInstance as any).ng1XOutputB('bar8'); + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo7, bar8, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)'); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo7, bar8, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)'); - // Update `ng2ADataA`/`ng2ADataB`/`ng2ADataC`. - // (Should propagate everywhere.) - ng2ComponentAInstance.ng2ADataA = {value: 'foo9'}; - ng2ComponentAInstance.ng2ADataB = {value: 'bar9'}; - ng2ComponentAInstance.ng2ADataC = {value: 'baz9'}; - $digest(adapter); - tick(); + // Update `ng2ADataA`/`ng2ADataB`/`ng2ADataC`. + // (Should propagate everywhere.) + ng2ComponentAInstance.ng2ADataA = {value: 'foo9'}; + ng2ComponentAInstance.ng2ADataB = {value: 'bar9'}; + ng2ComponentAInstance.ng2ADataC = {value: 'baz9'}; + $digest(adapter); + tick(); - expect(multiTrim(document.body.textContent)) - .toBe('ng2A(foo9, bar9, baz9) | ng1X(foo9, bar9, baz9) | ng2B(foo9, baz9)'); - }); - })); + expect(multiTrim(document.body.textContent)) + .toBe('ng2A(foo9, bar9, baz9) | ng1X(foo9, bar9, baz9) | ng2B(foo9, baz9)'); + }); + })); it('should support ng2 > ng1 > ng2 > ng1 (with `require`)', async(() => { // Define `ng1Component`