test(upgrade): disable failing ngUpgrade tests when in ivy mode (#27132)

PR Close #27132
This commit is contained in:
Pete Bacon Darwin 2018-11-22 21:51:55 +00:00 committed by Jason Aden
parent a4462c24fa
commit bc0fbfc93e
8 changed files with 3642 additions and 3474 deletions

View File

@ -7,6 +7,7 @@
*/ */
import {Compiler, Component, ComponentFactory, Injector, NgModule, TestabilityRegistry} from '@angular/core'; import {Compiler, Component, ComponentFactory, Injector, NgModule, TestabilityRegistry} from '@angular/core';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {fixmeIvy} from '@angular/private/testing';
import * as angular from '@angular/upgrade/src/common/angular1'; import * as angular from '@angular/upgrade/src/common/angular1';
import {DowngradeComponentAdapter, groupNodesBySelector} from '@angular/upgrade/src/common/downgrade_component_adapter'; import {DowngradeComponentAdapter, groupNodesBySelector} from '@angular/upgrade/src/common/downgrade_component_adapter';
@ -177,26 +178,28 @@ withEachNg1Version(() => {
beforeEach(() => registry.unregisterAllApplications()); beforeEach(() => registry.unregisterAllApplications());
afterEach(() => 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); let registry = TestBed.get(TestabilityRegistry);
adapter.createComponent([]); adapter.createComponent([]);
expect(registry.getAllTestabilities().length).toEqual(1); expect(registry.getAllTestabilities().length).toEqual(1);
adapter = getAdaptor(); // get a new adaptor to creat a new component adapter = getAdaptor(); // get a new adaptor to creat a new component
adapter.createComponent([]); adapter.createComponent([]);
expect(registry.getAllTestabilities().length).toEqual(2); expect(registry.getAllTestabilities().length).toEqual(2);
}); });
it('should remove the testability hook when destroy a component', () => { fixmeIvy('FW-561: Runtime compiler is not loaded') &&
const registry = TestBed.get(TestabilityRegistry); it('should remove the testability hook when destroy a component', () => {
expect(registry.getAllTestabilities().length).toEqual(0); const registry = TestBed.get(TestabilityRegistry);
adapter.createComponent([]); expect(registry.getAllTestabilities().length).toEqual(0);
expect(registry.getAllTestabilities().length).toEqual(1); adapter.createComponent([]);
adapter.registerCleanup(); expect(registry.getAllTestabilities().length).toEqual(1);
element.remove !(); adapter.registerCleanup();
expect(registry.getAllTestabilities().length).toEqual(0); element.remove !();
}); expect(registry.getAllTestabilities().length).toEqual(0);
});
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, Injector, Input, NgModule, NgZone, Sim
import {async} from '@angular/core/testing'; import {async} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {fixmeIvy} from '@angular/private/testing';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import * as angular from '@angular/upgrade/static/src/common/angular1'; 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(() => { fixmeIvy(
let appComponent: AppComponent; '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('<my-app></my-app>');
let appComponent: AppComponent;
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'}) @Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
class AppComponent { class AppComponent {
// TODO(issue/24571): remove '!'. value?: number;
value !: number; constructor() { appComponent = this; }
constructor() { appComponent = this; } }
}
@Component({ @Component({
selector: 'my-child', selector: 'my-child',
template: '<div>{{ valueFromPromise }}</div>', template: '<div>{{ valueFromPromise }}</div>',
}) })
class ChildComponent { class ChildComponent {
// TODO(issue/24571): remove '!'. valueFromPromise?: number;
valueFromPromise !: number; @Input()
@Input() set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
constructor(private zone: NgZone) {} constructor(private zone: NgZone) {}
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes['value'].isFirstChange()) return; if (changes['value'].isFirstChange()) return;
this.zone.onMicrotaskEmpty.subscribe( // HACK(ivy): Using setTimeout allows this test to pass but hides the ivy renderer
() => { expect(element.textContent).toEqual('5'); }); // 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({ @NgModule({
declarations: [AppComponent, ChildComponent], declarations: [AppComponent, ChildComponent],
entryComponents: [AppComponent], entryComponents: [AppComponent],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'myApp', downgradeComponent({component: AppComponent})); 'myApp', downgradeComponent({component: AppComponent}));
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
const element = html('<my-app></my-app>'); appComponent.value = 5;
});
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { }));
appComponent.value = 5;
});
}));
// This test demonstrates https://github.com/angular/angular/issues/6385 // This test demonstrates https://github.com/angular/angular/issues/6385
// which was invalidly fixed by https://github.com/angular/angular/pull/6386 // which was invalidly fixed by https://github.com/angular/angular/pull/6386

View File

@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, Injector, Input, NgModule, destroyPlat
import {async} from '@angular/core/testing'; import {async} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {fixmeIvy} from '@angular/private/testing';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import * as angular from '@angular/upgrade/static/src/common/angular1'; import * as angular from '@angular/upgrade/static/src/common/angular1';
@ -21,78 +22,82 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform()); beforeEach(() => destroyPlatform());
afterEach(() => 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) // the ng2 component that will be used in ng1 (downgraded)
@Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`}) @Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`})
class Ng2Component { class Ng2Component {
prop = 'NG2'; prop = 'NG2';
ngContent = 'ng2-content'; ngContent = 'ng2-content';
} }
// our upgrade module to host the component to downgrade // our upgrade module to host the component to downgrade
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component] entryComponents: [Ng2Component]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// the ng1 app module that will consume the downgraded component // the ng1 app module that will consume the downgraded component
const ng1Module = angular const ng1Module = angular
.module('ng1', []) .module('ng1', [])
// create an ng1 facade of the ng2 component // create an ng1 facade of the ng2 component
.directive('ng2', downgradeComponent({component: Ng2Component})) .directive('ng2', downgradeComponent({component: Ng2Component}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
$rootScope['prop'] = 'NG1'; $rootScope['prop'] = 'NG1';
$rootScope['ngContent'] = 'ng1-content'; $rootScope['ngContent'] = 'ng1-content';
}); });
const element = html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>'); const element =
html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]'); expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]');
}); });
})); }));
it('should correctly project structural directives', async(() => { fixmeIvy('FW-714: ng1 projected content is not being rendered') &&
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'}) it('should correctly project structural directives', async(() => {
class Ng2Component { @Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
// TODO(issue/24571): remove '!'. class Ng2Component {
@Input() itemId !: string; // TODO(issue/24571): remove '!'.
} @Input() itemId !: string;
}
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component] entryComponents: [Ng2Component]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = angular.module('ng1', []) const ng1Module =
.directive('ng2', downgradeComponent({component: Ng2Component})) angular.module('ng1', [])
.run(($rootScope: angular.IRootScopeService) => { .directive('ng2', downgradeComponent({component: Ng2Component}))
$rootScope['items'] = [ .run(($rootScope: angular.IRootScopeService) => {
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, $rootScope['items'] = [
{id: 'c', subitems: [7, 8, 9]} {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
]; {id: 'c', subitems: [7, 8, 9]}
}); ];
});
const element = html(` const element = html(`
<ng2 ng-repeat="item in items" [item-id]="item.id"> <ng2 ng-repeat="item in items" [item-id]="item.id">
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div> <div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
</ng2> </ng2>
`); `);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
}); });
})); }));
it('should instantiate ng1 in ng2 template and project content', async(() => { 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({ @Component({
selector: 'ng2', selector: 'ng2',
template: '2a(<ng-content select=".ng1a"></ng-content>)' + template: '2a(<ng-content select=".ng1a"></ng-content>)' +
'2b(<ng-content select=".ng1b"></ng-content>)' '2b(<ng-content select=".ng1b"></ng-content>)'
}) })
class Ng2Component { class Ng2Component {
constructor() {} constructor() {}
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component})); 'ng2', downgradeComponent({component: Ng2Component}));
// The ng-if on one of the projected children is here to make sure // 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. // the correct slot is targeted even with structural directives in play.
const element = html( const element = html(
'<ng2><div ng-if="true" class="ng1a">1a</div><div' + '<ng2><div ng-if="true" class="ng1a">1a</div><div' +
' class="ng1b">1b</div></ng2>'); ' class="ng1b">1b</div></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); expect(document.body.textContent).toEqual('2a(1a)2b(1b)');
}); });
})); }));
}); });
}); });

View File

@ -10,6 +10,7 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Compiler, Component, Compone
import {async, fakeAsync, tick} from '@angular/core/testing'; import {async, fakeAsync, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {fixmeIvy} from '@angular/private/testing';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import * as angular from '@angular/upgrade/static/src/common/angular1'; import * as angular from '@angular/upgrade/static/src/common/angular1';
@ -21,104 +22,106 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform()); beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform()); afterEach(() => destroyPlatform());
it('should bind properties, events', async(() => { fixmeIvy('FW-716: Error: [$rootScope:inprog] $digest already in progress') &&
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { it('should bind properties, events', async(() => {
$rootScope['name'] = 'world'; const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['dataA'] = 'A'; $rootScope['name'] = 'world';
$rootScope['dataB'] = 'B'; $rootScope['dataA'] = 'A';
$rootScope['modelA'] = 'initModelA'; $rootScope['dataB'] = 'B';
$rootScope['modelB'] = 'initModelB'; $rootScope['modelA'] = 'initModelA';
$rootScope['eventA'] = '?'; $rootScope['modelB'] = 'initModelB';
$rootScope['eventB'] = '?'; $rootScope['eventA'] = '?';
}); $rootScope['eventB'] = '?';
});
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
outputs: [ outputs: [
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange' 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
], 'twoWayBEmitter: twoWayBChange'
template: 'ignore: {{ignore}}; ' + ],
'literal: {{literal}}; interpolate: {{interpolate}}; ' + template: 'ignore: {{ignore}}; ' +
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' + 'literal: {{literal}}; interpolate: {{interpolate}}; ' +
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})' 'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
}) 'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
class Ng2Component implements OnChanges { })
ngOnChangesCount = 0; class Ng2Component implements OnChanges {
ignore = '-'; ngOnChangesCount = 0;
literal = '?'; ignore = '-';
interpolate = '?'; literal = '?';
oneWayA = '?'; interpolate = '?';
oneWayB = '?'; oneWayA = '?';
twoWayA = '?'; oneWayB = '?';
twoWayB = '?'; twoWayA = '?';
eventA = new EventEmitter(); twoWayB = '?';
eventB = new EventEmitter(); eventA = new EventEmitter();
twoWayAEmitter = new EventEmitter(); eventB = new EventEmitter();
twoWayBEmitter = new EventEmitter(); twoWayAEmitter = new EventEmitter();
twoWayBEmitter = new EventEmitter();
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
const assert = (prop: string, value: any) => { const assert = (prop: string, value: any) => {
const propVal = (this as any)[prop]; const propVal = (this as any)[prop];
if (propVal != value) { if (propVal != value) {
throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`); 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({ ng1Module.directive('ng2', downgradeComponent({
component: Ng2Component, component: Ng2Component,
})); }));
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const element = html(` const element = html(`
<div> <div>
<ng2 literal="Text" interpolate="Hello {{name}}" <ng2 literal="Text" interpolate="Hello {{name}}"
bind-one-way-a="dataA" [one-way-b]="dataB" bind-one-way-a="dataA" [one-way-b]="dataB"
@ -127,23 +130,23 @@ withEachNg1Version(() => {
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}}; | modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
</div>`); </div>`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual( .toEqual(
'ignore: -; ' + 'ignore: -; ' +
'literal: Text; interpolate: Hello world; ' + 'literal: Text; interpolate: Hello world; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
$apply(upgrade, 'name = "everyone"'); $apply(upgrade, 'name = "everyone"');
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual( .toEqual(
'ignore: -; ' + 'ignore: -; ' +
'literal: Text; interpolate: Hello everyone; ' + 'literal: Text; interpolate: Hello everyone; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
}); });
})); }));
it('should bind properties to onpush components', async(() => { it('should bind properties to onpush components', async(() => {
const ng1Module = angular.module('ng1', []).run( const ng1Module = angular.module('ng1', []).run(
@ -186,57 +189,58 @@ withEachNg1Version(() => {
}); });
})); }));
it('should support two-way binding and event listener', async(() => { fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') &&
const listenerSpy = jasmine.createSpy('$rootScope.listener'); it('should support two-way binding and event listener', async(() => {
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { const listenerSpy = jasmine.createSpy('$rootScope.listener');
$rootScope['value'] = 'world'; const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['listener'] = listenerSpy; $rootScope['value'] = 'world';
}); $rootScope['listener'] = listenerSpy;
});
@Component({selector: 'ng2', template: `model: {{model}};`}) @Component({selector: 'ng2', template: `model: {{model}};`})
class Ng2Component implements OnChanges { class Ng2Component implements OnChanges {
ngOnChangesCount = 0; ngOnChangesCount = 0;
@Input() model = '?'; @Input() model = '?';
@Output() modelChange = new EventEmitter(); @Output() modelChange = new EventEmitter();
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
switch (this.ngOnChangesCount++) { switch (this.ngOnChangesCount++) {
case 0: case 0:
expect(changes.model.currentValue).toBe('world'); expect(changes.model.currentValue).toBe('world');
this.modelChange.emit('newC'); this.modelChange.emit('newC');
break; break;
case 1: case 1:
expect(changes.model.currentValue).toBe('newC'); expect(changes.model.currentValue).toBe('newC');
break; break;
default: default:
throw new Error('Called too many times! ' + JSON.stringify(changes)); throw new Error('Called too many times! ' + JSON.stringify(changes));
}
}
} }
}
}
ng1Module.directive('ng2', downgradeComponent({component: Ng2Component})); ng1Module.directive('ng2', downgradeComponent({component: Ng2Component}));
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const element = html(` const element = html(`
<div> <div>
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2> <ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
| value: {{value}} | value: {{value}}
</div> </div>
`); `);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
expect(listenerSpy).toHaveBeenCalledWith('newC'); expect(listenerSpy).toHaveBeenCalledWith('newC');
}); });
})); }));
it('should run change-detection on every digest (by default)', async(() => { it('should run change-detection on every digest (by default)', async(() => {
let ng2Component: Ng2Component; let ng2Component: Ng2Component;
@ -400,65 +404,66 @@ withEachNg1Version(() => {
}); });
})); }));
it('should initialize inputs in time for `ngOnChanges`', async(() => { fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') &&
@Component({ it('should initialize inputs in time for `ngOnChanges`', async(() => {
selector: 'ng2', @Component({
template: ` selector: 'ng2',
template: `
ngOnChangesCount: {{ ngOnChangesCount }} | ngOnChangesCount: {{ ngOnChangesCount }} |
firstChangesCount: {{ firstChangesCount }} | firstChangesCount: {{ firstChangesCount }} |
initialValue: {{ initialValue }}` initialValue: {{ initialValue }}`
}) })
class Ng2Component implements OnChanges { class Ng2Component implements OnChanges {
ngOnChangesCount = 0; ngOnChangesCount = 0;
firstChangesCount = 0; firstChangesCount = 0;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
initialValue !: string; initialValue !: string;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() foo !: string; @Input() foo !: string;
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
this.ngOnChangesCount++; this.ngOnChangesCount++;
if (this.ngOnChangesCount === 1) { if (this.ngOnChangesCount === 1) {
this.initialValue = this.foo; this.initialValue = this.foo;
}
if (changes['foo'] && changes['foo'].isFirstChange()) {
this.firstChangesCount++;
}
}
} }
if (changes['foo'] && changes['foo'].isFirstChange()) { @NgModule({
this.firstChangesCount++; imports: [BrowserModule, UpgradeModule],
declarations: [Ng2Component],
entryComponents: [Ng2Component]
})
class Ng2Module {
ngDoBootstrap() {}
} }
}
}
@NgModule({ const ng1Module = angular.module('ng1', []).directive(
imports: [BrowserModule, UpgradeModule], 'ng2', downgradeComponent({component: Ng2Component}));
declarations: [Ng2Component],
entryComponents: [Ng2Component]
})
class Ng2Module {
ngDoBootstrap() {}
}
const ng1Module = angular.module('ng1', []).directive( const element = html(`
'ng2', downgradeComponent({component: Ng2Component}));
const element = html(`
<ng2 [foo]="'foo'"></ng2> <ng2 [foo]="'foo'"></ng2>
<ng2 foo="bar"></ng2> <ng2 foo="bar"></ng2>
<ng2 [foo]="'baz'" ng-if="true"></ng2> <ng2 [foo]="'baz'" ng-if="true"></ng2>
<ng2 foo="qux" ng-if="true"></ng2> <ng2 foo="qux" ng-if="true"></ng2>
`); `);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
const nodes = element.querySelectorAll('ng2'); const nodes = element.querySelectorAll('ng2');
const expectedTextWith = (value: string) => const expectedTextWith = (value: string) =>
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`; `ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo')); expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar')); expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz')); expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux')); expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
}); });
})); }));
it('should bind to ng-model', async(() => { it('should bind to ng-model', async(() => {
const ng1Module = angular.module('ng1', []).run( 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; let destroyed = false;
@Component({selector: 'ng2', template: 'test'}) @Component({selector: 'ng2', template: 'test'})
class Ng2Component implements OnDestroy { class Ng2Component implements OnDestroy {
ngOnDestroy() { destroyed = true; } ngOnDestroy() { destroyed = true; }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = const ng1Module =
angular.module('ng1', []) angular.module('ng1', [])
.directive( .directive(
'ng1', 'ng1',
() => { return {template: '<div ng-if="!destroyIt"><ng2></ng2></div>'}; }) () => { return {template: '<div ng-if="!destroyIt"><ng2></ng2></div>'}; })
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive('ng2', downgradeComponent({component: Ng2Component}));
const element = html('<ng1></ng1>'); const element = html('<ng1></ng1>');
platformBrowserDynamic().bootstrapModule(Ng2Module).then((ref) => { platformBrowserDynamic().bootstrapModule(Ng2Module).then((ref) => {
const adapter = ref.injector.get(UpgradeModule) as UpgradeModule; const adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
adapter.bootstrap(element, [ng1Module.name]); adapter.bootstrap(element, [ng1Module.name]);
expect(element.textContent).toContain('test'); expect(element.textContent).toContain('test');
expect(destroyed).toBe(false); expect(destroyed).toBe(false);
const $rootScope = adapter.$injector.get('$rootScope'); const $rootScope = adapter.$injector.get('$rootScope');
$rootScope.$apply('destroyIt = true'); $rootScope.$apply('destroyIt = true');
expect(element.textContent).not.toContain('test'); expect(element.textContent).not.toContain('test');
expect(destroyed).toBe(true); expect(destroyed).toBe(true);
}); });
})); }));
it('should properly run cleanup with multiple levels of nesting', async(() => { fixmeIvy('FW-642: ASSERTION ERROR: Slot should have been initialized to NO_CHANGE') &&
let destroyed = false; it('should properly run cleanup with multiple levels of nesting', async(() => {
let destroyed = false;
@Component({ @Component({
selector: 'ng2-outer', selector: 'ng2-outer',
template: '<div *ngIf="!destroyIt"><ng1></ng1></div>', template: '<div *ngIf="!destroyIt"><ng1></ng1></div>',
}) })
class Ng2OuterComponent { class Ng2OuterComponent {
@Input() destroyIt = false; @Input() destroyIt = false;
} }
@Component({selector: 'ng2-inner', template: 'test'}) @Component({selector: 'ng2-inner', template: 'test'})
class Ng2InnerComponent implements OnDestroy { class Ng2InnerComponent implements OnDestroy {
ngOnDestroy() { destroyed = true; } ngOnDestroy() { destroyed = true; }
} }
@Directive({selector: 'ng1'}) @Directive({selector: 'ng1'})
class Ng1ComponentFacade extends UpgradeComponent { class Ng1ComponentFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1', elementRef, injector); super('ng1', elementRef, injector);
} }
} }
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng1ComponentFacade, Ng2InnerComponent, Ng2OuterComponent], declarations: [Ng1ComponentFacade, Ng2InnerComponent, Ng2OuterComponent],
entryComponents: [Ng2InnerComponent, Ng2OuterComponent], entryComponents: [Ng2InnerComponent, Ng2OuterComponent],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = const ng1Module =
angular.module('ng1', []) angular.module('ng1', [])
.directive('ng1', () => ({template: '<ng2-inner></ng2-inner>'})) .directive('ng1', () => ({template: '<ng2-inner></ng2-inner>'}))
.directive('ng2Inner', downgradeComponent({component: Ng2InnerComponent})) .directive('ng2Inner', downgradeComponent({component: Ng2InnerComponent}))
.directive('ng2Outer', downgradeComponent({component: Ng2OuterComponent})); .directive('ng2Outer', downgradeComponent({component: Ng2OuterComponent}));
const element = html('<ng2-outer [destroy-it]="destroyIt"></ng2-outer>'); const element = html('<ng2-outer [destroy-it]="destroyIt"></ng2-outer>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(element.textContent).toBe('test'); expect(element.textContent).toBe('test');
expect(destroyed).toBe(false); expect(destroyed).toBe(false);
$apply(upgrade, 'destroyIt = true'); $apply(upgrade, 'destroyIt = true');
expect(element.textContent).toBe(''); expect(element.textContent).toBe('');
expect(destroyed).toBe(true); expect(destroyed).toBe(true);
}); });
})); }));
it('should work when compiled outside the dom (by fallback to the root ng2.injector)', it('should work when compiled outside the dom (by fallback to the root ng2.injector)',
async(() => { async(() => {
@ -704,86 +711,88 @@ withEachNg1Version(() => {
}); });
})); }));
it('should respect hierarchical dependency injection for ng2', async(() => { fixmeIvy('FW-714: ng1 projected content is not being rendered') &&
@Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'}) it('should respect hierarchical dependency injection for ng2', async(() => {
class ParentComponent { @Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'})
} class ParentComponent {
}
@Component({selector: 'child', template: 'child'}) @Component({selector: 'child', template: 'child'})
class ChildComponent { class ChildComponent {
constructor(parent: ParentComponent) {} constructor(parent: ParentComponent) {}
} }
@NgModule({ @NgModule({
declarations: [ParentComponent, ChildComponent], declarations: [ParentComponent, ChildComponent],
entryComponents: [ParentComponent, ChildComponent], entryComponents: [ParentComponent, ChildComponent],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = const ng1Module =
angular.module('ng1', []) angular.module('ng1', [])
.directive('parent', downgradeComponent({component: ParentComponent})) .directive('parent', downgradeComponent({component: ParentComponent}))
.directive('child', downgradeComponent({component: ChildComponent})); .directive('child', downgradeComponent({component: ChildComponent}));
const element = html('<parent><child></child></parent>'); const element = html('<parent><child></child></parent>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(document.body.textContent)).toBe('parent(child)'); 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: ''}) @Component({selector: 'ng2', template: ''})
class Ng2Component { class Ng2Component {
constructor(injector: Injector) { componentInjector = injector; } constructor(injector: Injector) { componentInjector = injector; }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
@Component({template: ''}) @Component({template: ''})
class LazyLoadedComponent { class LazyLoadedComponent {
constructor(public module: NgModuleRef<any>) {} constructor(public module: NgModuleRef<any>) {}
} }
@NgModule({ @NgModule({
declarations: [LazyLoadedComponent], declarations: [LazyLoadedComponent],
entryComponents: [LazyLoadedComponent], entryComponents: [LazyLoadedComponent],
}) })
class LazyLoadedModule { class LazyLoadedModule {
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component})); 'ng2', downgradeComponent({component: Ng2Component}));
const element = html('<ng2></ng2>'); const element = html('<ng2></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
const modInjector = upgrade.injector; const modInjector = upgrade.injector;
// Emulate the router lazy loading a module and creating a component // Emulate the router lazy loading a module and creating a component
const compiler = modInjector.get(Compiler); const compiler = modInjector.get(Compiler);
const modFactory = compiler.compileModuleSync(LazyLoadedModule); const modFactory = compiler.compileModuleSync(LazyLoadedModule);
const childMod = modFactory.create(modInjector); const childMod = modFactory.create(modInjector);
const cmpFactory = const cmpFactory =
childMod.componentFactoryResolver.resolveComponentFactory(LazyLoadedComponent) !; childMod.componentFactoryResolver.resolveComponentFactory(LazyLoadedComponent) !;
const lazyCmp = cmpFactory.create(componentInjector); 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(() => { it('should throw if `downgradedModule` is specified', async(() => {
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})

View File

@ -11,6 +11,7 @@ import {async, fakeAsync, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
import {fixmeIvy} from '@angular/private/testing';
import {downgradeComponent, downgradeModule} from '@angular/upgrade/static'; import {downgradeComponent, downgradeModule} from '@angular/upgrade/static';
import * as angular from '@angular/upgrade/static/src/common/angular1'; 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'; 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(() => { fixmeIvy('FW-718: upgraded service not being initialized correctly on the injector') &&
class Ng2Service { it('should support using an upgraded service', async(() => {
constructor(@Inject('ng1Value') private ng1Value: string) {} class Ng2Service {
getValue = () => `${this.ng1Value}-bar`; constructor(@Inject('ng1Value') private ng1Value: string) {}
} getValue = () => `${this.ng1Value}-bar`;
}
@Component({selector: 'ng2', template: '{{ value }}'}) @Component({selector: 'ng2', template: '{{ value }}'})
class Ng2Component { class Ng2Component {
value: string; value: string;
constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); } constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
providers: [ providers: [
Ng2Service, Ng2Service,
{ {
provide: 'ng1Value', provide: 'ng1Value',
useFactory: (i: angular.IInjectorService) => i.get('ng1Value'), useFactory: (i: angular.IInjectorService) => i.get('ng1Value'),
deps: ['$injector'], deps: ['$injector'],
}, },
], ],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest})) .directive(
.value('ng1Value', 'foo'); 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
.value('ng1Value', 'foo');
const element = html('<div><ng2 ng-if="loadNg2"></ng2></div>'); const element = html('<div><ng2 ng-if="loadNg2"></ng2></div>');
const $injector = angular.bootstrap(element, [ng1Module.name]); const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
expect(element.textContent).toBe(''); expect(element.textContent).toBe('');
expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
$rootScope.$apply('loadNg2 = true'); $rootScope.$apply('loadNg2 = true');
expect(element.textContent).toBe(''); expect(element.textContent).toBe('');
expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
// Wait for the module to be bootstrapped. // Wait for the module to be bootstrapped.
setTimeout(() => { setTimeout(() => {
expect(() => $injector.get(INJECTOR_KEY)).not.toThrow(); expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
// Wait for `$evalAsync()` to propagate inputs. // Wait for `$evalAsync()` to propagate inputs.
setTimeout(() => expect(element.textContent).toBe('foo-bar')); setTimeout(() => expect(element.textContent).toBe('foo-bar'));
}); });
})); }));
it('should create components inside the Angular zone', async(() => { it('should create components inside the Angular zone', async(() => {
@Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'}) @Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'})
@ -222,99 +225,103 @@ withEachNg1Version(() => {
}); });
})); }));
it('should destroy components inside the Angular zone', async(() => { fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') &&
let destroyedInTheZone = false; it('should destroy components inside the Angular zone', async(() => {
let destroyedInTheZone = false;
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})
class Ng2Component implements OnDestroy { class Ng2Component implements OnDestroy {
ngOnDestroy() { destroyedInTheZone = NgZone.isInAngularZone(); } ngOnDestroy() { destroyedInTheZone = NgZone.isInAngularZone(); }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive(
'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
const element = html('<ng2 ng-if="!hideNg2"></ng2>'); const element = html('<ng2 ng-if="!hideNg2"></ng2>');
const $injector = angular.bootstrap(element, [ng1Module.name]); const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
// Wait for the module to be bootstrapped. // Wait for the module to be bootstrapped.
setTimeout(() => { setTimeout(() => {
$rootScope.$apply('hideNg2 = true'); $rootScope.$apply('hideNg2 = true');
expect(destroyedInTheZone).toBe(true); expect(destroyedInTheZone).toBe(true);
}); });
})); }));
it('should propagate input changes inside the Angular zone', async(() => { fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') &&
let ng2Component: Ng2Component; it('should propagate input changes inside the Angular zone', async(() => {
let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})
class Ng2Component implements OnChanges { class Ng2Component implements OnChanges {
@Input() attrInput = 'foo'; @Input() attrInput = 'foo';
@Input() propInput = 'foo'; @Input() propInput = 'foo';
constructor() { ng2Component = this; } constructor() { ng2Component = this; }
ngOnChanges() {} ngOnChanges() {}
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest})) .directive(
.run(($rootScope: angular.IRootScopeService) => { 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
$rootScope.attrVal = 'bar'; .run(($rootScope: angular.IRootScopeService) => {
$rootScope.propVal = 'bar'; $rootScope.attrVal = 'bar';
}); $rootScope.propVal = 'bar';
});
const element = html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>'); const element =
const $injector = angular.bootstrap(element, [ng1Module.name]); html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; 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 module to be bootstrapped.
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true); const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
const changesSpy = const changesSpy =
spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone); spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
expect(ng2Component.attrInput).toBe('bar'); expect(ng2Component.attrInput).toBe('bar');
expect(ng2Component.propInput).toBe('bar'); expect(ng2Component.propInput).toBe('bar');
$rootScope.$apply('attrVal = "baz"'); $rootScope.$apply('attrVal = "baz"');
expect(ng2Component.attrInput).toBe('baz'); expect(ng2Component.attrInput).toBe('baz');
expect(ng2Component.propInput).toBe('bar'); expect(ng2Component.propInput).toBe('bar');
expect(changesSpy).toHaveBeenCalledTimes(1); expect(changesSpy).toHaveBeenCalledTimes(1);
$rootScope.$apply('propVal = "qux"'); $rootScope.$apply('propVal = "qux"');
expect(ng2Component.attrInput).toBe('baz'); expect(ng2Component.attrInput).toBe('baz');
expect(ng2Component.propInput).toBe('qux'); expect(ng2Component.propInput).toBe('qux');
expect(changesSpy).toHaveBeenCalledTimes(2); expect(changesSpy).toHaveBeenCalledTimes(2);
}); });
}); });
})); }));
it('should wire up the component for change detection', async(() => { it('should wire up the component for change detection', async(() => {
@Component( @Component(
@ -358,209 +365,215 @@ withEachNg1Version(() => {
}); });
})); }));
it('should run the lifecycle hooks in the correct order', async(() => { fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') &&
const logs: string[] = []; fixmeIvy('FW-714: ng1 projected content is not being rendered') &&
let rootScope: angular.IRootScopeService; it('should run the lifecycle hooks in the correct order', async(() => {
const logs: string[] = [];
let rootScope: angular.IRootScopeService;
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
{{ value }} {{ value }}
<button (click)="value = 'qux'"></button> <button (click)="value = 'qux'"></button>
<ng-content></ng-content> <ng-content></ng-content>
` `
}) })
class Ng2Component implements AfterContentChecked, class Ng2Component implements AfterContentChecked,
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy,
OnInit { OnInit {
@Input() value = 'foo'; @Input() value = 'foo';
ngAfterContentChecked() { this.log('AfterContentChecked'); } ngAfterContentChecked() { this.log('AfterContentChecked'); }
ngAfterContentInit() { this.log('AfterContentInit'); } ngAfterContentInit() { this.log('AfterContentInit'); }
ngAfterViewChecked() { this.log('AfterViewChecked'); } ngAfterViewChecked() { this.log('AfterViewChecked'); }
ngAfterViewInit() { this.log('AfterViewInit'); } ngAfterViewInit() { this.log('AfterViewInit'); }
ngDoCheck() { this.log('DoCheck'); } ngDoCheck() { this.log('DoCheck'); }
ngOnChanges() { this.log('OnChanges'); } ngOnChanges() { this.log('OnChanges'); }
ngOnDestroy() { this.log('OnDestroy'); } ngOnDestroy() { this.log('OnDestroy'); }
ngOnInit() { this.log('OnInit'); } ngOnInit() { this.log('OnInit'); }
private log(hook: string) { logs.push(`${hook}(${this.value})`); } private log(hook: string) { logs.push(`${hook}(${this.value})`); }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest})) .directive(
.run(($rootScope: angular.IRootScopeService) => { 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
rootScope = $rootScope; .run(($rootScope: angular.IRootScopeService) => {
rootScope.value = 'bar'; rootScope = $rootScope;
}); rootScope.value = 'bar';
});
const element = const element =
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>'); html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
angular.bootstrap(element, [ng1Module.name]); angular.bootstrap(element, [ng1Module.name]);
setTimeout(() => { // Wait for the module to be bootstrapped. setTimeout(() => { // Wait for the module to be bootstrapped.
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
const button = element.querySelector('button') !; const button = element.querySelector('button') !;
// Once initialized. // Once initialized.
expect(multiTrim(element.textContent)).toBe('bar Content'); expect(multiTrim(element.textContent)).toBe('bar Content');
expect(logs).toEqual([ expect(logs).toEqual([
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
'OnChanges(bar)', 'OnChanges(bar)',
// Initial CD triggered directly through the `detectChanges()` or `inputChanges` // Initial CD triggered directly through the `detectChanges()` or
// $watcher (for `propagateDigest` true/false respectively). // `inputChanges`
'OnInit(bar)', // $watcher (for `propagateDigest` true/false respectively).
'DoCheck(bar)', 'OnInit(bar)',
'AfterContentInit(bar)', 'DoCheck(bar)',
'AfterContentChecked(bar)', 'AfterContentInit(bar)',
'AfterViewInit(bar)', 'AfterContentChecked(bar)',
'AfterViewChecked(bar)', 'AfterViewInit(bar)',
...(propagateDigest ? 'AfterViewChecked(bar)',
[ ...(propagateDigest ?
// CD triggered directly through the `detectChanges()` $watcher (2nd [
// $digest). // CD triggered directly through the `detectChanges()` $watcher (2nd
'DoCheck(bar)', // $digest).
'AfterContentChecked(bar)', 'DoCheck(bar)',
'AfterViewChecked(bar)', 'AfterContentChecked(bar)',
] : 'AfterViewChecked(bar)',
[]), ] :
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`). []),
'DoCheck(bar)', // CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
'AfterContentChecked(bar)', 'DoCheck(bar)',
'AfterViewChecked(bar)', 'AfterContentChecked(bar)',
]); 'AfterViewChecked(bar)',
logs.length = 0; ]);
logs.length = 0;
// Change inputs and run `$digest`. // Change inputs and run `$digest`.
rootScope.$apply('value = "baz"'); rootScope.$apply('value = "baz"');
expect(multiTrim(element.textContent)).toBe('baz Content'); expect(multiTrim(element.textContent)).toBe('baz Content');
expect(logs).toEqual([ expect(logs).toEqual([
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
'OnChanges(baz)', 'OnChanges(baz)',
// `propagateDigest: true` (3 CD runs): // `propagateDigest: true` (3 CD runs):
// - CD triggered due to entering/leaving the NgZone (in `inputChanges` // - CD triggered due to entering/leaving the NgZone (in `inputChanges`
// $watcher). // $watcher).
// - CD triggered directly through the `detectChanges()` $watcher. // - CD triggered directly through the `detectChanges()` $watcher.
// - CD triggered due to entering/leaving the NgZone (in `detectChanges` // - CD triggered due to entering/leaving the NgZone (in `detectChanges`
// $watcher). // $watcher).
// `propagateDigest: false` (2 CD runs): // `propagateDigest: false` (2 CD runs):
// - CD triggered directly through the `inputChanges` $watcher. // - CD triggered directly through the `inputChanges` $watcher.
// - CD triggered due to entering/leaving the NgZone (in `inputChanges` // - CD triggered due to entering/leaving the NgZone (in `inputChanges`
// $watcher). // $watcher).
'DoCheck(baz)', 'DoCheck(baz)',
'AfterContentChecked(baz)', 'AfterContentChecked(baz)',
'AfterViewChecked(baz)', 'AfterViewChecked(baz)',
'DoCheck(baz)', 'DoCheck(baz)',
'AfterContentChecked(baz)', 'AfterContentChecked(baz)',
'AfterViewChecked(baz)', 'AfterViewChecked(baz)',
...(propagateDigest ? ...(propagateDigest ?
[ [
'DoCheck(baz)', 'DoCheck(baz)',
'AfterContentChecked(baz)', 'AfterContentChecked(baz)',
'AfterViewChecked(baz)', 'AfterViewChecked(baz)',
] : ] :
[]), []),
]); ]);
logs.length = 0; logs.length = 0;
// Run `$digest` (without changing inputs). // Run `$digest` (without changing inputs).
rootScope.$digest(); rootScope.$digest();
expect(multiTrim(element.textContent)).toBe('baz Content'); expect(multiTrim(element.textContent)).toBe('baz Content');
expect(logs).toEqual( expect(logs).toEqual(
propagateDigest ? propagateDigest ?
[ [
// CD triggered directly through the `detectChanges()` $watcher. // CD triggered directly through the `detectChanges()` $watcher.
'DoCheck(baz)', 'DoCheck(baz)',
'AfterContentChecked(baz)', 'AfterContentChecked(baz)',
'AfterViewChecked(baz)', 'AfterViewChecked(baz)',
// CD triggered due to entering/leaving the NgZone (in the above $watcher). // CD triggered due to entering/leaving the NgZone (in the above
'DoCheck(baz)', // $watcher).
'AfterContentChecked(baz)', 'DoCheck(baz)',
'AfterViewChecked(baz)', 'AfterContentChecked(baz)',
] : 'AfterViewChecked(baz)',
[]); ] :
logs.length = 0; []);
logs.length = 0;
// Trigger change detection (without changing inputs). // Trigger change detection (without changing inputs).
button.click(); button.click();
expect(multiTrim(element.textContent)).toBe('qux Content'); expect(multiTrim(element.textContent)).toBe('qux Content');
expect(logs).toEqual([ expect(logs).toEqual([
'DoCheck(qux)', 'DoCheck(qux)',
'AfterContentChecked(qux)', 'AfterContentChecked(qux)',
'AfterViewChecked(qux)', 'AfterViewChecked(qux)',
]); ]);
logs.length = 0; logs.length = 0;
// Destroy the component. // Destroy the component.
rootScope.$apply('hideNg2 = true'); rootScope.$apply('hideNg2 = true');
expect(logs).toEqual([ expect(logs).toEqual([
'OnDestroy(qux)', 'OnDestroy(qux)',
]); ]);
logs.length = 0; logs.length = 0;
}); });
}); });
})); }));
it('should detach hostViews from the ApplicationRef once destroyed', async(() => { fixmeIvy('FW-717: Browser locks up and disconnects') &&
let ng2Component: Ng2Component; it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})
class Ng2Component { class Ng2Component {
constructor(public appRef: ApplicationRef) { constructor(public appRef: ApplicationRef) {
ng2Component = this; ng2Component = this;
spyOn(appRef, 'attachView').and.callThrough(); spyOn(appRef, 'attachView').and.callThrough();
spyOn(appRef, 'detachView').and.callThrough(); spyOn(appRef, 'detachView').and.callThrough();
} }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive(
'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
const element = html('<ng2 ng-if="!hideNg2"></ng2>'); const element = html('<ng2 ng-if="!hideNg2"></ng2>');
const $injector = angular.bootstrap(element, [ng1Module.name]); const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
setTimeout(() => { // Wait for the module to be bootstrapped. setTimeout(() => { // Wait for the module to be bootstrapped.
setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`). setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`).
const hostView: ViewRef = const hostView: ViewRef =
(ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0]; (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(hostView.destroyed).toBe(true);
expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView); expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView);
}); });
}); });
})); }));
it('should only retrieve the Angular zone once (and cache it for later use)', it('should only retrieve the Angular zone once (and cache it for later use)',
fakeAsync(() => { fakeAsync(() => {

View File

@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, Injector, Input, NgModule, destroyPlat
import {async} from '@angular/core/testing'; import {async} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {fixmeIvy} from '@angular/private/testing';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import * as angular from '@angular/upgrade/static/src/common/angular1'; 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 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 // This is wrapping (upgrading) an AngularJS component to be used in an Angular
// component // component
@Directive({selector: 'ng1'}) @Directive({selector: 'ng1'})
class Ng1Component extends UpgradeComponent { class Ng1Component extends UpgradeComponent {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() title !: string; @Input() title !: string;
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1', elementRef, injector); super('ng1', elementRef, injector);
} }
} }
// This is an Angular component that will be downgraded // This is an Angular component that will be downgraded
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)' template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)'
}) })
class Ng2Component { class Ng2Component {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input('name') nameProp !: string; @Input('name') nameProp !: string;
} }
// This module represents the Angular pieces of the application // This module represents the Angular pieces of the application
@NgModule({ @NgModule({
declarations: [Ng1Component, Ng2Component], declarations: [Ng1Component, Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from complaining */ ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from
} complaining */
} }
}
// This module represents the AngularJS pieces of the application // This module represents the AngularJS pieces of the application
const ng1Module = const ng1Module =
angular angular
.module('myExample', []) .module('myExample', [])
// This is an AngularJS component that will be upgraded // This is an AngularJS component that will be upgraded
.directive( .directive(
'ng1', 'ng1',
() => { () => {
return { return {
scope: {title: '='}, scope: {title: '='},
transclude: true, transclude: true,
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
}; };
}) })
// This is wrapping (downgrading) an Angular component to be used in AngularJS // This is wrapping (downgrading) an Angular component to be used in
.directive('ng2', downgradeComponent({component: Ng2Component})); // AngularJS
.directive('ng2', downgradeComponent({component: Ng2Component}));
// This is the (AngularJS) application bootstrap element // This is the (AngularJS) application bootstrap element
// Notice that it is actually a downgraded Angular component // Notice that it is actually a downgraded Angular component
const element = html('<ng2 name="World">project</ng2>'); const element = html('<ng2 name="World">project</ng2>');
// Let's use a helper function to make this simpler // Let's use a helper function to make this simpler
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent))
.toBe('ng2[ng1[Hello World!](transclude)](project)'); .toBe('ng2[ng1[Hello World!](transclude)](project)');
}); });
})); }));
}); });
}); });

View File

@ -10,6 +10,7 @@ import {Component, Directive, ElementRef, ErrorHandler, EventEmitter, Inject, In
import {async, fakeAsync, tick} from '@angular/core/testing'; import {async, fakeAsync, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {fixmeIvy} from '@angular/private/testing';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import * as angular from '@angular/upgrade/static/src/common/angular1'; import * as angular from '@angular/upgrade/static/src/common/angular1';
import {$EXCEPTION_HANDLER, $SCOPE} from '@angular/upgrade/static/src/common/constants'; import {$EXCEPTION_HANDLER, $SCOPE} from '@angular/upgrade/static/src/common/constants';
@ -2133,72 +2134,77 @@ withEachNg1Version(() => {
}); });
describe('transclusion', () => { describe('transclusion', () => {
it('should support single-slot transclusion', async(() => { fixmeIvy(
let ng2ComponentAInstance: Ng2ComponentA; `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.`) &&
let ng2ComponentBInstance: Ng2ComponentB; it('should support single-slot transclusion', async(() => {
let ng2ComponentAInstance: Ng2ComponentA;
let ng2ComponentBInstance: Ng2ComponentB;
// Define `ng1Component` // Define `ng1Component`
const ng1Component: const ng1Component: angular.IComponent = {
angular.IComponent = {template: 'ng1(<div ng-transclude></div>)', transclude: true}; template: 'ng1(<div ng-transclude></div>)',
transclude: true
};
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1'}) @Directive({selector: 'ng1'})
class Ng1ComponentFacade extends UpgradeComponent { class Ng1ComponentFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1', elementRef, injector); super('ng1', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2A', selector: 'ng2A',
template: 'ng2A(<ng1>{{ value }} | <ng2B *ngIf="showB"></ng2B></ng1>)' template: 'ng2A(<ng1>{{ value }} | <ng2B *ngIf="showB"></ng2B></ng1>)'
}) })
class Ng2ComponentA { class Ng2ComponentA {
value = 'foo'; value = 'foo';
showB = false; showB = false;
constructor() { ng2ComponentAInstance = this; } constructor() { ng2ComponentAInstance = this; }
} }
@Component({selector: 'ng2B', template: 'ng2B({{ value }})'}) @Component({selector: 'ng2B', template: 'ng2B({{ value }})'})
class Ng2ComponentB { class Ng2ComponentB {
value = 'bar'; value = 'bar';
constructor() { ng2ComponentBInstance = this; } constructor() { ng2ComponentBInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = angular.module('ng1Module', []) const ng1Module =
.component('ng1', ng1Component) angular.module('ng1Module', [])
.directive('ng2A', downgradeComponent({component: Ng2ComponentA})); .component('ng1', ng1Component)
.directive('ng2A', downgradeComponent({component: Ng2ComponentA}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB], declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB],
entryComponents: [Ng2ComponentA] entryComponents: [Ng2ComponentA]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = html(`<ng2-a></ng2-a>`); const element = html(`<ng2-a></ng2-a>`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))'); expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))');
ng2ComponentAInstance.value = 'baz'; ng2ComponentAInstance.value = 'baz';
ng2ComponentAInstance.showB = true; ng2ComponentAInstance.showB = true;
$digest(adapter); $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'; ng2ComponentBInstance.value = 'qux';
$digest(adapter); $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(() => { it('should support single-slot transclusion with fallback content', async(() => {
let ng1ControllerInstances: any[] = []; let ng1ControllerInstances: any[] = [];
@ -2525,28 +2531,30 @@ withEachNg1Version(() => {
}); });
})); }));
it('should support structural directives in transcluded content', async(() => { fixmeIvy(
let ng2ComponentInstance: Ng2Component; `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` // Define `ng1Component`
const ng1Component: angular.IComponent = { const ng1Component: angular.IComponent = {
template: template:
'ng1(x(<div ng-transclude="slotX"></div>) | default(<div ng-transclude=""></div>))', 'ng1(x(<div ng-transclude="slotX"></div>) | default(<div ng-transclude=""></div>))',
transclude: {slotX: 'contentX'} transclude: {slotX: 'contentX'}
}; };
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1'}) @Directive({selector: 'ng1'})
class Ng1ComponentFacade extends UpgradeComponent { class Ng1ComponentFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1', elementRef, injector); super('ng1', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
ng2( ng2(
<ng1> <ng1>
<content-x><div *ngIf="show">{{ x }}1</div></content-x> <content-x><div *ngIf="show">{{ x }}1</div></content-x>
@ -2555,49 +2563,53 @@ withEachNg1Version(() => {
<div *ngIf="show">{{ y }}2</div> <div *ngIf="show">{{ y }}2</div>
</ng1> </ng1>
)` )`
}) })
class Ng2Component { class Ng2Component {
x = 'foo'; x = 'foo';
y = 'bar'; y = 'bar';
show = true; show = true;
constructor() { ng2ComponentInstance = this; } constructor() { ng2ComponentInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = angular.module('ng1Module', []) const ng1Module =
.component('ng1', ng1Component) angular.module('ng1Module', [])
.directive('ng2', downgradeComponent({component: Ng2Component})); .component('ng1', ng1Component)
.directive('ng2', downgradeComponent({component: Ng2Component}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng1ComponentFacade, Ng2Component], declarations: [Ng1ComponentFacade, Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = html(`<ng2></ng2>`); const element = html(`<ng2></ng2>`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
expect(multiTrim(element.textContent, true)).toBe('ng2(ng1(x(foo1)|default(bar2)))'); expect(multiTrim(element.textContent, true))
.toBe('ng2(ng1(x(foo1)|default(bar2)))');
ng2ComponentInstance.x = 'baz'; ng2ComponentInstance.x = 'baz';
ng2ComponentInstance.y = 'qux'; ng2ComponentInstance.y = 'qux';
ng2ComponentInstance.show = false; ng2ComponentInstance.show = false;
$digest(adapter); $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; ng2ComponentInstance.show = true;
$digest(adapter); $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', () => { describe('lifecycle hooks', () => {
@ -3324,98 +3336,103 @@ withEachNg1Version(() => {
})); }));
it('should call `$onDestroy()` on controller', async(() => { fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') &&
const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA'); it('should call `$onDestroy()` on controller', async(() => {
const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB'); const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA');
const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB');
// Define `ng1Directive` // Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = { const ng1DirectiveA: angular.IDirective = {
template: 'ng1A', template: 'ng1A',
scope: {}, scope: {},
bindToController: false, bindToController: false,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: class {$onDestroy() { controllerOnDestroyA(); }} controller: class {$onDestroy() { controllerOnDestroyA(); }}
}; };
const ng1DirectiveB: angular.IDirective = { const ng1DirectiveB: angular.IDirective = {
template: 'ng1B', template: 'ng1B',
scope: {}, scope: {},
bindToController: true, bindToController: true,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: controller:
class {constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; }} class {constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; }}
}; };
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1A'}) @Directive({selector: 'ng1A'})
class Ng1ComponentAFacade extends UpgradeComponent { class Ng1ComponentAFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1A', elementRef, injector); super('ng1A', elementRef, injector);
} }
} }
@Directive({selector: 'ng1B'}) @Directive({selector: 'ng1B'})
class Ng1ComponentBFacade extends UpgradeComponent { class Ng1ComponentBFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1B', elementRef, injector); super('ng1B', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component( @Component({
{selector: 'ng2', template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>'}) selector: 'ng2',
class Ng2Component { template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>'
// TODO(issue/24571): remove '!'. })
@Input() show !: boolean; class Ng2Component {
} // TODO(issue/24571): remove '!'.
@Input() show !: boolean;
}
// Define `ng1Module` // Define `ng1Module`
const ng1Module = angular.module('ng1Module', []) const ng1Module =
.directive('ng1A', () => ng1DirectiveA) angular.module('ng1Module', [])
.directive('ng1B', () => ng1DirectiveB) .directive('ng1A', () => ng1DirectiveA)
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive('ng1B', () => ng1DirectiveB)
.directive('ng2', downgradeComponent({component: Ng2Component}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component], declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>'); const element = html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
const $rootScope = adapter.$injector.get('$rootScope') as angular.IRootScopeService; const $rootScope =
adapter.$injector.get('$rootScope') as angular.IRootScopeService;
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
expect(controllerOnDestroyA).not.toHaveBeenCalled(); expect(controllerOnDestroyA).not.toHaveBeenCalled();
expect(controllerOnDestroyB).not.toHaveBeenCalled(); expect(controllerOnDestroyB).not.toHaveBeenCalled();
$rootScope.$apply('destroyFromNg1 = true'); $rootScope.$apply('destroyFromNg1 = true');
expect(multiTrim(document.body.textContent)).toBe(''); expect(multiTrim(document.body.textContent)).toBe('');
expect(controllerOnDestroyA).toHaveBeenCalled(); expect(controllerOnDestroyA).toHaveBeenCalled();
expect(controllerOnDestroyB).toHaveBeenCalled(); expect(controllerOnDestroyB).toHaveBeenCalled();
controllerOnDestroyA.calls.reset(); controllerOnDestroyA.calls.reset();
controllerOnDestroyB.calls.reset(); controllerOnDestroyB.calls.reset();
$rootScope.$apply('destroyFromNg1 = false'); $rootScope.$apply('destroyFromNg1 = false');
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
expect(controllerOnDestroyA).not.toHaveBeenCalled(); expect(controllerOnDestroyA).not.toHaveBeenCalled();
expect(controllerOnDestroyB).not.toHaveBeenCalled(); expect(controllerOnDestroyB).not.toHaveBeenCalled();
$rootScope.$apply('destroyFromNg2 = true'); $rootScope.$apply('destroyFromNg2 = true');
expect(multiTrim(document.body.textContent)).toBe(''); expect(multiTrim(document.body.textContent)).toBe('');
expect(controllerOnDestroyA).toHaveBeenCalled(); expect(controllerOnDestroyA).toHaveBeenCalled();
expect(controllerOnDestroyB).toHaveBeenCalled(); expect(controllerOnDestroyB).toHaveBeenCalled();
}); });
})); }));
it('should not call `$onDestroy()` on scope', async(() => { it('should not call `$onDestroy()` on scope', async(() => {
const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy'); const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy');
@ -3948,22 +3965,23 @@ withEachNg1Version(() => {
}); });
})); }));
it('should support ng2 > ng1 > ng2 (with inputs/outputs)', fakeAsync(() => { fixmeIvy('FW-642: ASSERTION ERROR: Slot should have been initialized to NO_CHANGE') &&
let ng2ComponentAInstance: Ng2ComponentA; it('should support ng2 > ng1 > ng2 (with inputs/outputs)', fakeAsync(() => {
let ng2ComponentBInstance: Ng2ComponentB; let ng2ComponentAInstance: Ng2ComponentA;
let ng1ControllerXInstance: Ng1ControllerX; let ng2ComponentBInstance: Ng2ComponentB;
let ng1ControllerXInstance: Ng1ControllerX;
// Define `ng1Component` // Define `ng1Component`
class Ng1ControllerX { class Ng1ControllerX {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
ng1XInputA !: string; ng1XInputA !: string;
ng1XInputB: any; ng1XInputB: any;
ng1XInputC: any; ng1XInputC: any;
constructor() { ng1ControllerXInstance = this; } constructor() { ng1ControllerXInstance = this; }
} }
const ng1Component: angular.IComponent = { const ng1Component: angular.IComponent = {
template: ` template: `
ng1X({{ $ctrl.ng1XInputA }}, {{ $ctrl.ng1XInputB.value }}, {{ $ctrl.ng1XInputC.value }}) | ng1X({{ $ctrl.ng1XInputA }}, {{ $ctrl.ng1XInputB.value }}, {{ $ctrl.ng1XInputC.value }}) |
<ng2-b <ng2-b
[ng2-b-input1]="$ctrl.ng1XInputA" [ng2-b-input1]="$ctrl.ng1XInputA"
@ -3971,39 +3989,39 @@ withEachNg1Version(() => {
(ng2-b-output-c)="$ctrl.ng1XInputC = {value: $event}"> (ng2-b-output-c)="$ctrl.ng1XInputC = {value: $event}">
</ng2-b> </ng2-b>
`, `,
bindings: { bindings: {
ng1XInputA: '@', ng1XInputA: '@',
ng1XInputB: '<', ng1XInputB: '<',
ng1XInputC: '=', ng1XInputC: '=',
ng1XOutputA: '&', ng1XOutputA: '&',
ng1XOutputB: '&' ng1XOutputB: '&'
}, },
controller: Ng1ControllerX controller: Ng1ControllerX
}; };
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1X'}) @Directive({selector: 'ng1X'})
class Ng1ComponentXFacade extends UpgradeComponent { class Ng1ComponentXFacade extends UpgradeComponent {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() ng1XInputA !: string; @Input() ng1XInputA !: string;
@Input() ng1XInputB: any; @Input() ng1XInputB: any;
@Input() ng1XInputC: any; @Input() ng1XInputC: any;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Output() ng1XInputCChange !: EventEmitter<any>; @Output() ng1XInputCChange !: EventEmitter<any>;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Output() ng1XOutputA !: EventEmitter<any>; @Output() ng1XOutputA !: EventEmitter<any>;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Output() ng1XOutputB !: EventEmitter<any>; @Output() ng1XOutputB !: EventEmitter<any>;
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1X', elementRef, injector); super('ng1X', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2-a', selector: 'ng2-a',
template: ` template: `
ng2A({{ ng2ADataA.value }}, {{ ng2ADataB.value }}, {{ ng2ADataC.value }}) | ng2A({{ ng2ADataA.value }}, {{ ng2ADataB.value }}, {{ ng2ADataC.value }}) |
<ng1X <ng1X
ng1XInputA="{{ ng2ADataA.value }}" ng1XInputA="{{ ng2ADataA.value }}"
@ -4013,129 +4031,131 @@ withEachNg1Version(() => {
on-ng1XOutputB="ng2ADataB.value = $event"> on-ng1XOutputB="ng2ADataB.value = $event">
</ng1X> </ng1X>
` `
}) })
class Ng2ComponentA { class Ng2ComponentA {
ng2ADataA = {value: 'foo'}; ng2ADataA = {value: 'foo'};
ng2ADataB = {value: 'bar'}; ng2ADataB = {value: 'bar'};
ng2ADataC = {value: 'baz'}; ng2ADataC = {value: 'baz'};
constructor() { ng2ComponentAInstance = this; } constructor() { ng2ComponentAInstance = this; }
} }
@Component({selector: 'ng2-b', template: 'ng2B({{ ng2BInputA }}, {{ ng2BInputC }})'}) @Component({selector: 'ng2-b', template: 'ng2B({{ ng2BInputA }}, {{ ng2BInputC }})'})
class Ng2ComponentB { class Ng2ComponentB {
@Input('ng2BInput1') ng2BInputA: any; @Input('ng2BInput1') ng2BInputA: any;
@Input() ng2BInputC: any; @Input() ng2BInputC: any;
@Output() ng2BOutputC = new EventEmitter(); @Output() ng2BOutputC = new EventEmitter();
constructor() { ng2ComponentBInstance = this; } constructor() { ng2ComponentBInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = angular.module('ng1', []) const ng1Module =
.component('ng1X', ng1Component) angular.module('ng1', [])
.directive('ng2A', downgradeComponent({component: Ng2ComponentA})) .component('ng1X', ng1Component)
.directive('ng2B', downgradeComponent({component: Ng2ComponentB})); .directive('ng2A', downgradeComponent({component: Ng2ComponentA}))
.directive('ng2B', downgradeComponent({component: Ng2ComponentB}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
declarations: [Ng1ComponentXFacade, Ng2ComponentA, Ng2ComponentB], declarations: [Ng1ComponentXFacade, Ng2ComponentA, Ng2ComponentB],
entryComponents: [Ng2ComponentA, Ng2ComponentB], entryComponents: [Ng2ComponentA, Ng2ComponentB],
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = html(`<ng2-a></ng2-a>`); const element = html(`<ng2-a></ng2-a>`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
// Initial value propagation. // Initial value propagation.
// (ng2A > ng1X > ng2B) // (ng2A > ng1X > ng2B)
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo, baz)'); .toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo, baz)');
// Update `ng2BInputA`/`ng2BInputC`. // Update `ng2BInputA`/`ng2BInputC`.
// (Should not propagate upwards.) // (Should not propagate upwards.)
ng2ComponentBInstance.ng2BInputA = 'foo2'; ng2ComponentBInstance.ng2BInputA = 'foo2';
ng2ComponentBInstance.ng2BInputC = 'baz2'; ng2ComponentBInstance.ng2BInputC = 'baz2';
$digest(adapter); $digest(adapter);
tick(); tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo2, baz2)'); .toBe('ng2A(foo, bar, baz) | ng1X(foo, bar, baz) | ng2B(foo2, baz2)');
// Emit from `ng2BOutputC`. // Emit from `ng2BOutputC`.
// (Should propagate all the way up to `ng1ADataC` and back all the way down to // (Should propagate all the way up to `ng1ADataC` and back all the way down to
// `ng2BInputC`.) // `ng2BInputC`.)
ng2ComponentBInstance.ng2BOutputC.emit('baz3'); ng2ComponentBInstance.ng2BOutputC.emit('baz3');
$digest(adapter); $digest(adapter);
tick(); tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo, bar, baz3) | ng1X(foo, bar, baz3) | ng2B(foo2, baz3)'); .toBe('ng2A(foo, bar, baz3) | ng1X(foo, bar, baz3) | ng2B(foo2, baz3)');
// Update `ng1XInputA`/`ng1XInputB`. // Update `ng1XInputA`/`ng1XInputB`.
// (Should not propagate upwards, only downwards.) // (Should not propagate upwards, only downwards.)
ng1ControllerXInstance.ng1XInputA = 'foo4'; ng1ControllerXInstance.ng1XInputA = 'foo4';
ng1ControllerXInstance.ng1XInputB = {value: 'bar4'}; ng1ControllerXInstance.ng1XInputB = {value: 'bar4'};
$digest(adapter); $digest(adapter);
tick(); tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo, bar, baz3) | ng1X(foo4, bar4, baz3) | ng2B(foo4, baz3)'); .toBe('ng2A(foo, bar, baz3) | ng1X(foo4, bar4, baz3) | ng2B(foo4, baz3)');
// Update `ng1XInputC`. // Update `ng1XInputC`.
// (Should propagate upwards and downwards.) // (Should propagate upwards and downwards.)
ng1ControllerXInstance.ng1XInputC = {value: 'baz5'}; ng1ControllerXInstance.ng1XInputC = {value: 'baz5'};
$digest(adapter); $digest(adapter);
tick(); tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo, bar, baz5) | ng1X(foo4, bar4, baz5) | ng2B(foo4, baz5)'); .toBe('ng2A(foo, bar, baz5) | ng1X(foo4, bar4, baz5) | ng2B(foo4, baz5)');
// Update a property on `ng1XInputC`. // Update a property on `ng1XInputC`.
// (Should propagate upwards and downwards.) // (Should propagate upwards and downwards.)
ng1ControllerXInstance.ng1XInputC.value = 'baz6'; ng1ControllerXInstance.ng1XInputC.value = 'baz6';
$digest(adapter); $digest(adapter);
tick(); tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo, bar, baz6) | ng1X(foo4, bar4, baz6) | ng2B(foo4, baz6)'); .toBe('ng2A(foo, bar, baz6) | ng1X(foo4, bar4, baz6) | ng2B(foo4, baz6)');
// Emit from `ng1XOutputA`. // Emit from `ng1XOutputA`.
// (Should propagate upwards to `ng1ADataA` and back all the way down to `ng2BInputA`.) // (Should propagate upwards to `ng1ADataA` and back all the way down to
(ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'}); // `ng2BInputA`.)
$digest(adapter); (ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'});
tick(); $digest(adapter);
tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo7, bar, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)'); .toBe('ng2A(foo7, bar, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)');
// Emit from `ng1XOutputB`. // Emit from `ng1XOutputB`.
// (Should propagate upwards to `ng1ADataB`, but not downwards, // (Should propagate upwards to `ng1ADataB`, but not downwards,
// since `ng1XInputB` has been re-assigned (i.e. `ng2ADataB !== ng1XInputB`).) // since `ng1XInputB` has been re-assigned (i.e. `ng2ADataB !== ng1XInputB`).)
(ng1ControllerXInstance as any).ng1XOutputB('bar8'); (ng1ControllerXInstance as any).ng1XOutputB('bar8');
$digest(adapter); $digest(adapter);
tick(); tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo7, bar8, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)'); .toBe('ng2A(foo7, bar8, baz6) | ng1X(foo7, bar4, baz6) | ng2B(foo7, baz6)');
// Update `ng2ADataA`/`ng2ADataB`/`ng2ADataC`. // Update `ng2ADataA`/`ng2ADataB`/`ng2ADataC`.
// (Should propagate everywhere.) // (Should propagate everywhere.)
ng2ComponentAInstance.ng2ADataA = {value: 'foo9'}; ng2ComponentAInstance.ng2ADataA = {value: 'foo9'};
ng2ComponentAInstance.ng2ADataB = {value: 'bar9'}; ng2ComponentAInstance.ng2ADataB = {value: 'bar9'};
ng2ComponentAInstance.ng2ADataC = {value: 'baz9'}; ng2ComponentAInstance.ng2ADataC = {value: 'baz9'};
$digest(adapter); $digest(adapter);
tick(); tick();
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2A(foo9, bar9, baz9) | ng1X(foo9, bar9, baz9) | ng2B(foo9, baz9)'); .toBe('ng2A(foo9, bar9, baz9) | ng1X(foo9, bar9, baz9) | ng2B(foo9, baz9)');
}); });
})); }));
it('should support ng2 > ng1 > ng2 > ng1 (with `require`)', async(() => { it('should support ng2 > ng1 > ng2 > ng1 (with `require`)', async(() => {
// Define `ng1Component` // Define `ng1Component`