test(ivy): re-enable passing upgrade tests (#27736)

PR Close #27736
This commit is contained in:
Kara Erickson 2018-12-18 11:49:20 -08:00 committed by Miško Hevery
parent 1c93afe956
commit b04bc5d06c
2 changed files with 497 additions and 536 deletions

View File

@ -170,52 +170,50 @@ withEachNg1Version(() => {
}); });
describe('scope/component change-detection', () => { describe('scope/component change-detection', () => {
fixmeIvy('FW-714: ng1 projected content is not being rendered') it('should interleave scope and component expressions', async(() => {
.it('should interleave scope and component expressions', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []);
const ng1Module = angular.module('ng1', []); const log: string[] = [];
const log: string[] = []; const l = (value: string) => {
const l = (value: string) => { log.push(value);
log.push(value); return value + ';';
return value + ';'; };
};
ng1Module.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'})); ng1Module.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}));
ng1Module.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'})); ng1Module.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}));
ng1Module.run(($rootScope: any) => { ng1Module.run(($rootScope: any) => {
$rootScope.l = l; $rootScope.l = l;
$rootScope.reset = () => log.length = 0; $rootScope.reset = () => log.length = 0;
}); });
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}` template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
}) })
class Ng2 { class Ng2 {
l: any; l: any;
constructor() { this.l = l; } constructor() { this.l = l; }
} }
@NgModule({ @NgModule({
declarations: [ declarations:
adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2 [adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2],
], imports: [BrowserModule],
imports: [BrowserModule], })
}) class Ng2Module {
class Ng2Module { }
}
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
const element = const element =
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>'); html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
adapter.bootstrap(element, ['ng1']).ready((ref) => { adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;'); expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
// https://github.com/angular/angular.js/issues/12983 // https://github.com/angular/angular.js/issues/12983
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']); expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
ref.dispose(); ref.dispose();
}); });
})); }));
fixmeIvy( fixmeIvy(
@ -826,209 +824,197 @@ withEachNg1Version(() => {
}); });
describe('upgrade ng1 component', () => { describe('upgrade ng1 component', () => {
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should support `@` bindings', fakeAsync(() => {
.it('should support `@` bindings', fakeAsync(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); let ng2ComponentInstance: Ng2Component;
let ng2ComponentInstance: Ng2Component;
// Define `ng1Component` // Define `ng1Component`
const ng1Component: angular.IComponent = { const ng1Component: angular.IComponent = {
template: 'Inside: {{ $ctrl.inputA }}, {{ $ctrl.inputB }}', template: 'Inside: {{ $ctrl.inputA }}, {{ $ctrl.inputB }}',
bindings: {inputA: '@inputAttrA', inputB: '@'} bindings: {inputA: '@inputAttrA', inputB: '@'}
}; };
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
<ng1 inputAttrA="{{ dataA }}" inputB="{{ dataB }}"></ng1> <ng1 inputAttrA="{{ dataA }}" inputB="{{ dataB }}"></ng1>
| Outside: {{ dataA }}, {{ dataB }} | Outside: {{ dataA }}, {{ dataB }}
` `
}) })
class Ng2Component { class Ng2Component {
dataA = 'foo'; dataA = 'foo';
dataB = 'bar'; dataB = 'bar';
constructor() { ng2ComponentInstance = this; } constructor() { ng2ComponentInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = const ng1Module = angular.module('ng1Module', [])
angular.module('ng1Module', []) .component('ng1', ng1Component)
.component('ng1', ng1Component) .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule] imports: [BrowserModule]
}) })
class Ng2Module { class Ng2Module {
} }
// Bootstrap // Bootstrap
const element = html(`<ng2></ng2>`); const element = html(`<ng2></ng2>`);
adapter.bootstrap(element, ['ng1Module']).ready(ref => { adapter.bootstrap(element, ['ng1Module']).ready(ref => {
const ng1 = element.querySelector('ng1') !; const ng1 = element.querySelector('ng1') !;
const ng1Controller = angular.element(ng1).controller !('ng1'); const ng1Controller = angular.element(ng1).controller !('ng1');
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent)).toBe('Inside: foo, bar | Outside: foo, bar');
.toBe('Inside: foo, bar | Outside: foo, bar');
ng1Controller.inputA = 'baz'; ng1Controller.inputA = 'baz';
ng1Controller.inputB = 'qux'; ng1Controller.inputB = 'qux';
$digest(ref); $digest(ref);
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent)).toBe('Inside: baz, qux | Outside: foo, bar');
.toBe('Inside: baz, qux | Outside: foo, bar');
ng2ComponentInstance.dataA = 'foo2'; ng2ComponentInstance.dataA = 'foo2';
ng2ComponentInstance.dataB = 'bar2'; ng2ComponentInstance.dataB = 'bar2';
$digest(ref); $digest(ref);
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent))
.toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); .toBe('Inside: foo2, bar2 | Outside: foo2, bar2');
ref.dispose(); ref.dispose();
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should support `<` bindings', fakeAsync(() => {
.it('should support `<` bindings', fakeAsync(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); let ng2ComponentInstance: Ng2Component;
let ng2ComponentInstance: Ng2Component;
// Define `ng1Component` // Define `ng1Component`
const ng1Component: angular.IComponent = { const ng1Component: angular.IComponent = {
template: 'Inside: {{ $ctrl.inputA.value }}, {{ $ctrl.inputB.value }}', template: 'Inside: {{ $ctrl.inputA.value }}, {{ $ctrl.inputB.value }}',
bindings: {inputA: '<inputAttrA', inputB: '<'} bindings: {inputA: '<inputAttrA', inputB: '<'}
}; };
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
<ng1 [inputAttrA]="dataA" [inputB]="dataB"></ng1> <ng1 [inputAttrA]="dataA" [inputB]="dataB"></ng1>
| Outside: {{ dataA.value }}, {{ dataB.value }} | Outside: {{ dataA.value }}, {{ dataB.value }}
` `
}) })
class Ng2Component { class Ng2Component {
dataA = {value: 'foo'}; dataA = {value: 'foo'};
dataB = {value: 'bar'}; dataB = {value: 'bar'};
constructor() { ng2ComponentInstance = this; } constructor() { ng2ComponentInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = const ng1Module = angular.module('ng1Module', [])
angular.module('ng1Module', []) .component('ng1', ng1Component)
.component('ng1', ng1Component) .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule] imports: [BrowserModule]
}) })
class Ng2Module { class Ng2Module {
} }
// Bootstrap // Bootstrap
const element = html(`<ng2></ng2>`); const element = html(`<ng2></ng2>`);
adapter.bootstrap(element, ['ng1Module']).ready(ref => { adapter.bootstrap(element, ['ng1Module']).ready(ref => {
const ng1 = element.querySelector('ng1') !; const ng1 = element.querySelector('ng1') !;
const ng1Controller = angular.element(ng1).controller !('ng1'); const ng1Controller = angular.element(ng1).controller !('ng1');
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent)).toBe('Inside: foo, bar | Outside: foo, bar');
.toBe('Inside: foo, bar | Outside: foo, bar');
ng1Controller.inputA = {value: 'baz'}; ng1Controller.inputA = {value: 'baz'};
ng1Controller.inputB = {value: 'qux'}; ng1Controller.inputB = {value: 'qux'};
$digest(ref); $digest(ref);
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent)).toBe('Inside: baz, qux | Outside: foo, bar');
.toBe('Inside: baz, qux | Outside: foo, bar');
ng2ComponentInstance.dataA = {value: 'foo2'}; ng2ComponentInstance.dataA = {value: 'foo2'};
ng2ComponentInstance.dataB = {value: 'bar2'}; ng2ComponentInstance.dataB = {value: 'bar2'};
$digest(ref); $digest(ref);
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent))
.toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); .toBe('Inside: foo2, bar2 | Outside: foo2, bar2');
ref.dispose(); ref.dispose();
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should support `=` bindings', fakeAsync(() => {
.it('should support `=` bindings', fakeAsync(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); let ng2ComponentInstance: Ng2Component;
let ng2ComponentInstance: Ng2Component;
// Define `ng1Component` // Define `ng1Component`
const ng1Component: angular.IComponent = { const ng1Component: angular.IComponent = {
template: 'Inside: {{ $ctrl.inputA.value }}, {{ $ctrl.inputB.value }}', template: 'Inside: {{ $ctrl.inputA.value }}, {{ $ctrl.inputB.value }}',
bindings: {inputA: '=inputAttrA', inputB: '='} bindings: {inputA: '=inputAttrA', inputB: '='}
}; };
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
<ng1 [(inputAttrA)]="dataA" [(inputB)]="dataB"></ng1> <ng1 [(inputAttrA)]="dataA" [(inputB)]="dataB"></ng1>
| Outside: {{ dataA.value }}, {{ dataB.value }} | Outside: {{ dataA.value }}, {{ dataB.value }}
` `
}) })
class Ng2Component { class Ng2Component {
dataA = {value: 'foo'}; dataA = {value: 'foo'};
dataB = {value: 'bar'}; dataB = {value: 'bar'};
constructor() { ng2ComponentInstance = this; } constructor() { ng2ComponentInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = const ng1Module = angular.module('ng1Module', [])
angular.module('ng1Module', []) .component('ng1', ng1Component)
.component('ng1', ng1Component) .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component], declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule] imports: [BrowserModule]
}) })
class Ng2Module { class Ng2Module {
} }
// Bootstrap // Bootstrap
const element = html(`<ng2></ng2>`); const element = html(`<ng2></ng2>`);
adapter.bootstrap(element, ['ng1Module']).ready(ref => { adapter.bootstrap(element, ['ng1Module']).ready(ref => {
const ng1 = element.querySelector('ng1') !; const ng1 = element.querySelector('ng1') !;
const ng1Controller = angular.element(ng1).controller !('ng1'); const ng1Controller = angular.element(ng1).controller !('ng1');
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent)).toBe('Inside: foo, bar | Outside: foo, bar');
.toBe('Inside: foo, bar | Outside: foo, bar');
ng1Controller.inputA = {value: 'baz'}; ng1Controller.inputA = {value: 'baz'};
ng1Controller.inputB = {value: 'qux'}; ng1Controller.inputB = {value: 'qux'};
$digest(ref); $digest(ref);
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent)).toBe('Inside: baz, qux | Outside: baz, qux');
.toBe('Inside: baz, qux | Outside: baz, qux');
ng2ComponentInstance.dataA = {value: 'foo2'}; ng2ComponentInstance.dataA = {value: 'foo2'};
ng2ComponentInstance.dataB = {value: 'bar2'}; ng2ComponentInstance.dataB = {value: 'bar2'};
$digest(ref); $digest(ref);
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent))
.toBe('Inside: foo2, bar2 | Outside: foo2, bar2'); .toBe('Inside: foo2, bar2 | Outside: foo2, bar2');
ref.dispose(); ref.dispose();
}); });
})); }));
it('should support `&` bindings', fakeAsync(() => { it('should support `&` bindings', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
@ -1084,208 +1070,196 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should bind properties, events', async(() => {
.it('should bind properties, events', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []);
const ng1Module = angular.module('ng1', []);
const ng1 = () => { const ng1 = () => {
return { return {
template: 'Hello {{fullName}}; A: {{modelA}}; B: {{modelB}}; C: {{modelC}}; | ', template: 'Hello {{fullName}}; A: {{modelA}}; B: {{modelB}}; C: {{modelC}}; | ',
scope: { scope: {fullName: '@', modelA: '=dataA', modelB: '=dataB', modelC: '=', event: '&'},
fullName: '@', link: function(scope: any) {
modelA: '=dataA', scope.$watch('modelB', (v: string) => {
modelB: '=dataB', if (v == 'Savkin') {
modelC: '=', scope.modelB = 'SAVKIN';
event: '&' scope.event('WORKS');
},
link: function(scope: any) {
scope.$watch('modelB', (v: string) => {
if (v == 'Savkin') {
scope.modelB = 'SAVKIN';
scope.event('WORKS');
// Should not update because [model-a] is uni directional // Should not update because [model-a] is uni directional
scope.modelA = 'VICTOR'; scope.modelA = 'VICTOR';
} }
}); });
} }
}; };
}; };
ng1Module.directive('ng1', ng1); ng1Module.directive('ng1', ng1);
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: template:
'<ng1 fullName="{{last}}, {{first}}, {{city}}" [dataA]="first" [(dataB)]="last" [modelC]="city" ' + '<ng1 fullName="{{last}}, {{first}}, {{city}}" [dataA]="first" [(dataB)]="last" [modelC]="city" ' +
'(event)="event=$event"></ng1>' + '(event)="event=$event"></ng1>' +
'<ng1 fullName="{{\'TEST\'}}" dataA="First" dataB="Last" modelC="City"></ng1>' + '<ng1 fullName="{{\'TEST\'}}" dataA="First" dataB="Last" modelC="City"></ng1>' +
'{{event}}-{{last}}, {{first}}, {{city}}' '{{event}}-{{last}}, {{first}}, {{city}}'
}) })
class Ng2 { class Ng2 {
first = 'Victor'; first = 'Victor';
last = 'Savkin'; last = 'Savkin';
city = 'SF'; city = 'SF';
event = '?'; event = '?';
} }
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2], declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
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) => {
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
// events, and so without this we would not see the events processed. // events, and so without this we would not see the events processed.
setTimeout(() => { setTimeout(() => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual( .toEqual(
'Hello SAVKIN, Victor, SF; A: VICTOR; B: SAVKIN; C: SF; | Hello TEST; A: First; B: Last; C: City; | WORKS-SAVKIN, Victor, SF'); 'Hello SAVKIN, Victor, SF; A: VICTOR; B: SAVKIN; C: SF; | Hello TEST; A: First; B: Last; C: City; | WORKS-SAVKIN, Victor, SF');
ref.dispose(); ref.dispose();
}, 0); }, 0);
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should bind optional properties', async(() => {
.it('should bind optional properties', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []);
const ng1Module = angular.module('ng1', []);
const ng1 = () => { const ng1 = () => {
return { return {
template: 'Hello; A: {{modelA}}; B: {{modelB}}; | ', template: 'Hello; A: {{modelA}}; B: {{modelB}}; | ',
scope: {modelA: '=?dataA', modelB: '=?'} scope: {modelA: '=?dataA', modelB: '=?'}
}; };
}; };
ng1Module.directive('ng1', ng1); ng1Module.directive('ng1', ng1);
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: '<ng1 [dataA]="first" [modelB]="last"></ng1>' + template: '<ng1 [dataA]="first" [modelB]="last"></ng1>' +
'<ng1 dataA="First" modelB="Last"></ng1>' + '<ng1 dataA="First" modelB="Last"></ng1>' +
'<ng1></ng1>' + '<ng1></ng1>' +
'<ng1></ng1>' '<ng1></ng1>'
}) })
class Ng2 { class Ng2 {
first = 'Victor'; first = 'Victor';
last = 'Savkin'; last = 'Savkin';
} }
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2], declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
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) => {
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
// events, and so without this we would not see the events processed. // events, and so without this we would not see the events processed.
setTimeout(() => { setTimeout(() => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual( .toEqual(
'Hello; A: Victor; B: Savkin; | Hello; A: First; B: Last; | Hello; A: ; B: ; | Hello; A: ; B: ; |'); 'Hello; A: Victor; B: Savkin; | Hello; A: First; B: Last; | Hello; A: ; B: ; | Hello; A: ; B: ; |');
ref.dispose(); ref.dispose();
}, 0); }, 0);
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should bind properties, events in controller when bindToController is not used',
.it('should bind properties, events in controller when bindToController is not used', async(() => {
async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []);
const ng1Module = angular.module('ng1', []);
const ng1 = () => { const ng1 = () => {
return { return {
restrict: 'E', restrict: 'E',
template: '{{someText}} - Length: {{data.length}}', template: '{{someText}} - Length: {{data.length}}',
scope: {data: '='}, scope: {data: '='},
controller: function($scope: any) { controller: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; }
$scope.someText = 'ng1 - Data: ' + $scope.data; };
} };
};
};
ng1Module.directive('ng1', ng1); ng1Module.directive('ng1', ng1);
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: template:
'{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>' '{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>'
}) })
class Ng2 { class Ng2 {
dataList = [1, 2, 3]; dataList = [1, 2, 3];
someText = 'ng2'; someText = 'ng2';
} }
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2], declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
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) => {
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
// events, and so without this we would not see the events processed. // events, and so without this we would not see the events processed.
setTimeout(() => { setTimeout(() => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3'); .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3');
ref.dispose(); ref.dispose();
}, 0); }, 0);
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should bind properties, events in link function', async(() => {
.it('should bind properties, events in link function', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []);
const ng1Module = angular.module('ng1', []);
const ng1 = () => { const ng1 = () => {
return { return {
restrict: 'E', restrict: 'E',
template: '{{someText}} - Length: {{data.length}}', template: '{{someText}} - Length: {{data.length}}',
scope: {data: '='}, scope: {data: '='},
link: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; } link: function($scope: any) { $scope.someText = 'ng1 - Data: ' + $scope.data; }
}; };
}; };
ng1Module.directive('ng1', ng1); ng1Module.directive('ng1', ng1);
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: template:
'{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>' '{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>'
}) })
class Ng2 { class Ng2 {
dataList = [1, 2, 3]; dataList = [1, 2, 3];
someText = 'ng2'; someText = 'ng2';
} }
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2], declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
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) => {
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
// events, and so without this we would not see the events processed. // events, and so without this we would not see the events processed.
setTimeout(() => { setTimeout(() => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3'); .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3');
ref.dispose(); ref.dispose();
}, 0); }, 0);
}); });
})); }));
it('should support templateUrl fetched from $httpBackend', async(() => { it('should support templateUrl fetched from $httpBackend', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
@ -1468,75 +1442,73 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should support bindToController', async(() => {
.it('should support bindToController', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []);
const ng1Module = angular.module('ng1', []);
const ng1 = () => { const ng1 = () => {
return { return {
scope: {title: '@'}, scope: {title: '@'},
bindToController: true, bindToController: true,
template: '{{ctl.title}}', template: '{{ctl.title}}',
controllerAs: 'ctl', controllerAs: 'ctl',
controller: class {} controller: class {}
}; };
}; };
ng1Module.directive('ng1', ng1); ng1Module.directive('ng1', ng1);
@Component({selector: 'ng2', template: '<ng1 title="WORKS"></ng1>'}) @Component({selector: 'ng2', template: '<ng1 title="WORKS"></ng1>'})
class Ng2 { class Ng2 {
} }
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2], declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
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(multiTrim(document.body.textContent)).toEqual('WORKS'); expect(multiTrim(document.body.textContent)).toEqual('WORKS');
ref.dispose(); ref.dispose();
}); });
})); }));
fixmeIvy('FW-724: upgraded ng1 components are not being rendered') it('should support bindToController with bindings', async(() => {
.it('should support bindToController with bindings', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []);
const ng1Module = angular.module('ng1', []);
const ng1 = () => { const ng1 = () => {
return { return {
scope: {}, scope: {},
bindToController: {title: '@'}, bindToController: {title: '@'},
template: '{{ctl.title}}', template: '{{ctl.title}}',
controllerAs: 'ctl', controllerAs: 'ctl',
controller: class {} controller: class {}
}; };
}; };
ng1Module.directive('ng1', ng1); ng1Module.directive('ng1', ng1);
@Component({selector: 'ng2', template: '<ng1 title="WORKS"></ng1>'}) @Component({selector: 'ng2', template: '<ng1 title="WORKS"></ng1>'})
class Ng2 { class Ng2 {
} }
@NgModule({ @NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2], declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
} }
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
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(multiTrim(document.body.textContent)).toEqual('WORKS'); expect(multiTrim(document.body.textContent)).toEqual('WORKS');
ref.dispose(); ref.dispose();
}); });
})); }));
it('should support single require in linking fn', async(() => { it('should support single require in linking fn', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));

View File

@ -2134,8 +2134,7 @@ withEachNg1Version(() => {
}); });
describe('transclusion', () => { describe('transclusion', () => {
fixmeIvy( fixmeIvy(`FW-863: Error: Failed to execute 'insertBefore' on 'Node'`)
`Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`)
.it('should support single-slot transclusion', async(() => { .it('should support single-slot transclusion', async(() => {
let ng2ComponentAInstance: Ng2ComponentA; let ng2ComponentAInstance: Ng2ComponentA;
let ng2ComponentBInstance: Ng2ComponentB; let ng2ComponentBInstance: Ng2ComponentB;
@ -2531,8 +2530,7 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy( fixmeIvy(`FW-863: Error: Failed to execute 'insertBefore' on 'Node'`)
`Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`)
.it('should support structural directives in transcluded content', async(() => { .it('should support structural directives in transcluded content', async(() => {
let ng2ComponentInstance: Ng2Component; let ng2ComponentInstance: Ng2Component;
@ -3336,106 +3334,98 @@ withEachNg1Version(() => {
})); }));
fixmeIvy( it('should call `$onDestroy()` on controller', async(() => {
'FW-843: destroy hooks are not registered on upgraded ng1 components contained in ng2 component templates under ivy') const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA');
.it('should call `$onDestroy()` on controller', async(() => { const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB');
const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA');
const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB');
// Define `ng1Directive` // Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = { const ng1DirectiveA: angular.IDirective = {
template: 'ng1A', template: 'ng1A',
scope: {}, scope: {},
bindToController: false, bindToController: false,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: class {$onDestroy() { controllerOnDestroyA(); }} controller: class {$onDestroy() { controllerOnDestroyA(); }}
}; };
const ng1DirectiveB: angular.IDirective = { const ng1DirectiveB: angular.IDirective = {
template: 'ng1B', template: 'ng1B',
scope: {}, scope: {},
bindToController: true, bindToController: true,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: class { controller:
constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; } class {constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; }}
} };
};
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1A'}) @Directive({selector: 'ng1A'})
class Ng1ComponentAFacade extends UpgradeComponent { class Ng1ComponentAFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1A', elementRef, injector); super('ng1A', elementRef, injector);
} }
} }
@Directive({selector: 'ng1B'}) @Directive({selector: 'ng1B'})
class Ng1ComponentBFacade extends UpgradeComponent { class Ng1ComponentBFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1B', elementRef, injector); super('ng1B', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component(
selector: 'ng2', {selector: 'ng2', template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>'})
template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>' class Ng2Component {
}) // TODO(issue/24571): remove '!'.
class Ng2Component { @Input() show !: boolean;
// TODO(issue/24571): remove '!'. }
@Input() show !: boolean;
}
// Define `ng1Module` // Define `ng1Module`
const ng1Module = const ng1Module = angular.module('ng1Module', [])
angular.module('ng1Module', []) .directive('ng1A', () => ng1DirectiveA)
.directive('ng1A', () => ng1DirectiveA) .directive('ng1B', () => ng1DirectiveB)
.directive('ng1B', () => ng1DirectiveB) .directive('ng2', downgradeComponent({component: Ng2Component}));
.directive('ng2', downgradeComponent({component: Ng2Component}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component], declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = const element = html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>');
html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
const $rootScope = const $rootScope = adapter.$injector.get('$rootScope') as angular.IRootScopeService;
adapter.$injector.get('$rootScope') as angular.IRootScopeService;
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
expect(controllerOnDestroyA).not.toHaveBeenCalled(); expect(controllerOnDestroyA).not.toHaveBeenCalled();
expect(controllerOnDestroyB).not.toHaveBeenCalled(); expect(controllerOnDestroyB).not.toHaveBeenCalled();
$rootScope.$apply('destroyFromNg1 = true'); $rootScope.$apply('destroyFromNg1 = true');
expect(multiTrim(document.body.textContent)).toBe(''); expect(multiTrim(document.body.textContent)).toBe('');
expect(controllerOnDestroyA).toHaveBeenCalled(); expect(controllerOnDestroyA).toHaveBeenCalled();
expect(controllerOnDestroyB).toHaveBeenCalled(); expect(controllerOnDestroyB).toHaveBeenCalled();
controllerOnDestroyA.calls.reset(); controllerOnDestroyA.calls.reset();
controllerOnDestroyB.calls.reset(); controllerOnDestroyB.calls.reset();
$rootScope.$apply('destroyFromNg1 = false'); $rootScope.$apply('destroyFromNg1 = false');
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
expect(controllerOnDestroyA).not.toHaveBeenCalled(); expect(controllerOnDestroyA).not.toHaveBeenCalled();
expect(controllerOnDestroyB).not.toHaveBeenCalled(); expect(controllerOnDestroyB).not.toHaveBeenCalled();
$rootScope.$apply('destroyFromNg2 = true'); $rootScope.$apply('destroyFromNg2 = true');
expect(multiTrim(document.body.textContent)).toBe(''); expect(multiTrim(document.body.textContent)).toBe('');
expect(controllerOnDestroyA).toHaveBeenCalled(); expect(controllerOnDestroyA).toHaveBeenCalled();
expect(controllerOnDestroyB).toHaveBeenCalled(); expect(controllerOnDestroyB).toHaveBeenCalled();
}); });
})); }));
it('should not call `$onDestroy()` on scope', async(() => { it('should not call `$onDestroy()` on scope', async(() => {
const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy'); const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy');
@ -3968,7 +3958,6 @@ withEachNg1Version(() => {
}); });
})); }));
// fixmeIvy('FW-724: upgraded ng1 components are not being rendered')
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;