feat(upgrade): return a function (instead of array) from downgradeInjectable()
(#14037)
This makes it more consistent with the dynamic version of `upgrade` and makes it possible to share code between the dynamic and static versions. This commit also refactors the file layout, moving common and dynamic-specific files to `common/` and `dynamic/` directories respectively and renaming `aot/` to `static/`. Some private keys, used as AngularJS DI tokens, have also been renamed, but this should not affect apps, since these keys are undocumented and not supposed to be used externally. BREAKING CHANGE: Previously, `upgrade/static/downgradeInjectable` returned an array of the form: ```js ['dep1', 'dep2', ..., function factory(dep1, dep2, ...) { ... }] ``` Now it returns a function with an `$inject` property: ```js factory.$inject = ['dep1', 'dep2', ...]; function factory(dep1, dep2, ...) { ... } ``` It shouldn't affect the behavior of apps, since both forms are equally suitable to be used for registering AngularJS injectable services, but it is possible that type-checking might fail or that current code breaks if it relies on the returned value being an array.
This commit is contained in:

committed by
Miško Hevery

parent
49fce37013
commit
1f90f29369
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, ElementRef, Injector, Input, NgModule, NgZone, SimpleChange, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
|
||||
export function main() {
|
||||
describe('scope/component change-detection', () => {
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should interleave scope and component expressions', async(() => {
|
||||
const log: any[] /** TODO #9100 */ = [];
|
||||
const l = (value: any /** TODO #9100 */) => {
|
||||
log.push(value);
|
||||
return value + ';';
|
||||
};
|
||||
|
||||
@Directive({selector: 'ng1a'})
|
||||
class Ng1aComponent extends UpgradeComponent {
|
||||
constructor(elementRef: ElementRef, injector: Injector) {
|
||||
super('ng1a', elementRef, injector);
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'ng1b'})
|
||||
class Ng1bComponent extends UpgradeComponent {
|
||||
constructor(elementRef: ElementRef, injector: Injector) {
|
||||
super('ng1b', elementRef, injector);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
|
||||
})
|
||||
class Ng2Component {
|
||||
l: (value: any) => string;
|
||||
constructor() { this.l = l; }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng1aComponent, Ng1bComponent, Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', [])
|
||||
.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}))
|
||||
.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}))
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
||||
.run(($rootScope: any /** TODO #9100 */) => {
|
||||
$rootScope.l = l;
|
||||
$rootScope.reset = () => log.length = 0;
|
||||
});
|
||||
|
||||
const element =
|
||||
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
||||
// https://github.com/angular/angular.js/issues/12983
|
||||
expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||
let appComponent: AppComponent;
|
||||
|
||||
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
|
||||
class AppComponent {
|
||||
value: number;
|
||||
constructor() { appComponent = this; }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-child',
|
||||
template: '<div>{{valueFromPromise}}',
|
||||
})
|
||||
class ChildComponent {
|
||||
valueFromPromise: number;
|
||||
@Input()
|
||||
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
|
||||
|
||||
constructor(private zone: NgZone) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['value'].isFirstChange()) return;
|
||||
|
||||
this.zone.onMicrotaskEmpty.subscribe(
|
||||
() => { expect(element.textContent).toEqual('5'); });
|
||||
|
||||
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, ChildComponent],
|
||||
entryComponents: [AppComponent],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'myApp', downgradeComponent({component: AppComponent}));
|
||||
|
||||
|
||||
const element = html('<my-app></my-app>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
appComponent.value = 5;
|
||||
});
|
||||
}));
|
||||
|
||||
// This test demonstrates https://github.com/angular/angular/issues/6385
|
||||
// which was invalidly fixed by https://github.com/angular/angular/pull/6386
|
||||
// it('should not trigger $digest from an async operation in a watcher', async(() => {
|
||||
// @Component({selector: 'my-app', template: ''})
|
||||
// class AppComponent {
|
||||
// }
|
||||
|
||||
// @NgModule({declarations: [AppComponent], imports: [BrowserModule]})
|
||||
// class Ng2Module {
|
||||
// }
|
||||
|
||||
// const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
// const ng1Module = angular.module('ng1', []).directive(
|
||||
// 'myApp', adapter.downgradeNg2Component(AppComponent));
|
||||
|
||||
// const element = html('<my-app></my-app>');
|
||||
|
||||
// adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
// let doTimeout = false;
|
||||
// let timeoutId: number;
|
||||
// ref.ng1RootScope.$watch(() => {
|
||||
// if (doTimeout && !timeoutId) {
|
||||
// timeoutId = window.setTimeout(function() {
|
||||
// timeoutId = null;
|
||||
// }, 10);
|
||||
// }
|
||||
// });
|
||||
// doTimeout = true;
|
||||
// });
|
||||
// }));
|
||||
});
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, ElementRef, Injector, NgModule, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
|
||||
export function main() {
|
||||
describe('content projection', () => {
|
||||
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should instantiate ng2 in ng1 template and project content', async(() => {
|
||||
|
||||
// the ng2 component that will be used in ng1 (downgraded)
|
||||
@Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
||||
class Ng2Component {
|
||||
}
|
||||
|
||||
// our upgrade module to host the component to downgrade
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
// the ng1 app module that will consume the downgraded component
|
||||
const ng1Module = angular
|
||||
.module('ng1', [])
|
||||
// create an ng1 facade of the ng2 component
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
|
||||
const element =
|
||||
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
||||
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||
})
|
||||
class Ng2Component {
|
||||
}
|
||||
|
||||
|
||||
@Directive({selector: 'ng1'})
|
||||
class Ng1WrapperComponent extends UpgradeComponent {
|
||||
constructor(elementRef: ElementRef, injector: Injector) {
|
||||
super('ng1', elementRef, injector);
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng1WrapperComponent, Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', [])
|
||||
.directive(
|
||||
'ng1',
|
||||
() => {
|
||||
return {
|
||||
transclude: true,
|
||||
template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'
|
||||
};
|
||||
})
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
|
||||
const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
@ -0,0 +1,304 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, EventEmitter, NgModule, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html, multiTrim} from '../test_helpers';
|
||||
|
||||
export function main() {
|
||||
describe('downgrade ng2 component', () => {
|
||||
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should bind properties, events', async(() => {
|
||||
|
||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||
$rootScope['dataA'] = 'A';
|
||||
$rootScope['dataB'] = 'B';
|
||||
$rootScope['modelA'] = 'initModelA';
|
||||
$rootScope['modelB'] = 'initModelB';
|
||||
$rootScope['eventA'] = '?';
|
||||
$rootScope['eventB'] = '?';
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
outputs: [
|
||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange'
|
||||
],
|
||||
template: 'ignore: {{ignore}}; ' +
|
||||
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
||||
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
||||
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
ignore = '-';
|
||||
literal = '?';
|
||||
interpolate = '?';
|
||||
oneWayA = '?';
|
||||
oneWayB = '?';
|
||||
twoWayA = '?';
|
||||
twoWayB = '?';
|
||||
eventA = new EventEmitter();
|
||||
eventB = new EventEmitter();
|
||||
twoWayAEmitter = new EventEmitter();
|
||||
twoWayBEmitter = new EventEmitter();
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const assert = (prop: string, value: any) => {
|
||||
const propVal = (this as any)[prop];
|
||||
if (propVal != value) {
|
||||
throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`);
|
||||
}
|
||||
};
|
||||
|
||||
const assertChange = (prop: string, value: any) => {
|
||||
assert(prop, value);
|
||||
if (!changes[prop]) {
|
||||
throw new Error(`Changes record for '${prop}' not found.`);
|
||||
}
|
||||
const actualValue = changes[prop].currentValue;
|
||||
if (actualValue != value) {
|
||||
throw new Error(
|
||||
`Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`);
|
||||
}
|
||||
};
|
||||
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
assert('ignore', '-');
|
||||
assertChange('literal', 'Text');
|
||||
assertChange('interpolate', 'Hello world');
|
||||
assertChange('oneWayA', 'A');
|
||||
assertChange('oneWayB', 'B');
|
||||
assertChange('twoWayA', 'initModelA');
|
||||
assertChange('twoWayB', 'initModelB');
|
||||
|
||||
this.twoWayAEmitter.emit('newA');
|
||||
this.twoWayBEmitter.emit('newB');
|
||||
this.eventA.emit('aFired');
|
||||
this.eventB.emit('bFired');
|
||||
break;
|
||||
case 1:
|
||||
assertChange('twoWayA', 'newA');
|
||||
break;
|
||||
case 2:
|
||||
assertChange('twoWayB', 'newB');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ng1Module.directive(
|
||||
'ng2', downgradeComponent({
|
||||
component: Ng2Component,
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
outputs: [
|
||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
|
||||
'twoWayBEmitter: twoWayBChange'
|
||||
]
|
||||
}));
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const element = html(`
|
||||
<div>
|
||||
<ng2 literal="Text" interpolate="Hello {{'world'}}"
|
||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
||||
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||
</div>`);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello world; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should bind to ng-model', async(() => {
|
||||
const ng1Module = angular.module('ng1', []).run(
|
||||
($rootScope: angular.IScope) => { $rootScope['modelA'] = 'A'; });
|
||||
|
||||
let ng2Instance: Ng2;
|
||||
@Component({selector: 'ng2', template: '<span>{{_value}}</span>'})
|
||||
class Ng2 {
|
||||
private _value: any = '';
|
||||
private _onChangeCallback: (_: any) => void = () => {};
|
||||
constructor() { ng2Instance = this; }
|
||||
writeValue(value: any) { this._value = value; }
|
||||
registerOnChange(fn: any) { this._onChangeCallback = fn; }
|
||||
doChange(newValue: string) {
|
||||
this._value = newValue;
|
||||
this._onChangeCallback(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
ng1Module.directive('ng2', downgradeComponent({component: Ng2}));
|
||||
|
||||
const element = html(`<div><ng2 ng-model="modelA"></ng2> | {{modelA}}</div>`);
|
||||
|
||||
@NgModule(
|
||||
{declarations: [Ng2], entryComponents: [Ng2], imports: [BrowserModule, UpgradeModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(Ng2Module).then((ref) => {
|
||||
const adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
|
||||
adapter.bootstrap(element, [ng1Module.name]);
|
||||
const $rootScope = adapter.$injector.get('$rootScope');
|
||||
|
||||
expect(multiTrim(document.body.textContent)).toEqual('A | A');
|
||||
|
||||
$rootScope.modelA = 'B';
|
||||
$rootScope.$apply();
|
||||
expect(multiTrim(document.body.textContent)).toEqual('B | B');
|
||||
|
||||
ng2Instance.doChange('C');
|
||||
expect($rootScope.modelA).toBe('C');
|
||||
expect(multiTrim(document.body.textContent)).toEqual('C | C');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
|
||||
|
||||
let destroyed = false;
|
||||
@Component({selector: 'ng2', template: 'test'})
|
||||
class Ng2Component implements OnDestroy {
|
||||
ngOnDestroy() { destroyed = true; }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module =
|
||||
angular.module('ng1', [])
|
||||
.directive(
|
||||
'ng1',
|
||||
() => { return {template: '<div ng-if="!destroyIt"><ng2></ng2></div>'}; })
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
const element = html('<ng1></ng1>');
|
||||
platformBrowserDynamic().bootstrapModule(Ng2Module).then((ref) => {
|
||||
const adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
|
||||
adapter.bootstrap(element, [ng1Module.name]);
|
||||
expect(element.textContent).toContain('test');
|
||||
expect(destroyed).toBe(false);
|
||||
|
||||
const $rootScope = adapter.$injector.get('$rootScope');
|
||||
$rootScope.$apply('destroyIt = true');
|
||||
|
||||
expect(element.textContent).not.toContain('test');
|
||||
expect(destroyed).toBe(true);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should work when compiled outside the dom (by fallback to the root ng2.injector)',
|
||||
async(() => {
|
||||
|
||||
@Component({selector: 'ng2', template: 'test'})
|
||||
class Ng2Component {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module =
|
||||
angular.module('ng1', [])
|
||||
.directive(
|
||||
'ng1',
|
||||
[
|
||||
'$compile',
|
||||
($compile: angular.ICompileService) => {
|
||||
return {
|
||||
link: function(
|
||||
$scope: angular.IScope, $element: angular.IAugmentedJQuery,
|
||||
$attrs: angular.IAttributes) {
|
||||
// here we compile some HTML that contains a downgraded component
|
||||
// since it is not currently in the DOM it is not able to "require"
|
||||
// an ng2 injector so it should use the `moduleInjector` instead.
|
||||
const compiled = $compile('<ng2></ng2>');
|
||||
const template = compiled($scope);
|
||||
$element.append(template);
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
|
||||
const element = html('<ng1></ng1>');
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
// the fact that the body contains the correct text means that the
|
||||
// downgraded component was able to access the moduleInjector
|
||||
// (since there is no other injector in this system)
|
||||
expect(multiTrim(document.body.textContent)).toEqual('test');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should allow attribute selectors for components in ng2', async(() => {
|
||||
@Component({selector: '[itWorks]', template: 'It works'})
|
||||
class WorksComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'root-component', template: '<span itWorks></span>!'})
|
||||
class RootComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [RootComponent, WorksComponent],
|
||||
entryComponents: [RootComponent],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'rootComponent', downgradeComponent({component: RootComponent}));
|
||||
|
||||
const element = html('<root-component></root-component>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(multiTrim(document.body.textContent)).toBe('It works!');
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, ElementRef, Injector, Input, NgModule, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html, multiTrim} from '../test_helpers';
|
||||
|
||||
export function main() {
|
||||
describe('examples', () => {
|
||||
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1));
|
||||
|
||||
it('should verify UpgradeAdapter example', async(() => {
|
||||
|
||||
// This is wrapping (upgrading) an AngularJS component to be used in an Angular
|
||||
// component
|
||||
@Directive({selector: 'ng1'})
|
||||
class Ng1Component extends UpgradeComponent {
|
||||
@Input() title: string;
|
||||
|
||||
constructor(elementRef: ElementRef, injector: Injector) {
|
||||
super('ng1', elementRef, injector);
|
||||
}
|
||||
}
|
||||
|
||||
// This is an Angular component that will be downgraded
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)'
|
||||
})
|
||||
class Ng2Component {
|
||||
@Input('name') nameProp: string;
|
||||
}
|
||||
|
||||
// This module represents the Angular pieces of the application
|
||||
@NgModule({
|
||||
declarations: [Ng1Component, Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */
|
||||
}
|
||||
}
|
||||
|
||||
// This module represents the AngularJS pieces of the application
|
||||
const ng1Module =
|
||||
angular
|
||||
.module('myExample', [])
|
||||
// This is an AngularJS component that will be upgraded
|
||||
.directive(
|
||||
'ng1',
|
||||
() => {
|
||||
return {
|
||||
scope: {title: '='},
|
||||
transclude: true,
|
||||
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
||||
};
|
||||
})
|
||||
// This is wrapping (downgrading) an Angular component to be used in AngularJS
|
||||
.directive(
|
||||
'ng2',
|
||||
downgradeComponent({component: Ng2Component, inputs: ['nameProp: name']}));
|
||||
|
||||
// This is the (AngularJS) application bootstrap element
|
||||
// Notice that it is actually a downgraded Angular component
|
||||
const element = html('<ng2 name="World">project</ng2>');
|
||||
|
||||
// Let's use a helper function to make this simpler
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
expect(multiTrim(element.textContent))
|
||||
.toBe('ng2[ng1[Hello World!](transclude)](project)');
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectionToken, Injector, NgModule, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {$INJECTOR, INJECTOR_KEY} from '@angular/upgrade/src/common/constants';
|
||||
import {UpgradeModule, downgradeInjectable} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
|
||||
export function main() {
|
||||
describe('injection', () => {
|
||||
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should downgrade ng2 service to ng1', async(() => {
|
||||
// Tokens used in ng2 to identify services
|
||||
const Ng2Service = new InjectionToken('ng2-service');
|
||||
|
||||
// Sample ng1 NgModule for tests
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
providers: [
|
||||
{provide: Ng2Service, useValue: 'ng2 service value'},
|
||||
]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
// create the ng1 module that will import an ng2 service
|
||||
const ng1Module =
|
||||
angular.module('ng1Module', []).factory('ng2Service', downgradeInjectable(Ng2Service));
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, html('<div>'), ng1Module)
|
||||
.then((upgrade) => {
|
||||
const ng1Injector = upgrade.$injector;
|
||||
expect(ng1Injector.get('ng2Service')).toBe('ng2 service value');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should upgrade ng1 service to ng2', async(() => {
|
||||
// Tokens used in ng2 to identify services
|
||||
const Ng1Service = new InjectionToken('ng1-service');
|
||||
|
||||
// Sample ng1 NgModule for tests
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
providers: [
|
||||
// the following line is the "upgrade" of an AngularJS service
|
||||
{
|
||||
provide: Ng1Service,
|
||||
useFactory: (i: angular.IInjectorService) => i.get('ng1Service'),
|
||||
deps: ['$injector']
|
||||
}
|
||||
]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
// create the ng1 module that will import an ng2 service
|
||||
const ng1Module = angular.module('ng1Module', []).value('ng1Service', 'ng1 service value');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, html('<div>'), ng1Module)
|
||||
.then((upgrade) => {
|
||||
const ng2Injector = upgrade.injector;
|
||||
expect(ng2Injector.get(Ng1Service)).toBe('ng1 service value');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should initialize the upgraded injector before application run blocks are executed',
|
||||
async(() => {
|
||||
let runBlockTriggered = false;
|
||||
|
||||
const ng1Module = angular.module('ng1Module', []).run([
|
||||
INJECTOR_KEY,
|
||||
function(injector: Injector) {
|
||||
runBlockTriggered = true;
|
||||
expect(injector.get($INJECTOR)).toBeDefined();
|
||||
}
|
||||
]);
|
||||
|
||||
@NgModule({imports: [BrowserModule, UpgradeModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, html('<div>'), ng1Module).then(() => {
|
||||
expect(runBlockTriggered).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModule, Testability, destroyPlatform} from '@angular/core';
|
||||
import {NgZone} from '@angular/core/src/zone/ng_zone';
|
||||
import {fakeAsync, tick} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {UpgradeModule} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
|
||||
export function main() {
|
||||
describe('testability', () => {
|
||||
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
@NgModule({imports: [BrowserModule, UpgradeModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
it('should handle deferred bootstrap', fakeAsync(() => {
|
||||
let applicationRunning = false;
|
||||
let stayedInTheZone: boolean;
|
||||
const ng1Module = angular.module('ng1', []).run(() => {
|
||||
applicationRunning = true;
|
||||
stayedInTheZone = NgZone.isInAngularZone();
|
||||
});
|
||||
|
||||
const element = html('<div></div>');
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module);
|
||||
|
||||
setTimeout(() => { (<any>window).angular.resumeBootstrap(); }, 100);
|
||||
|
||||
expect(applicationRunning).toEqual(false);
|
||||
tick(100);
|
||||
expect(applicationRunning).toEqual(true);
|
||||
expect(stayedInTheZone).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should wait for ng2 testability', fakeAsync(() => {
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
const element = html('<div></div>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
|
||||
const ng2Testability: Testability = upgrade.injector.get(Testability);
|
||||
ng2Testability.increasePendingRequestCount();
|
||||
let ng2Stable = false;
|
||||
let ng1Stable = false;
|
||||
|
||||
angular.getTestability(element).whenStable(() => { ng1Stable = true; });
|
||||
|
||||
setTimeout(() => {
|
||||
ng2Stable = true;
|
||||
ng2Testability.decreasePendingRequestCount();
|
||||
}, 100);
|
||||
|
||||
expect(ng1Stable).toEqual(false);
|
||||
expect(ng2Stable).toEqual(false);
|
||||
tick(100);
|
||||
expect(ng1Stable).toEqual(true);
|
||||
expect(ng2Stable).toEqual(true);
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user