1386 lines
57 KiB
TypeScript
1386 lines
57 KiB
TypeScript
/**
|
|
* @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 {ChangeDetectorRef, Class, Component, EventEmitter, NO_ERRORS_SCHEMA, NgModule, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
|
|
import {async, fakeAsync, flushMicrotasks, 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/angular_js';
|
|
import {UpgradeAdapter, sortProjectableNodes} from '@angular/upgrade/src/upgrade_adapter';
|
|
|
|
export function main() {
|
|
describe('adapter: ng1 to ng2', () => {
|
|
beforeEach(() => destroyPlatform());
|
|
afterEach(() => destroyPlatform());
|
|
|
|
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
|
|
|
|
describe('bootstrap errors', () => {
|
|
let adapter: UpgradeAdapter;
|
|
|
|
beforeEach(() => {
|
|
angular.module('ng1', []);
|
|
|
|
const ng2Component = Component({
|
|
selector: 'ng2',
|
|
template: `<BAD TEMPLATE div></div>`,
|
|
}).Class({constructor: function() {}});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [ng2Component],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
adapter = new UpgradeAdapter(Ng2Module);
|
|
});
|
|
|
|
it('should throw an uncaught error', fakeAsync(() => {
|
|
const resolveSpy = jasmine.createSpy('resolveSpy');
|
|
spyOn(console, 'error');
|
|
|
|
expect(() => {
|
|
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']).ready(resolveSpy);
|
|
flushMicrotasks();
|
|
}).toThrowError();
|
|
expect(resolveSpy).not.toHaveBeenCalled();
|
|
}));
|
|
|
|
it('should output an error message to the console and re-throw', fakeAsync(() => {
|
|
let consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
|
|
expect(() => {
|
|
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
|
|
flushMicrotasks();
|
|
}).toThrowError();
|
|
let args: any[] = consoleErrorSpy.calls.mostRecent().args;
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
expect(args.length).toBeGreaterThan(0);
|
|
expect(args[0]).toEqual(jasmine.any(Error));
|
|
}));
|
|
});
|
|
|
|
it('should instantiate ng2 in ng1 template and project content', async(() => {
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const Ng2 = Component({
|
|
selector: 'ng2',
|
|
template: `{{ 'NG2' }}(<ng-content></ng-content>)`,
|
|
}).Class({constructor: function() {}});
|
|
|
|
const Ng2Module = NgModule({declarations: [Ng2], imports: [BrowserModule]}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const element =
|
|
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const Ng2 = Component({
|
|
selector: 'ng2',
|
|
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
|
}).Class({constructor: function Ng2() {}});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function Ng2Module() {}});
|
|
|
|
ng1Module.directive('ng1', () => {
|
|
return {transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'};
|
|
});
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
|
|
const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>');
|
|
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('supports the compilerOptions argument', async(() => {
|
|
const platformRef = platformBrowserDynamic();
|
|
spyOn(platformRef, '_bootstrapModuleWithZone').and.callThrough();
|
|
|
|
const ng1Module = angular.module('ng1', []);
|
|
const Ng2 = Component({
|
|
selector: 'ng2',
|
|
template: `{{ 'NG2' }}(<ng-content></ng-content>)`
|
|
}).Class({constructor: function() {}});
|
|
|
|
const element =
|
|
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
|
|
|
const Ng2AppModule =
|
|
NgModule({
|
|
declarations: [Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function Ng2AppModule() {}, ngDoBootstrap: function() {}});
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect((platformRef as any)._bootstrapModuleWithZone)
|
|
.toHaveBeenCalledWith(
|
|
jasmine.any(Function), {providers: []}, jasmine.any(Object),
|
|
jasmine.any(Function));
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
describe('scope/component change-detection', () => {
|
|
it('should interleave scope and component expressions', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
const log: string[] = [];
|
|
const l = (value: string) => {
|
|
log.push(value);
|
|
return value + ';';
|
|
};
|
|
|
|
ng1Module.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}));
|
|
ng1Module.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}));
|
|
ng1Module.run(($rootScope: any) => {
|
|
$rootScope.l = l;
|
|
$rootScope.reset = () => log.length = 0;
|
|
});
|
|
|
|
const Ng2 = Component({
|
|
selector: 'ng2',
|
|
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
|
|
}).Class({constructor: function() { this.l = l; }});
|
|
|
|
const Ng2Module =
|
|
NgModule({
|
|
declarations: [
|
|
adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2
|
|
],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
|
|
const element =
|
|
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
|
// https://github.com/angular/angular.js/issues/12983
|
|
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
});
|
|
|
|
describe('downgrade ng2 component', () => {
|
|
it('should bind properties, events', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
ng1Module.run(($rootScope: any) => {
|
|
$rootScope.dataA = 'A';
|
|
$rootScope.dataB = 'B';
|
|
$rootScope.modelA = 'initModelA';
|
|
$rootScope.modelB = 'initModelB';
|
|
$rootScope.eventA = '?';
|
|
$rootScope.eventB = '?';
|
|
});
|
|
const Ng2 = 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({
|
|
constructor: function() {
|
|
this.ngOnChangesCount = 0;
|
|
this.ignore = '-';
|
|
this.literal = '?';
|
|
this.interpolate = '?';
|
|
this.oneWayA = '?';
|
|
this.oneWayB = '?';
|
|
this.twoWayA = '?';
|
|
this.twoWayB = '?';
|
|
this.eventA = new EventEmitter();
|
|
this.eventB = new EventEmitter();
|
|
this.twoWayAEmitter = new EventEmitter();
|
|
this.twoWayBEmitter = new EventEmitter();
|
|
},
|
|
ngOnChanges: function(changes: SimpleChanges) {
|
|
const assert = (prop: string, value: any) => {
|
|
if (this[prop] != value) {
|
|
throw new Error(`Expected: '${prop}' to be '${value}' but was '${this[prop]}'`);
|
|
}
|
|
};
|
|
|
|
const assertChange = (prop: string, value: any) => {
|
|
assert(prop, value);
|
|
if (!changes[prop]) {
|
|
throw new Error(`Changes record for '${prop}' not found.`);
|
|
}
|
|
const actValue = changes[prop].currentValue;
|
|
if (actValue != value) {
|
|
throw new Error(
|
|
`Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`);
|
|
}
|
|
};
|
|
|
|
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', adapter.downgradeNg2Component(Ng2));
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
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>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
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;');
|
|
ref.dispose();
|
|
});
|
|
|
|
}));
|
|
|
|
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
const onDestroyed: EventEmitter<string> = new EventEmitter<string>();
|
|
|
|
ng1Module.directive('ng1', () => {
|
|
return {
|
|
template: '<div ng-if="!destroyIt"><ng2></ng2></div>',
|
|
controller: function($rootScope: any, $timeout: Function) {
|
|
$timeout(() => { $rootScope.destroyIt = true; });
|
|
}
|
|
};
|
|
});
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: 'test'}).Class({
|
|
constructor: function() {},
|
|
ngOnDestroy: function() { onDestroyed.emit('destroyed'); }
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html('<ng1></ng1>');
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
onDestroyed.subscribe(() => { ref.dispose(); });
|
|
});
|
|
}));
|
|
|
|
|
|
it('should fallback to the root ng2.injector when compiled outside the dom', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
ng1Module.directive('ng1', [
|
|
'$compile',
|
|
($compile: Function) => {
|
|
return {
|
|
link: function($scope: any, $element: any, $attrs: any) {
|
|
const compiled = $compile('<ng2></ng2>');
|
|
const template = compiled($scope);
|
|
$element.append(template);
|
|
}
|
|
};
|
|
}
|
|
]);
|
|
|
|
const Ng2 =
|
|
Component({selector: 'ng2', template: 'test'}).Class({constructor: function() {}});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html('<ng1></ng1>');
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('test');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support multi-slot projection', async(() => {
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const Ng2 = Component({
|
|
selector: 'ng2',
|
|
template: '2a(<ng-content select=".ng1a"></ng-content>)' +
|
|
'2b(<ng-content select=".ng1b"></ng-content>)'
|
|
}).Class({constructor: function() {}});
|
|
|
|
const Ng2Module = NgModule({declarations: [Ng2], imports: [BrowserModule]}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
// The ng-if on one of the projected children is here to make sure
|
|
// the correct slot is targeted even with structural directives in play.
|
|
const element = html(
|
|
'<ng2><div ng-if="true" class="ng1a">1a</div><div' +
|
|
' class="ng1b">1b</div></ng2>');
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(document.body.textContent).toEqual('2a(1a)2b(1b)');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
});
|
|
|
|
describe('upgrade ng1 component', () => {
|
|
it('should bind properties, events', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; C: {{modelC}}; | ',
|
|
scope: {fullName: '@', modelA: '=dataA', modelB: '=dataB', modelC: '=', event: '&'},
|
|
link: function(scope: any) {
|
|
scope.$watch('dataB', (v: string) => {
|
|
if (v == 'Savkin') {
|
|
scope.dataB = 'SAVKIN';
|
|
scope.event('WORKS');
|
|
|
|
// Should not update because [model-a] is uni directional
|
|
scope.dataA = 'VICTOR';
|
|
}
|
|
});
|
|
}
|
|
};
|
|
};
|
|
ng1Module.directive('ng1', ng1);
|
|
const Ng2 =
|
|
Component({
|
|
selector: 'ng2',
|
|
template:
|
|
'<ng1 fullName="{{last}}, {{first}}, {{city}}" [modelA]="first" [(modelB)]="last" [modelC]="city" ' +
|
|
'(event)="event=$event"></ng1>' +
|
|
'<ng1 fullName="{{\'TEST\'}}" modelA="First" modelB="Last" modelC="City"></ng1>' +
|
|
'{{event}}-{{last}}, {{first}}, {{city}}'
|
|
}).Class({
|
|
constructor: function() {
|
|
this.first = 'Victor';
|
|
this.last = 'Savkin';
|
|
this.city = 'SF';
|
|
this.event = '?';
|
|
}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
|
// events, and so without this we would not see the events processed.
|
|
setTimeout(() => {
|
|
expect(multiTrim(document.body.textContent))
|
|
.toEqual(
|
|
'Hello SAVKIN, Victor, SF; A: VICTOR; B: SAVKIN; C: SF; | Hello TEST; A: First; B: Last; C: City; | WORKS-SAVKIN, Victor, SF');
|
|
ref.dispose();
|
|
}, 0);
|
|
});
|
|
}));
|
|
|
|
it('should bind optional properties', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
template: 'Hello; A: {{dataA}}; B: {{modelB}}; | ',
|
|
scope: {modelA: '=?dataA', modelB: '=?'}
|
|
};
|
|
};
|
|
ng1Module.directive('ng1', ng1);
|
|
const Ng2 = Component({
|
|
selector: 'ng2',
|
|
template: '<ng1 [modelA]="first" [modelB]="last"></ng1>' +
|
|
'<ng1 modelA="First" modelB="Last"></ng1>' +
|
|
'<ng1></ng1>' +
|
|
'<ng1></ng1>'
|
|
}).Class({
|
|
constructor: function() {
|
|
this.first = 'Victor';
|
|
this.last = 'Savkin';
|
|
}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
|
// events, and so without this we would not see the events processed.
|
|
setTimeout(() => {
|
|
expect(multiTrim(document.body.textContent))
|
|
.toEqual(
|
|
'Hello; A: Victor; B: Savkin; | Hello; A: First; B: Last; | Hello; A: ; B: ; | Hello; A: ; B: ; |');
|
|
ref.dispose();
|
|
}, 0);
|
|
});
|
|
}));
|
|
|
|
it('should bind properties, events in controller when bindToController is not used',
|
|
async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
restrict: 'E',
|
|
template: '{{someText}} - Length: {{data.length}}',
|
|
scope: {data: '='},
|
|
controller: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; }
|
|
};
|
|
};
|
|
|
|
ng1Module.directive('ng1', ng1);
|
|
const Ng2 =
|
|
Component({
|
|
selector: 'ng2',
|
|
template:
|
|
'{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>'
|
|
}).Class({
|
|
|
|
constructor: function() {
|
|
this.dataList = [1, 2, 3];
|
|
this.someText = 'ng2';
|
|
}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
|
// events, and so without this we would not see the events processed.
|
|
setTimeout(() => {
|
|
expect(multiTrim(document.body.textContent))
|
|
.toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3');
|
|
ref.dispose();
|
|
}, 0);
|
|
});
|
|
}));
|
|
|
|
it('should bind properties, events in link function', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
restrict: 'E',
|
|
template: '{{someText}} - Length: {{data.length}}',
|
|
scope: {data: '='},
|
|
link: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; }
|
|
};
|
|
};
|
|
|
|
ng1Module.directive('ng1', ng1);
|
|
const Ng2 =
|
|
Component({
|
|
selector: 'ng2',
|
|
template:
|
|
'{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>'
|
|
}).Class({
|
|
|
|
constructor: function() {
|
|
this.dataList = [1, 2, 3];
|
|
this.someText = 'ng2';
|
|
}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
|
// events, and so without this we would not see the events processed.
|
|
setTimeout(() => {
|
|
expect(multiTrim(document.body.textContent))
|
|
.toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3');
|
|
ref.dispose();
|
|
}, 0);
|
|
});
|
|
}));
|
|
|
|
it('should support templateUrl fetched from $httpBackend', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
ng1Module.value(
|
|
'$httpBackend', (method: string, url: string, post: any, cbFn: Function) => {
|
|
cbFn(200, `${method}:${url}`);
|
|
});
|
|
|
|
const ng1 = () => { return {templateUrl: 'url.html'}; };
|
|
ng1Module.directive('ng1', ng1);
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('GET:url.html');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support templateUrl as a function', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
ng1Module.value(
|
|
'$httpBackend', (method: string, url: string, post: any, cbFn: Function) => {
|
|
cbFn(200, `${method}:${url}`);
|
|
});
|
|
|
|
const ng1 = () => { return {templateUrl() { return 'url.html'; }}; };
|
|
ng1Module.directive('ng1', ng1);
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('GET:url.html');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support empty template', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => { return {template: ''}; };
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support template as a function', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => { return {template() { return ''; }}; };
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support templateUrl fetched from $templateCache', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
ng1Module.run(($templateCache: any) => $templateCache.put('url.html', 'WORKS'));
|
|
|
|
const ng1 = () => { return {templateUrl: 'url.html'}; };
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('WORKS');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support controller with controllerAs', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
scope: true,
|
|
template:
|
|
'{{ctl.scope}}; {{ctl.isClass}}; {{ctl.hasElement}}; {{ctl.isPublished()}}',
|
|
controllerAs: 'ctl',
|
|
controller: Class({
|
|
constructor: function($scope: any, $element: any) {
|
|
(<any>this).verifyIAmAClass();
|
|
this.scope = $scope.$parent.$parent == $scope.$root ? 'scope' : 'wrong-scope';
|
|
this.hasElement = $element[0].nodeName;
|
|
this.$element = $element;
|
|
},
|
|
verifyIAmAClass: function() { this.isClass = 'isClass'; },
|
|
isPublished: function() {
|
|
return this.$element.controller('ng1') == this ? 'published' : 'not-published';
|
|
}
|
|
})
|
|
};
|
|
};
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('scope; isClass; NG1; published');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support bindToController', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
scope: {title: '@'},
|
|
bindToController: true,
|
|
template: '{{ctl.title}}',
|
|
controllerAs: 'ctl',
|
|
controller: Class({constructor: function() {}})
|
|
};
|
|
};
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1 title="WORKS"></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('WORKS');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support bindToController with bindings', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
scope: {},
|
|
bindToController: {title: '@'},
|
|
template: '{{ctl.title}}',
|
|
controllerAs: 'ctl',
|
|
controller: Class({constructor: function() {}})
|
|
};
|
|
};
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1 title="WORKS"></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('WORKS');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support single require in linking fn', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = ($rootScope: any) => {
|
|
return {
|
|
scope: {title: '@'},
|
|
bindToController: true,
|
|
template: '{{ctl.status}}',
|
|
require: 'ng1',
|
|
controllerAs: 'ctrl',
|
|
controller: Class({constructor: function() { this.status = 'WORKS'; }}),
|
|
link: function(scope: any, element: any, attrs: any, linkController: any) {
|
|
expect(scope.$root).toEqual($rootScope);
|
|
expect(element[0].nodeName).toEqual('NG1');
|
|
expect(linkController.status).toEqual('WORKS');
|
|
scope.ctl = linkController;
|
|
}
|
|
};
|
|
};
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('WORKS');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support array require in linking fn', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const parent = () => {
|
|
return {controller: Class({constructor: function() { this.parent = 'PARENT'; }})};
|
|
};
|
|
const ng1 = () => {
|
|
return {
|
|
scope: {title: '@'},
|
|
bindToController: true,
|
|
template: '{{parent.parent}}:{{ng1.status}}',
|
|
require: ['ng1', '^parent', '?^^notFound'],
|
|
controllerAs: 'ctrl',
|
|
controller: Class({constructor: function() { this.status = 'WORKS'; }}),
|
|
link: function(scope: any, element: any, attrs: any, linkControllers: any) {
|
|
expect(linkControllers[0].status).toEqual('WORKS');
|
|
expect(linkControllers[1].parent).toEqual('PARENT');
|
|
expect(linkControllers[2]).toBe(undefined);
|
|
scope.ng1 = linkControllers[0];
|
|
scope.parent = linkControllers[1];
|
|
}
|
|
};
|
|
};
|
|
ng1Module.directive('parent', parent);
|
|
ng1Module.directive('ng1', ng1);
|
|
|
|
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
const element = html(`<div><parent><ng2></ng2></parent></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('PARENT:WORKS');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should call $onInit of components', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const $onInitSpy = jasmine.createSpy('$onInit');
|
|
|
|
@Component({selector: 'ng2', template: '<ng1></ng1>'})
|
|
class Ng2Component {
|
|
}
|
|
|
|
angular.module('ng1', [])
|
|
.component('ng1', {
|
|
bindings: {},
|
|
template: '',
|
|
controller: function() { this.$onInit = $onInitSpy; }
|
|
})
|
|
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
|
|
|
@NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
|
|
imports: [BrowserModule],
|
|
})
|
|
class Ng2Module {
|
|
}
|
|
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect($onInitSpy).toHaveBeenCalled();
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should call $doCheck of components', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const $doCheckSpy = jasmine.createSpy('$doCheck');
|
|
let changeDetector: ChangeDetectorRef;
|
|
|
|
@Component({selector: 'ng2', template: '<ng1></ng1>'})
|
|
class Ng2Component {
|
|
constructor(cd: ChangeDetectorRef) { changeDetector = cd; }
|
|
}
|
|
|
|
angular.module('ng1', [])
|
|
.component('ng1', {
|
|
bindings: {},
|
|
template: '{{$ctrl.value}}',
|
|
controller: function() { this.$doCheck = $doCheckSpy; }
|
|
})
|
|
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
|
|
|
|
|
@NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
|
|
imports: [BrowserModule],
|
|
})
|
|
class Ng2Module {
|
|
}
|
|
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect($doCheckSpy).toHaveBeenCalled();
|
|
|
|
$doCheckSpy.calls.reset();
|
|
changeDetector.detectChanges();
|
|
|
|
expect($doCheckSpy).toHaveBeenCalled();
|
|
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should call $onChanges of components', fakeAsync(() => {
|
|
const EXPECTED_VALUE = '$onChanges called';
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const $onChangesSpy = jasmine.createSpy('$onChanges');
|
|
let ng2Instance: any;
|
|
|
|
@Component({selector: 'ng2', template: '<ng1 [val]="val"></ng1>'})
|
|
class Ng2Component {
|
|
constructor() { ng2Instance = this; }
|
|
}
|
|
|
|
angular.module('ng1Module', [])
|
|
.component('ng1', {
|
|
bindings: {val: '<'},
|
|
template: '',
|
|
controller: function() { this.$onChanges = $onChangesSpy; }
|
|
})
|
|
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
|
|
|
@NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
|
|
imports: [BrowserModule],
|
|
})
|
|
class Ng2Module {
|
|
}
|
|
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1Module']).ready((ref) => {
|
|
|
|
ng2Instance.val = EXPECTED_VALUE;
|
|
tick();
|
|
ref.ng1RootScope.$digest();
|
|
|
|
expect($onChangesSpy).toHaveBeenCalled();
|
|
const changes = $onChangesSpy.calls.mostRecent().args[0] as SimpleChanges;
|
|
expect(changes['val'].currentValue).toEqual(EXPECTED_VALUE);
|
|
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should call $onDestroy of components', fakeAsync(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const $onDestroySpy = jasmine.createSpy('$onDestroy');
|
|
|
|
@Component({selector: 'ng2', template: '<ng1></ng1>'})
|
|
class Ng2Component {
|
|
}
|
|
|
|
angular.module('ng1', [])
|
|
.component('ng1', {
|
|
bindings: {},
|
|
template: '<div>ng1</div>',
|
|
controller: function() { this.$onDestroy = $onDestroySpy; }
|
|
})
|
|
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
|
|
|
|
|
@NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
|
|
imports: [BrowserModule],
|
|
})
|
|
class Ng2Module {
|
|
}
|
|
|
|
|
|
const element = html(`<div ng-if="!destroy"><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
const $rootScope = ref.ng1RootScope as any;
|
|
|
|
$rootScope.destroy = false;
|
|
tick();
|
|
$rootScope.$digest();
|
|
|
|
expect($onDestroySpy).not.toHaveBeenCalled();
|
|
|
|
$rootScope.destroy = true;
|
|
tick();
|
|
$rootScope.$digest();
|
|
|
|
expect($onDestroySpy).toHaveBeenCalled();
|
|
|
|
ref.dispose();
|
|
|
|
if (!(global as any)['requestAnimationFrame']) {
|
|
// Needed for browser which don't support RAF and use a 16.6 setTimeout instead in
|
|
// ng1's AnimateRunner.
|
|
// This setTimeout remains at the end of the test and needs to be discarded.
|
|
tick(20);
|
|
}
|
|
});
|
|
}));
|
|
|
|
it('should bind input properties (<) of components', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = {
|
|
bindings: {personProfile: '<'},
|
|
template: 'Hello {{$ctrl.personProfile.firstName}} {{$ctrl.personProfile.lastName}}',
|
|
controller: Class({constructor: function() {}})
|
|
};
|
|
ng1Module.component('ng1', ng1);
|
|
|
|
const Ng2 =
|
|
Component({selector: 'ng2', template: '<ng1 [personProfile]="goku"></ng1>'}).Class({
|
|
constructor: function() { this.goku = {firstName: 'GOKU', lastName: 'SAN'}; }
|
|
});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
|
|
const element = html(`<div><ng2></ng2></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual(`Hello GOKU SAN`);
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should support ng2 > ng1 > ng2', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const ng1 = {
|
|
template: 'ng1(<ng2b></ng2b>)',
|
|
};
|
|
ng1Module.component('ng1', ng1);
|
|
|
|
const Ng2a = Component({selector: 'ng2a', template: 'ng2a(<ng1></ng1>)'}).Class({
|
|
constructor: function() {}
|
|
});
|
|
ng1Module.directive('ng2a', adapter.downgradeNg2Component(Ng2a));
|
|
|
|
const Ng2b =
|
|
Component({selector: 'ng2b', template: 'ng2b'}).Class({constructor: function() {}});
|
|
ng1Module.directive('ng2b', adapter.downgradeNg2Component(Ng2b));
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2a, Ng2b],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
const element = html(`<div><ng2a></ng2a></div>`);
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('ng2a(ng1(ng2b))');
|
|
});
|
|
}));
|
|
});
|
|
|
|
describe('injection', () => {
|
|
function SomeToken() {}
|
|
|
|
it('should export ng2 instance to ng1', async(() => {
|
|
const MyNg2Module = NgModule({
|
|
providers: [{provide: SomeToken, useValue: 'correct_value'}],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(MyNg2Module);
|
|
const module = angular.module('myExample', []);
|
|
module.factory('someToken', adapter.downgradeNg2Provider(SomeToken));
|
|
adapter.bootstrap(html('<div>'), ['myExample']).ready((ref) => {
|
|
expect(ref.ng1Injector.get('someToken')).toBe('correct_value');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should export ng1 instance to ng2', async(() => {
|
|
const MyNg2Module =
|
|
NgModule({imports: [BrowserModule]}).Class({constructor: function() {}});
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(MyNg2Module);
|
|
const module = angular.module('myExample', []);
|
|
module.value('testValue', 'secreteToken');
|
|
adapter.upgradeNg1Provider('testValue');
|
|
adapter.upgradeNg1Provider('testValue', {asToken: 'testToken'});
|
|
adapter.upgradeNg1Provider('testValue', {asToken: String});
|
|
adapter.bootstrap(html('<div>'), ['myExample']).ready((ref) => {
|
|
expect(ref.ng2Injector.get('testValue')).toBe('secreteToken');
|
|
expect(ref.ng2Injector.get(String)).toBe('secreteToken');
|
|
expect(ref.ng2Injector.get('testToken')).toBe('secreteToken');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
it('should respect hierarchical dependency injection for ng2', async(() => {
|
|
const ng1Module = angular.module('ng1', []);
|
|
|
|
const Ng2Parent = Component({
|
|
selector: 'ng2-parent',
|
|
template: `ng2-parent(<ng-content></ng-content>)`
|
|
}).Class({constructor: function() {}});
|
|
const Ng2Child = Component({selector: 'ng2-child', template: `ng2-child`}).Class({
|
|
constructor: [Ng2Parent, function(parent: any) {}]
|
|
});
|
|
|
|
const Ng2Module =
|
|
NgModule({declarations: [Ng2Parent, Ng2Child], imports: [BrowserModule]}).Class({
|
|
constructor: function() {}
|
|
});
|
|
|
|
const element = html('<ng2-parent><ng2-child></ng2-child></ng2-parent>');
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
|
ng1Module.directive('ng2Parent', adapter.downgradeNg2Component(Ng2Parent))
|
|
.directive('ng2Child', adapter.downgradeNg2Component(Ng2Child));
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(document.body.textContent).toEqual('ng2-parent(ng2-child)');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
});
|
|
|
|
describe('testability', () => {
|
|
it('should handle deferred bootstrap', async(() => {
|
|
const MyNg2Module =
|
|
NgModule({imports: [BrowserModule]}).Class({constructor: function() {}});
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(MyNg2Module);
|
|
angular.module('ng1', []);
|
|
let bootstrapResumed: boolean = false;
|
|
|
|
const element = html('<div></div>');
|
|
window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
|
|
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
expect(bootstrapResumed).toEqual(true);
|
|
ref.dispose();
|
|
});
|
|
|
|
setTimeout(() => {
|
|
bootstrapResumed = true;
|
|
(<any>window).angular.resumeBootstrap();
|
|
}, 100);
|
|
}));
|
|
|
|
it('should wait for ng2 testability', async(() => {
|
|
const MyNg2Module =
|
|
NgModule({imports: [BrowserModule]}).Class({constructor: function() {}});
|
|
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(MyNg2Module);
|
|
angular.module('ng1', []);
|
|
const element = html('<div></div>');
|
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
const ng2Testability: Testability = ref.ng2Injector.get(Testability);
|
|
ng2Testability.increasePendingRequestCount();
|
|
let ng2Stable = false;
|
|
|
|
angular.getTestability(element).whenStable(() => {
|
|
expect(ng2Stable).toEqual(true);
|
|
ref.dispose();
|
|
});
|
|
|
|
setTimeout(() => {
|
|
ng2Stable = true;
|
|
ng2Testability.decreasePendingRequestCount();
|
|
}, 100);
|
|
});
|
|
}));
|
|
});
|
|
|
|
it('should allow attribute selectors for components in ng2', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
|
|
const ng1Module = angular.module('myExample', []);
|
|
|
|
@Component({selector: '[works]', template: 'works!'})
|
|
class WorksComponent {
|
|
}
|
|
|
|
@Component({selector: 'root-component', template: 'It <div works></div>'})
|
|
class RootComponent {
|
|
}
|
|
|
|
@NgModule({imports: [BrowserModule], declarations: [RootComponent, WorksComponent]})
|
|
class MyNg2Module {
|
|
}
|
|
|
|
ng1Module.directive('rootComponent', adapter.downgradeNg2Component(RootComponent));
|
|
|
|
document.body.innerHTML = '<root-component></root-component>';
|
|
adapter.bootstrap(document.body.firstElementChild, ['myExample']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent)).toEqual('It works!');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
|
|
describe('examples', () => {
|
|
it('should verify UpgradeAdapter example', async(() => {
|
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
const module = angular.module('myExample', []);
|
|
|
|
const ng1 = () => {
|
|
return {
|
|
scope: {title: '='},
|
|
transclude: true,
|
|
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
|
};
|
|
};
|
|
module.directive('ng1', ng1);
|
|
|
|
const Ng2 =
|
|
Component({
|
|
selector: 'ng2',
|
|
inputs: ['name'],
|
|
template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)'
|
|
}).Class({constructor: function() {}});
|
|
|
|
const Ng2Module = NgModule({
|
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
|
imports: [BrowserModule],
|
|
}).Class({constructor: function() {}});
|
|
|
|
module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
|
|
document.body.innerHTML = '<ng2 name="World">project</ng2>';
|
|
|
|
adapter.bootstrap(document.body.firstElementChild, ['myExample']).ready((ref) => {
|
|
expect(multiTrim(document.body.textContent))
|
|
.toEqual('ng2[ng1[Hello World!](transclude)](project)');
|
|
ref.dispose();
|
|
});
|
|
}));
|
|
});
|
|
});
|
|
|
|
describe('sortProjectableNodes', () => {
|
|
it('should return an array of node collections for each selector', () => {
|
|
const contentNodes = nodes(
|
|
'<div class="x"><span>div-1 content</span></div>' +
|
|
'<input type="number" name="myNum">' +
|
|
'<input type="date" name="myDate">' +
|
|
'<span>span content</span>' +
|
|
'<div class="x"><span>div-2 content</span></div>');
|
|
|
|
const selectors = ['input[type=date]', 'span', '.x'];
|
|
const projectableNodes = sortProjectableNodes(selectors, contentNodes);
|
|
|
|
expect(projectableNodes[0]).toEqual(nodes('<input type="date" name="myDate">'));
|
|
expect(projectableNodes[1]).toEqual(nodes('<span>span content</span>'));
|
|
expect(projectableNodes[2])
|
|
.toEqual(nodes(
|
|
'<div class="x"><span>div-1 content</span></div>' +
|
|
'<div class="x"><span>div-2 content</span></div>'));
|
|
});
|
|
|
|
it('should collect up unmatched nodes for the wildcard selector', () => {
|
|
const contentNodes = nodes(
|
|
'<div class="x"><span>div-1 content</span></div>' +
|
|
'<input type="number" name="myNum">' +
|
|
'<input type="date" name="myDate">' +
|
|
'<span>span content</span>' +
|
|
'<div class="x"><span>div-2 content</span></div>');
|
|
|
|
const selectors = ['.x', '*', 'input[type=date]'];
|
|
const projectableNodes = sortProjectableNodes(selectors, contentNodes);
|
|
|
|
expect(projectableNodes[0])
|
|
.toEqual(nodes(
|
|
'<div class="x"><span>div-1 content</span></div>' +
|
|
'<div class="x"><span>div-2 content</span></div>'));
|
|
expect(projectableNodes[1])
|
|
.toEqual(nodes(
|
|
'<input type="number" name="myNum">' +
|
|
'<span>span content</span>'));
|
|
expect(projectableNodes[2]).toEqual(nodes('<input type="date" name="myDate">'));
|
|
});
|
|
|
|
it('should return an array of empty arrays if there are no nodes passed in', () => {
|
|
const selectors = ['.x', '*', 'input[type=date]'];
|
|
const projectableNodes = sortProjectableNodes(selectors, []);
|
|
expect(projectableNodes).toEqual([[], [], []]);
|
|
});
|
|
|
|
it('should return an empty array for each selector that does not match', () => {
|
|
const contentNodes = nodes(
|
|
'<div class="x"><span>div-1 content</span></div>' +
|
|
'<input type="number" name="myNum">' +
|
|
'<input type="date" name="myDate">' +
|
|
'<span>span content</span>' +
|
|
'<div class="x"><span>div-2 content</span></div>');
|
|
|
|
const noSelectorNodes = sortProjectableNodes([], contentNodes);
|
|
expect(noSelectorNodes).toEqual([]);
|
|
|
|
const noMatchSelectorNodes = sortProjectableNodes(['.not-there'], contentNodes);
|
|
expect(noMatchSelectorNodes).toEqual([[]]);
|
|
});
|
|
});
|
|
}
|
|
|
|
function multiTrim(text: string): string {
|
|
return text.replace(/\n/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
}
|
|
|
|
function html(html: string): Element {
|
|
const body = document.body;
|
|
body.innerHTML = html;
|
|
|
|
if (body.childNodes.length == 1 && body.firstChild instanceof HTMLElement) {
|
|
return <Element>body.firstChild;
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
function nodes(html: string) {
|
|
const element = document.createElement('div');
|
|
element.innerHTML = html;
|
|
return Array.prototype.slice.call(element.childNodes);
|
|
}
|