test(upgrade): disable failing ngUpgrade tests when in ivy mode (#27132)
PR Close #27132
This commit is contained in:
parent
a4462c24fa
commit
bc0fbfc93e
@ -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
@ -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
|
||||||
|
@ -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)');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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: ''})
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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)');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user