From 430f367c2fd26eef95b718f35fa9c85cece0c890 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Mon, 14 Mar 2016 14:36:41 -0700 Subject: [PATCH] fix(upgrade): make ngUpgrade work with testability API Closes #7603 --- modules/angular2/src/upgrade/angular_js.ts | 31 ++++++-- modules/angular2/src/upgrade/constants.ts | 1 + .../angular2/src/upgrade/upgrade_adapter.ts | 73 ++++++++++++++++--- modules/angular2/test/upgrade/upgrade_spec.ts | 58 ++++++++++++++- 4 files changed, 143 insertions(+), 20 deletions(-) diff --git a/modules/angular2/src/upgrade/angular_js.ts b/modules/angular2/src/upgrade/angular_js.ts index 817815094f..bdd75dbb66 100644 --- a/modules/angular2/src/upgrade/angular_js.ts +++ b/modules/angular2/src/upgrade/angular_js.ts @@ -97,16 +97,33 @@ export interface IControllerService { export interface IInjectorService { get(key: string): any; } +export interface ITestabilityService { + findBindings(element: Element, expression: string, opt_exactMatch?: boolean): Element[]; + findModels(element: Element, expression: string, opt_exactMatch?: boolean): Element[]; + getLocation(): string; + setLocation(url: string): void; + whenStable(callback: Function): void; +} + function noNg() { throw new Error('AngularJS v1.x is not loaded!'); } -var angular: { - bootstrap: (e: Element, modules: string[], config: IAngularBootstrapConfig) => void, - module: (prefix: string, dependencies?: string[]) => IModule, - element: (e: Element) => IAugmentedJQuery, - version: {major: number} -} = {bootstrap: noNg, module: noNg, element: noNg, version: noNg}; +var angular: + { + bootstrap: (e: Element, modules: string[], config: IAngularBootstrapConfig) => void, + module: (prefix: string, dependencies?: string[]) => IModule, + element: (e: Element) => IAugmentedJQuery, + version: {major: number}, resumeBootstrap?: () => void, + getTestability: (e: Element) => ITestabilityService + } = { + bootstrap: noNg, + module: noNg, + element: noNg, + version: noNg, + resumeBootstrap: noNg, + getTestability: noNg + }; try { @@ -121,3 +138,5 @@ export var bootstrap = angular.bootstrap; export var module = angular.module; export var element = angular.element; export var version = angular.version; +export var resumeBootstrap = angular.resumeBootstrap; +export var getTestability = angular.getTestability; diff --git a/modules/angular2/src/upgrade/constants.ts b/modules/angular2/src/upgrade/constants.ts index 5934bea4e5..93c16d690f 100644 --- a/modules/angular2/src/upgrade/constants.ts +++ b/modules/angular2/src/upgrade/constants.ts @@ -12,4 +12,5 @@ export const NG1_HTTP_BACKEND = '$httpBackend'; export const NG1_INJECTOR = '$injector'; export const NG1_PARSE = '$parse'; export const NG1_TEMPLATE_CACHE = '$templateCache'; +export const NG1_TESTABILITY = '$$testability'; export const REQUIRE_INJECTOR = '^' + NG2_INJECTOR; diff --git a/modules/angular2/src/upgrade/upgrade_adapter.ts b/modules/angular2/src/upgrade/upgrade_adapter.ts index be3074ff0c..e78bf69cc6 100644 --- a/modules/angular2/src/upgrade/upgrade_adapter.ts +++ b/modules/angular2/src/upgrade/upgrade_adapter.ts @@ -10,8 +10,10 @@ import { HostViewFactoryRef, Provider, Type, + Testability, APPLICATION_COMMON_PROVIDERS } from 'angular2/core'; +import {global} from 'angular2/src/facade/lang'; import {ObservableWrapper} from 'angular2/src/facade/async'; import {BROWSER_PROVIDERS, BROWSER_APP_PROVIDERS} from 'angular2/platform/browser'; @@ -23,6 +25,7 @@ import { NG1_PARSE, NG1_ROOT_SCOPE, NG1_SCOPE, + NG1_TESTABILITY, NG2_APP_VIEW_MANAGER, NG2_COMPILER, NG2_INJECTOR, @@ -309,6 +312,7 @@ export class UpgradeAdapter { var rootScope: angular.IRootScopeService; var hostViewFactoryRefMap: HostViewFactoryRefMap = {}; var ng1Module = angular.module(this.idPrefix, modules); + var ng1BootstrapPromise: Promise = null; var ng1compilePromise: Promise = null; ng1Module.value(NG2_INJECTOR, injector) .value(NG2_ZONE, ngZone) @@ -331,23 +335,68 @@ export class UpgradeAdapter { return rootScope = rootScopeDelegate; } ]); - } - ]) - .run([ - '$injector', - '$rootScope', - (injector: angular.IInjectorService, rootScope: angular.IRootScopeService) => { - ng1Injector = injector; - ObservableWrapper.subscribe(ngZone.onMicrotaskEmpty, - (_) => ngZone.runOutsideAngular(() => rootScope.$apply())); - ng1compilePromise = - UpgradeNg1ComponentAdapterBuilder.resolve(this.downgradedComponents, injector); + provide.decorator(NG1_TESTABILITY, [ + '$delegate', + function(testabilityDelegate: angular.ITestabilityService) { + var ng2Testability: Testability = injector.get(Testability); + + var origonalWhenStable: Function = testabilityDelegate.whenStable; + var newWhenStable = (callback: Function): void => { + var whenStableContext: any = this; + origonalWhenStable.call(this, function() { + if (ng2Testability.isStable()) { + callback.apply(this, arguments); + } else { + ng2Testability.whenStable(newWhenStable.bind(whenStableContext, callback)); + } + }); + }; + + testabilityDelegate.whenStable = newWhenStable; + return testabilityDelegate; + } + ]); } ]); + ng1compilePromise = new Promise((resolve, reject) => { + ng1Module.run([ + '$injector', + '$rootScope', + (injector: angular.IInjectorService, rootScope: angular.IRootScopeService) => { + ng1Injector = injector; + ObservableWrapper.subscribe(ngZone.onMicrotaskEmpty, + (_) => ngZone.runOutsideAngular(() => rootScope.$apply())); + UpgradeNg1ComponentAdapterBuilder.resolve(this.downgradedComponents, injector) + .then(resolve, reject); + } + ]); + }); + + // Make sure resumeBootstrap() only exists if the current bootstrap is deferred + var windowAngular = (global).angular; + windowAngular.resumeBootstrap = undefined; + angular.element(element).data(controllerKey(NG2_INJECTOR), injector); ngZone.run(() => { angular.bootstrap(element, [this.idPrefix], config); }); - Promise.all([this.compileNg2Components(compiler, hostViewFactoryRefMap), ng1compilePromise]) + ng1BootstrapPromise = new Promise((resolve, reject) => { + if (windowAngular.resumeBootstrap) { + var originalResumeBootstrap: () => void = windowAngular.resumeBootstrap; + windowAngular.resumeBootstrap = function() { + windowAngular.resumeBootstrap = originalResumeBootstrap; + windowAngular.resumeBootstrap.apply(this, arguments); + resolve(); + }; + } else { + resolve(); + } + }); + + Promise.all([ + this.compileNg2Components(compiler, hostViewFactoryRefMap), + ng1BootstrapPromise, + ng1compilePromise + ]) .then(() => { ngZone.run(() => { if (rootScopePrototype) { diff --git a/modules/angular2/test/upgrade/upgrade_spec.ts b/modules/angular2/test/upgrade/upgrade_spec.ts index aca7157361..53b085c69b 100644 --- a/modules/angular2/test/upgrade/upgrade_spec.ts +++ b/modules/angular2/test/upgrade/upgrade_spec.ts @@ -12,7 +12,16 @@ import { } from 'angular2/testing_internal'; import {DOM} from 'angular2/src/platform/dom/dom_adapter'; -import {Component, Class, Inject, EventEmitter, ApplicationRef, provide} from 'angular2/core'; +import {global} from 'angular2/src/facade/lang'; +import { + Component, + Class, + Inject, + EventEmitter, + ApplicationRef, + provide, + Testability, +} from 'angular2/core'; import {UpgradeAdapter} from 'angular2/upgrade'; import * as angular from 'angular2/src/upgrade/angular_js'; @@ -559,6 +568,52 @@ export function main() { })); }); + describe('testability', () => { + it('should handle deferred bootstrap', inject([AsyncTestCompleter], (async) => { + var adapter: UpgradeAdapter = new UpgradeAdapter(); + var ng1Module = angular.module('ng1', []); + var bootstrapResumed: boolean = false; + + var element = html("
"); + window.name = 'NG_DEFER_BOOTSTRAP!' + window.name; + + adapter.bootstrap(element, ['ng1']) + .ready((ref) => { + expect(bootstrapResumed).toEqual(true); + ref.dispose(); + async.done(); + }); + + setTimeout(() => { + bootstrapResumed = true; + (global).angular.resumeBootstrap(); + }, 100); + })); + + it('should wait for ng2 testability', inject([AsyncTestCompleter], (async) => { + var adapter: UpgradeAdapter = new UpgradeAdapter(); + var ng1Module = angular.module('ng1', []); + var element = html("
"); + adapter.bootstrap(element, ['ng1']) + .ready((ref) => { + var ng2Testability: Testability = ref.ng2Injector.get(Testability); + ng2Testability.increasePendingRequestCount(); + var ng2Stable = false; + + angular.getTestability(element).whenStable(function() { + expect(ng2Stable).toEqual(true); + ref.dispose(); + async.done(); + }); + + setTimeout(() => { + ng2Stable = true; + ng2Testability.decreasePendingRequestCount(); + }, 100); + }); + })); + }); + describe('examples', () => { it('should verify UpgradeAdapter example', inject([AsyncTestCompleter], (async) => { var adapter = new UpgradeAdapter(); @@ -594,7 +649,6 @@ export function main() { }); })); }); - }); }