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

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 {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {fixmeIvy} from '@angular/private/testing';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import * as angular from '@angular/upgrade/static/src/common/angular1';
@ -75,57 +76,60 @@ withEachNg1Version(() => {
});
}));
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
let appComponent: AppComponent;
fixmeIvy(
'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger') &&
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
const element = html('<my-app></my-app>');
let appComponent: AppComponent;
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
class AppComponent {
// TODO(issue/24571): remove '!'.
value !: number;
constructor() { appComponent = this; }
}
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
class AppComponent {
value?: number;
constructor() { appComponent = this; }
}
@Component({
selector: 'my-child',
template: '<div>{{ valueFromPromise }}</div>',
})
class ChildComponent {
// TODO(issue/24571): remove '!'.
valueFromPromise !: number;
@Input()
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
@Component({
selector: 'my-child',
template: '<div>{{ valueFromPromise }}</div>',
})
class ChildComponent {
valueFromPromise?: number;
@Input()
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
constructor(private zone: NgZone) {}
constructor(private zone: NgZone) {}
ngOnChanges(changes: SimpleChanges) {
if (changes['value'].isFirstChange()) return;
ngOnChanges(changes: SimpleChanges) {
if (changes['value'].isFirstChange()) return;
this.zone.onMicrotaskEmpty.subscribe(
() => { expect(element.textContent).toEqual('5'); });
// HACK(ivy): Using setTimeout allows this test to pass but hides the ivy renderer
// timing BC.
// setTimeout(() => expect(element.textContent).toEqual('5'), 0);
this.zone.onMicrotaskEmpty.subscribe(
() => { expect(element.textContent).toEqual('5'); });
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
}
}
// Create a micro-task to update the value to be rendered asynchronously.
Promise.resolve().then(
() => this.valueFromPromise = changes['value'].currentValue);
}
}
@NgModule({
declarations: [AppComponent, ChildComponent],
entryComponents: [AppComponent],
imports: [BrowserModule, UpgradeModule]
})
class Ng2Module {
ngDoBootstrap() {}
}
@NgModule({
declarations: [AppComponent, ChildComponent],
entryComponents: [AppComponent],
imports: [BrowserModule, UpgradeModule]
})
class Ng2Module {
ngDoBootstrap() {}
}
const ng1Module = angular.module('ng1', []).directive(
'myApp', downgradeComponent({component: AppComponent}));
const ng1Module = angular.module('ng1', []).directive(
'myApp', downgradeComponent({component: AppComponent}));
const element = html('<my-app></my-app>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
appComponent.value = 5;
});
}));
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
appComponent.value = 5;
});
}));
// This test demonstrates https://github.com/angular/angular/issues/6385
// which was invalidly fixed by https://github.com/angular/angular/pull/6386

View File

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

View File

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

View File

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

View File

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

View File

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