diff --git a/karma-base.conf.js b/karma-base.conf.js index b733dbe4f..26ffa771a 100644 --- a/karma-base.conf.js +++ b/karma-base.conf.js @@ -16,6 +16,7 @@ module.exports = function (config) { {pattern: 'node_modules/rxjs/**/**/*.js', included: false, watched: false }, {pattern: 'node_modules/rxjs/**/**/*.js.map', included: false, watched: false }, {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, + {pattern: 'node_modules/es6-promise/**/*.js', included: false, watched: false }, {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, {pattern: 'test/assets/**/*.*', watched: true, served: true, included: false}, {pattern: 'build/**/*.js.map', watched: true, served: true, included: false}, diff --git a/karma-dist-sauce-jasmine.conf.js b/karma-dist-sauce-jasmine.conf.js index 401ac3b71..44f6be74a 100644 --- a/karma-dist-sauce-jasmine.conf.js +++ b/karma-dist-sauce-jasmine.conf.js @@ -8,5 +8,5 @@ module.exports = function (config) { require('./karma-dist-jasmine.conf.js')(config); - require('./sauce.conf')(config); + require('./sauce.conf')(config, ['SL_IOS9']); }; diff --git a/lib/common/promise.ts b/lib/common/promise.ts index 6dcb4e76b..57671f2d8 100644 --- a/lib/common/promise.ts +++ b/lib/common/promise.ts @@ -330,6 +330,44 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr ZoneAwarePromise['all'] = ZoneAwarePromise.all; const NativePromise = global[symbolPromise] = global['Promise']; + const ZONE_AWARE_PROMISE = Zone.__symbol__('ZoneAwarePromise'); + + let desc = Object.getOwnPropertyDescriptor(global, 'Promise'); + if (!desc || desc.configurable) { + desc && delete desc.writable; + desc && delete desc.value; + if (!desc) { + desc = {configurable: true, enumerable: true}; + } + desc.get = function() { + // if we already set ZoneAwarePromise, use patched one + // otherwise return native one. + return global[ZONE_AWARE_PROMISE] ? global[ZONE_AWARE_PROMISE] : global[symbolPromise]; + }; + desc.set = function(NewNativePromise) { + if (NewNativePromise === ZoneAwarePromise) { + // if the NewNativePromise is ZoneAwarePromise + // save to global + global[ZONE_AWARE_PROMISE] = NewNativePromise; + } else { + // if the NewNativePromise is not ZoneAwarePromise + // for example: after load zone.js, some library just + // set es6-promise to global, if we set it to global + // directly, assertZonePatched will fail and angular + // will not loaded, so we just set the NewNativePromise + // to global[symbolPromise], so the result is just like + // we load ES6 Promise before zone.js + global[symbolPromise] = NewNativePromise; + if (!NewNativePromise.prototype[symbolThen]) { + patchThen(NewNativePromise); + } + api.setNativePromise(NewNativePromise); + } + }; + + Object.defineProperty(global, 'Promise', desc); + } + global['Promise'] = ZoneAwarePromise; const symbolThenPatched = __symbol__('thenPatched'); diff --git a/lib/zone.ts b/lib/zone.ts index 2e4288a99..d873bb965 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -322,6 +322,7 @@ interface _ZonePrivate { showUncaughtError: () => boolean; patchEventTarget: (global: any, apis: any[], options?: any) => boolean[]; patchOnProperties: (obj: any, properties: string[]) => void; + setNativePromise: (nativePromise: any) => void; patchMethod: (target: any, name: string, patchFn: (delegate: Function, delegateName: string, name: string) => @@ -1318,6 +1319,9 @@ const Zone: ZoneType = (function(global: any) { patchEventTarget: () => [], patchOnProperties: noop, patchMethod: () => noop, + setNativePromise: (NativePromise: any) => { + nativeMicroTaskQueuePromise = NativePromise.resolve(0); + }, }; let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task = null; diff --git a/sauce.conf.js b/sauce.conf.js index d1c0393b0..a6026ad50 100644 --- a/sauce.conf.js +++ b/sauce.conf.js @@ -1,10 +1,10 @@ // Sauce configuration -module.exports = function (config) { +module.exports = function (config, ignoredLaunchers) { // The WS server is not available with Sauce config.files.unshift('test/saucelabs.js'); - var customLaunchers = { + var basicLaunchers = { 'SL_CHROME': { base: 'SauceLabs', browserName: 'chrome', @@ -153,6 +153,17 @@ module.exports = function (config) { } }; + var customLaunchers = {}; + if (!ignoredLaunchers) { + customLaunchers = basicLaunchers; + } else { + Object.keys(basicLaunchers).forEach(function(key) { + if (ignoredLaunchers.filter(function(ignore) {return ignore === key;}).length === 0) { + customLaunchers[key] = basicLaunchers[key]; + } + }); + } + config.set({ captureTimeout: 120000, browserNoActivityTimeout: 240000, diff --git a/test/common/Promise.spec.ts b/test/common/Promise.spec.ts index f8f0e8e1b..00c4db71f 100644 --- a/test/common/Promise.spec.ts +++ b/test/common/Promise.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {isNode} from '../../lib/common/utils'; +import {isNode, zoneSymbol} from '../../lib/common/utils'; import {ifEnvSupports} from '../test-util'; declare const global: any; @@ -58,6 +58,31 @@ describe( log = []; }); + xit('should allow set es6 Promise after load ZoneAwarePromise', (done) => { + const ES6Promise = require('es6-promise').Promise; + const NativePromise = global[zoneSymbol('Promise')]; + + try { + global['Promise'] = ES6Promise; + Zone.assertZonePatched(); + expect(global[zoneSymbol('Promise')]).toBe(ES6Promise); + const promise = Promise.resolve(0); + console.log('promise', promise); + promise + .then(value => { + expect(value).toBe(0); + done(); + }) + .catch(error => { + fail(error); + }); + } finally { + global['Promise'] = NativePromise; + Zone.assertZonePatched(); + expect(global[zoneSymbol('Promise')]).toBe(NativePromise); + } + }); + it('should pretend to be a native code', () => { expect(String(Promise).indexOf('[native code]') >= 0).toBe(true); }); diff --git a/test/common/zone.spec.ts b/test/common/zone.spec.ts index 95cd0219a..f969df913 100644 --- a/test/common/zone.spec.ts +++ b/test/common/zone.spec.ts @@ -5,6 +5,7 @@ * 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 {zoneSymbol} from '../../lib/common/utils'; describe('Zone', function() { const rootZone = Zone.current; @@ -326,17 +327,23 @@ describe('Zone', function() { Zone.assertZonePatched(); }); - it('should throw when Promise has been patched', () => { - class WrongPromise {} + it('should keep ZoneAwarePromise has been patched', () => { + class WrongPromise { + static resolve(value: any) {} + + then() {} + } const ZoneAwarePromise = global.Promise; + const NativePromise = (global as any)[zoneSymbol('Promise')]; global.Promise = WrongPromise; try { expect(ZoneAwarePromise).toBeTruthy(); - expect(() => Zone.assertZonePatched()).toThrow(); + Zone.assertZonePatched(); + expect(global.Promise).toBe(ZoneAwarePromise); } finally { // restore it. - global.Promise = ZoneAwarePromise; + global.Promise = NativePromise; } Zone.assertZonePatched(); }); diff --git a/test/main.ts b/test/main.ts index 117e274e6..f8c33f3ea 100644 --- a/test/main.ts +++ b/test/main.ts @@ -16,7 +16,10 @@ __karma__.loaded = function() {}; (window as any).global = window; System.config({ defaultJSExtensions: true, - map: {'rxjs': 'base/node_modules/rxjs'}, + map: { + 'rxjs': 'base/node_modules/rxjs', + 'es6-promise': 'base/node_modules/es6-promise/dist/es6-promise' + }, }); let browserPatchedPromise: any = null; @@ -43,4 +46,4 @@ browserPatchedPromise.then(() => { console.error(error.stack || error); }); }); -}); \ No newline at end of file +}); diff --git a/test/rxjs/rxjs.Observable.combine.spec.ts b/test/rxjs/rxjs.Observable.combine.spec.ts index 2196369e1..c77f0596e 100644 --- a/test/rxjs/rxjs.Observable.combine.spec.ts +++ b/test/rxjs/rxjs.Observable.combine.spec.ts @@ -51,13 +51,11 @@ describe('Observable.combine', () => { const constructorZone1: Zone = Zone.current.fork({name: 'Constructor Zone1'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); observable1 = constructorZone1.run(() => { - const source = Rx.Observable.interval(10); - const highOrder = source - .map((src: any) => { - expect(Zone.current.name).toEqual(constructorZone1.name); - return Rx.Observable.interval(50).take(3); - }) - .take(2); + const source = Rx.Observable.of(1, 2, 3); + const highOrder = source.map((src: any) => { + expect(Zone.current.name).toEqual(constructorZone1.name); + return Rx.Observable.of(src); + }); return highOrder.combineAll((x: any, y: any) => { expect(Zone.current.name).toEqual(constructorZone1.name); return {x: x, y: y}; @@ -76,14 +74,10 @@ describe('Observable.combine', () => { () => { log.push('completed'); expect(Zone.current.name).toEqual(subscriptionZone.name); - expect(log).toEqual([ - {x: 0, y: 0}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 2, y: 1}, {x: 2, y: 2}, 'completed' - ]); + expect(log).toEqual([{x: 1, y: 2}, 'completed']); done(); }); }); - - expect(log).toEqual([]); }, Zone.root)); it('combineLatest func callback should run in the correct zone', () => { @@ -141,4 +135,4 @@ describe('Observable.combine', () => { expect(log).toEqual([7, 8, 9, 'completed']); }); -}); \ No newline at end of file +});