test(upgrade): re-enable tests fixed in ivy (#27454)

PR Close #27454
This commit is contained in:
Pete Bacon Darwin 2018-12-04 10:55:03 +00:00 committed by Igor Minar
parent 130ae158c4
commit 145121a75d
5 changed files with 418 additions and 428 deletions

View File

@ -7,7 +7,6 @@
*/ */
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';

View File

@ -1681,52 +1681,51 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('unknown') && it('should not call `$onInit()` on scope', async(() => {
it('should not call `$onInit()` on scope', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const $onInitSpy = jasmine.createSpy('$onInit');
const $onInitSpy = jasmine.createSpy('$onInit');
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'}) @Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component { class Ng2Component {
} }
angular.module('ng1', []) angular.module('ng1', [])
.directive('ng1A', () => ({ .directive('ng1A', () => ({
template: '', template: '',
scope: {}, scope: {},
bindToController: true, bindToController: true,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: function($scope: angular.IScope) { controller: function($scope: angular.IScope) {
Object.getPrototypeOf($scope).$onInit = $onInitSpy; Object.getPrototypeOf($scope).$onInit = $onInitSpy;
} }
})) }))
.directive('ng1B', () => ({ .directive('ng1B', () => ({
template: '', template: '',
scope: {}, scope: {},
bindToController: false, bindToController: false,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: function($scope: angular.IScope) { controller: function($scope: angular.IScope) {
$scope['$onInit'] = $onInitSpy; $scope['$onInit'] = $onInitSpy;
} }
})) }))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component)); .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({ @NgModule({
declarations: [ declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component Ng2Component
], ],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
const element = html(`<div><ng2></ng2></div>`); const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => { adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($onInitSpy).not.toHaveBeenCalled(); expect($onInitSpy).not.toHaveBeenCalled();
ref.dispose(); ref.dispose();
}); });
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown') &&
it('should call `$doCheck()` on controller', async(() => { it('should call `$doCheck()` on controller', async(() => {
@ -1783,61 +1782,60 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('unknown') && it('should not call `$doCheck()` on scope', async(() => {
it('should not call `$doCheck()` on scope', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const $doCheckSpyA = jasmine.createSpy('$doCheckA');
const $doCheckSpyA = jasmine.createSpy('$doCheckA'); const $doCheckSpyB = jasmine.createSpy('$doCheckB');
const $doCheckSpyB = jasmine.createSpy('$doCheckB'); let changeDetector: ChangeDetectorRef;
let changeDetector: ChangeDetectorRef;
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'}) @Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component { class Ng2Component {
constructor(cd: ChangeDetectorRef) { changeDetector = cd; } constructor(cd: ChangeDetectorRef) { changeDetector = cd; }
} }
angular.module('ng1', []) angular.module('ng1', [])
.directive('ng1A', () => ({ .directive('ng1A', () => ({
template: '', template: '',
scope: {}, scope: {},
bindToController: true, bindToController: true,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: function($scope: angular.IScope) { controller: function($scope: angular.IScope) {
Object.getPrototypeOf($scope).$doCheck = $doCheckSpyA; Object.getPrototypeOf($scope).$doCheck = $doCheckSpyA;
} }
})) }))
.directive('ng1B', () => ({ .directive('ng1B', () => ({
template: '', template: '',
scope: {}, scope: {},
bindToController: false, bindToController: false,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: function($scope: angular.IScope) { controller: function($scope: angular.IScope) {
$scope['$doCheck'] = $doCheckSpyB; $scope['$doCheck'] = $doCheckSpyB;
} }
})) }))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component)); .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({ @NgModule({
declarations: [ declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component Ng2Component
], ],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
const element = html(`<div><ng2></ng2></div>`); const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => { adapter.bootstrap(element, ['ng1']).ready((ref) => {
$doCheckSpyA.calls.reset(); $doCheckSpyA.calls.reset();
$doCheckSpyB.calls.reset(); $doCheckSpyB.calls.reset();
changeDetector.detectChanges(); changeDetector.detectChanges();
expect($doCheckSpyA).not.toHaveBeenCalled(); expect($doCheckSpyA).not.toHaveBeenCalled();
expect($doCheckSpyB).not.toHaveBeenCalled(); expect($doCheckSpyB).not.toHaveBeenCalled();
ref.dispose(); ref.dispose();
}); });
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown') &&
it('should call `$postLink()` on controller', async(() => { it('should call `$postLink()` on controller', async(() => {
@ -1885,52 +1883,51 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('unknown') && it('should not call `$postLink()` on scope', async(() => {
it('should not call `$postLink()` on scope', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const $postLinkSpy = jasmine.createSpy('$postLink');
const $postLinkSpy = jasmine.createSpy('$postLink');
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'}) @Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component { class Ng2Component {
} }
angular.module('ng1', []) angular.module('ng1', [])
.directive('ng1A', () => ({ .directive('ng1A', () => ({
template: '', template: '',
scope: {}, scope: {},
bindToController: true, bindToController: true,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: function($scope: angular.IScope) { controller: function($scope: angular.IScope) {
Object.getPrototypeOf($scope).$postLink = $postLinkSpy; Object.getPrototypeOf($scope).$postLink = $postLinkSpy;
} }
})) }))
.directive('ng1B', () => ({ .directive('ng1B', () => ({
template: '', template: '',
scope: {}, scope: {},
bindToController: false, bindToController: false,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: function($scope: angular.IScope) { controller: function($scope: angular.IScope) {
$scope['$postLink'] = $postLinkSpy; $scope['$postLink'] = $postLinkSpy;
} }
})) }))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component)); .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({ @NgModule({
declarations: [ declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'), adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component Ng2Component
], ],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
const element = html(`<div><ng2></ng2></div>`); const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => { adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($postLinkSpy).not.toHaveBeenCalled(); expect($postLinkSpy).not.toHaveBeenCalled();
ref.dispose(); ref.dispose();
}); });
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown') &&
it('should call `$onChanges()` on binding destination', fakeAsync(() => { it('should call `$onChanges()` on binding destination', fakeAsync(() => {

View File

@ -520,96 +520,94 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && it('should properly run cleanup when ng1 directive is destroyed', async(() => {
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);
}); });
})); }));
fixmeIvy('FW-642: ASSERTION ERROR: Slot should have been initialized to NO_CHANGE') && it('should properly run cleanup with multiple levels of nesting', async(() => {
it('should properly run cleanup with multiple levels of nesting', async(() => { let destroyed = false;
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(() => {
@ -743,7 +741,7 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-561: Runtime compiler is not loaded') && fixmeIvy('FW-717: Browser locks up and disconnects') &&
it('should work with ng2 lazy loaded components', async(() => { it('should work with ng2 lazy loaded components', async(() => {
let componentInjector: Injector; let componentInjector: Injector;

View File

@ -225,42 +225,41 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && it('should destroy components inside the Angular zone', async(() => {
it('should destroy components inside the Angular zone', async(() => { let destroyedInTheZone = false;
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);
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') &&
it('should propagate input changes inside the Angular zone', async(() => { it('should propagate input changes inside the Angular zone', async(() => {
@ -526,54 +525,53 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-717: Browser locks up and disconnects') && it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
it('should detach hostViews from the ApplicationRef once destroyed', async(() => { let ng2Component: Ng2Component;
let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})
class Ng2Component { class Ng2Component {
constructor(public appRef: ApplicationRef) { constructor(public appRef: ApplicationRef) {
ng2Component = this; ng2Component = this;
spyOn(appRef, 'attachView').and.callThrough(); spyOn(appRef, 'attachView').and.callThrough();
spyOn(appRef, 'detachView').and.callThrough(); spyOn(appRef, 'detachView').and.callThrough();
} }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive(
'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
const element = html('<ng2 ng-if="!hideNg2"></ng2>'); const element = html('<ng2 ng-if="!hideNg2"></ng2>');
const $injector = angular.bootstrap(element, [ng1Module.name]); const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
setTimeout(() => { // Wait for the module to be bootstrapped. setTimeout(() => { // Wait for the module to be bootstrapped.
setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`). setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`).
const hostView: ViewRef = const hostView: ViewRef =
(ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0]; (ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0];
expect(hostView.destroyed).toBe(false); expect(hostView.destroyed).toBe(false);
$rootScope.$apply('hideNg2 = true'); $rootScope.$apply('hideNg2 = true');
expect(hostView.destroyed).toBe(true); expect(hostView.destroyed).toBe(true);
expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView); expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView);
}); });
}); });
})); }));
it('should only retrieve the Angular zone once (and cache it for later use)', it('should only retrieve the Angular zone once (and cache it for later use)',
fakeAsync(() => { fakeAsync(() => {

View File

@ -3965,23 +3965,22 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-642: ASSERTION ERROR: Slot should have been initialized to NO_CHANGE') && it('should support ng2 > ng1 > ng2 (with inputs/outputs)', fakeAsync(() => {
it('should support ng2 > ng1 > ng2 (with inputs/outputs)', fakeAsync(() => { let ng2ComponentAInstance: Ng2ComponentA;
let ng2ComponentAInstance: Ng2ComponentA; let ng2ComponentBInstance: Ng2ComponentB;
let ng2ComponentBInstance: Ng2ComponentB; let ng1ControllerXInstance: Ng1ControllerX;
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"
@ -3989,39 +3988,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 }}"
@ -4031,131 +4030,130 @@ 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 = const ng1Module = angular.module('ng1', [])
angular.module('ng1', []) .component('ng1X', ng1Component)
.component('ng1X', ng1Component) .directive('ng2A', downgradeComponent({component: Ng2ComponentA}))
.directive('ng2A', downgradeComponent({component: Ng2ComponentA})) .directive('ng2B', downgradeComponent({component: Ng2ComponentB}));
.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 // (Should propagate upwards to `ng1ADataA` and back all the way down to
// `ng2BInputA`.) // `ng2BInputA`.)
(ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'}); (ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'});
$digest(adapter); $digest(adapter);
tick(); 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`