From d37e3320256cb803f3851157f82b7b47fbcf726e Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 16 Jul 2017 04:21:19 +0900 Subject: [PATCH 01/17] feat(rxjs): fix #830, monkey patch rxjs to make rxjs run in correct zone --- karma-base.conf.js | 1 + lib/node/rollup-main.ts | 3 +- lib/rxjs/rxjs.ts | 110 +++++++++++++++++++++++++++ package.json | 1 + test/browser-zone-setup.ts | 3 +- test/browser_entry_point.ts | 3 +- test/common_tests.ts | 1 + test/node_entry_point.ts | 1 + test/rxjs/rxjs.spec.ts | 145 ++++++++++++++++++++++++++++++++++++ 9 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 lib/rxjs/rxjs.ts create mode 100644 test/rxjs/rxjs.spec.ts diff --git a/karma-base.conf.js b/karma-base.conf.js index d4dc0f165..8a64ac605 100644 --- a/karma-base.conf.js +++ b/karma-base.conf.js @@ -13,6 +13,7 @@ module.exports = function (config) { 'node_modules/systemjs/dist/system-polyfills.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/whatwg-fetch/fetch.js', + {pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true}, {pattern: 'test/assets/**/*.*', watched: true, served: true, included: false}, {pattern: 'build/**/*.js.map', watched: true, served: true, included: false}, {pattern: 'build/**/*.js', watched: true, served: true, included: false} diff --git a/lib/node/rollup-main.ts b/lib/node/rollup-main.ts index 136714b1e..4566fde47 100644 --- a/lib/node/rollup-main.ts +++ b/lib/node/rollup-main.ts @@ -9,4 +9,5 @@ import '../zone'; import '../common/promise'; import '../common/to-string'; -import './node'; \ No newline at end of file +import './node'; +import '../rxjs/rxjs'; \ No newline at end of file diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts new file mode 100644 index 000000000..1a6a8d1a7 --- /dev/null +++ b/lib/rxjs/rxjs.ts @@ -0,0 +1,110 @@ +/** + * @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 + */ +Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { + let rxjs; + + const subscribeSource = 'rxjs.subscribe'; + const nextSource = 'rxjs.Subscriber.next'; + const errorSource = 'rxjs.Subscriber.error'; + const completeSource = 'rxjs.Subscriber.complete'; + const unsubscribeSource = 'rxjs.Subscriber.unsubscribe'; + + try { + rxjs = require('rxjs'); + } catch (error) { + return; + } + + const Observable = rxjs.Observable; + + rxjs.Observable = function () { + Observable.apply(this, arguments); + this._zone = Zone.current; + + const _subscribe = this._subscribe; + this._subscribe = function () { + const currentZone = Zone.current; + const observableZone = this._zone; + let sub; + if (observableZone && observableZone !== currentZone) { + sub = observableZone.run(_subscribe, this, arguments); + if (sub) { + sub._zone = observableZone; + } + } else { + sub = _subscribe.apply(this, arguments); + if (sub) { + sub._zone = currentZone; + } + } + return sub; + }; + return this; + }; + + rxjs.Observable.prototype = Observable.prototype; + const subscribe = Observable.prototype.subscribe; + Observable.prototype.subscribe = function() { + const sub = subscribe.apply(this, arguments); + if (sub) { + sub._zone = Zone.current; + } + return sub; + } + + const Subscriber = rxjs.Subscriber; + + const next = Subscriber.prototype.next; + const error = Subscriber.prototype.error; + const complete = Subscriber.prototype.complete; + const unsubscribe = Subscriber.prototype.unsubscribe; + + Subscriber.prototype.next = function () { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(next, this, arguments, nextSource); + } else { + return next.apply(this, arguments); + } + } + + Subscriber.prototype.error = function () { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(error, this, arguments, errorSource); + } else { + return error.apply(this, arguments); + } + } + + Subscriber.prototype.complete = function () { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(complete, this, arguments, completeSource); + } else { + return complete.apply(this, arguments); + } + } + + Subscriber.prototype.unsubscribe = function () { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(unsubscribe, this, arguments, unsubscribeSource); + } else { + return unsubscribe.apply(this, arguments); + } + } +}); \ No newline at end of file diff --git a/package.json b/package.json index c375d452b..8e3cd15f6 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "phantomjs": "^2.1.7", "promises-aplus-tests": "^2.1.2", "pump": "^1.0.1", + "rxjs": "^5.4.2", "selenium-webdriver": "^3.4.0", "systemjs": "^0.19.37", "ts-loader": "^0.6.0", diff --git a/test/browser-zone-setup.ts b/test/browser-zone-setup.ts index 1c16e96a8..57c641595 100644 --- a/test/browser-zone-setup.ts +++ b/test/browser-zone-setup.ts @@ -17,4 +17,5 @@ import '../lib/zone-spec/proxy'; import '../lib/zone-spec/sync-test'; import '../lib/zone-spec/task-tracking'; import '../lib/zone-spec/wtf'; -import '../lib/extra/cordova'; \ No newline at end of file +import '../lib/extra/cordova'; +import '../lib/rxjs/rxjs'; \ No newline at end of file diff --git a/test/browser_entry_point.ts b/test/browser_entry_point.ts index 25efb0b0f..d9f602e24 100644 --- a/test/browser_entry_point.ts +++ b/test/browser_entry_point.ts @@ -23,4 +23,5 @@ import './browser/MediaQuery.spec'; import './browser/Notification.spec'; import './mocha-patch.spec'; import './jasmine-patch.spec'; -import './extra/cordova.spec'; \ No newline at end of file +import './extra/cordova.spec'; +import './rxjs/rxjs.spec'; \ No newline at end of file diff --git a/test/common_tests.ts b/test/common_tests.ts index bd684d273..186274955 100644 --- a/test/common_tests.ts +++ b/test/common_tests.ts @@ -21,5 +21,6 @@ import './zone-spec/sync-test.spec'; import './zone-spec/fake-async-test.spec'; import './zone-spec/proxy.spec'; import './zone-spec/task-tracking.spec'; +import './rxjs/rxjs.spec'; Error.stackTraceLimit = Number.POSITIVE_INFINITY; \ No newline at end of file diff --git a/test/node_entry_point.ts b/test/node_entry_point.ts index 0c54d2511..1a926e3c5 100644 --- a/test/node_entry_point.ts +++ b/test/node_entry_point.ts @@ -22,6 +22,7 @@ import '../lib/zone-spec/proxy'; import '../lib/zone-spec/sync-test'; import '../lib/zone-spec/task-tracking'; import '../lib/zone-spec/wtf'; +import '../lib/rxjs/rxjs'; // Setup test environment import './test-env-setup-jasmine'; diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts new file mode 100644 index 000000000..ce2ff9a25 --- /dev/null +++ b/test/rxjs/rxjs.spec.ts @@ -0,0 +1,145 @@ +/** + * @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 {Subscriber, Observable} from 'rxjs'; + +/** + * The point of these tests, is to ensure that all callbacks execute in the Zone which was active + * when the callback was passed into the Rx. + * + * The implications are: + * - Observable callback passed into `Observable` executes in the same Zone as when the + * `new Observable` was invoked. + * - The subscription callbacks passed into `subscribe` execute in the same Zone as when the + * `subscribe` method was invoked. + * - The operator callbacks passe into `map`, etc..., execute in the same Zone as when the + * `operator` (`lift`) method was invoked. + */ +describe('Zone interaction', () => { + it('should run methods in the zone of declaration', () => { + const log: string[] = []; + const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.current.fork({ name: 'Subscription Zone'}); + let subscriber: Subscriber = null; + const observable = constructorZone.run(() => new Observable((_subscriber) => { + subscriber = _subscriber; + log.push('setup'); + expect(Zone.current.name).toEqual(constructorZone.name); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })) as Observable; + subscriptionZone.run(() => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + () => null, + () => { + (process as any)._rawDebug('complete callback'); + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + } + )); + subscriber.next('MyValue'); + subscriber.complete(); + + expect(log).toEqual(['setup', 'next', 'complete', 'cleanup']); + log.length = 0; + + subscriptionZone.run(() => observable.subscribe( + () => null, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('error'); + }, + () => null + )); + subscriber.next('MyValue'); + subscriber.error('MyError'); + + expect(log).toEqual(['setup', 'error', 'cleanup']); + }); + + xit('should run methods in the zone of declaration when nexting synchronously', () => { + const log: string[] = []; + const rootZone: Zone = Zone.current; + const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.current.fork({ name: 'Subscription Zone'}); + const observable = constructorZone.run(() => new Observable((subscriber) => { + // Execute the `next`/`complete` in different zone, and assert that correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })) as Observable; + + subscriptionZone.run(() => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + () => null, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + } + )); + + expect(log).toEqual(['next', 'complete', 'cleanup']); + }); + + xit('should run operators in the zone of declaration', () => { + const log: string[] = []; + const rootZone: Zone = Zone.current; + const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); + const operatorZone: Zone = Zone.current.fork({ name: 'Operator Zone'}); + const subscriptionZone: Zone = Zone.current.fork({ name: 'Subscription Zone'}); + let observable = constructorZone.run(() => new Observable((subscriber) => { + // Execute the `next`/`complete` in different zone, and assert that correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })) as Observable; + + observable = operatorZone.run(() => observable.map((value) => { + expect(Zone.current.name).toEqual(operatorZone.name); + log.push('map: ' + value); + return value; + })) as Observable; + + subscriptionZone.run(() => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + (e) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('error: ' + e); + }, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + } + )); + + expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); + }); + +}); \ No newline at end of file From 0afdb9699ab48dd482a574b7797f5cd654d8f56b Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 16 Jul 2017 12:51:40 +0900 Subject: [PATCH 02/17] make test pass --- lib/rxjs/rxjs.ts | 91 +++++++++++++++++++++++++++++++----------- test/rxjs/rxjs.spec.ts | 4 +- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index 1a6a8d1a7..cb0d9802a 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -25,36 +25,65 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { rxjs.Observable = function () { Observable.apply(this, arguments); this._zone = Zone.current; - - const _subscribe = this._subscribe; - this._subscribe = function () { - const currentZone = Zone.current; - const observableZone = this._zone; - let sub; - if (observableZone && observableZone !== currentZone) { - sub = observableZone.run(_subscribe, this, arguments); - if (sub) { - sub._zone = observableZone; - } - } else { - sub = _subscribe.apply(this, arguments); - if (sub) { - sub._zone = currentZone; - } - } - return sub; - }; return this; }; rxjs.Observable.prototype = Observable.prototype; + const subscribe = Observable.prototype.subscribe; + const lift = Observable.prototype.lift; + Observable.prototype.subscribe = function() { - const sub = subscribe.apply(this, arguments); - if (sub) { - sub._zone = Zone.current; + const _zone = this._zone; + const currentZone = Zone.current; + + if (this._subscribe && typeof this._subscribe === 'function') { + this._subscribe._zone = this._zone; + const _subscribe = this._subscribe; + if (_zone) { + this._subscribe = function() { + const subscriber = arguments.length > 0 ? arguments[0] : undefined; + if (subscriber && !subscriber._zone) { + subscriber._zone = currentZone; + } + const tearDownLogic = _zone !== Zone.current ? _zone.run(_subscribe, this, arguments) : _subscribe.apply(this, arguments); + if (tearDownLogic && typeof tearDownLogic === 'function') { + const patchedTeadDownLogic = function() { + if (_zone && _zone !== Zone.current) { + return _zone.run(tearDownLogic, this, arguments); + } else { + return tearDownLogic.apply(this, arguments); + } + } + return patchedTeadDownLogic; + } + return tearDownLogic; + } + } + } + + if (this.operator && _zone && _zone !== currentZone) { + const call = this.operator.call; + this.operator.call = function() { + const subscriber = arguments.length > 0 ? arguments[0] : undefined; + if (!subscriber._zone) { + subscriber._zone = currentZone; + } + return _zone.run(call, this, arguments); + } } - return sub; + const result = subscribe.apply(this, arguments); + if (this._subscribe) { + this._subscribe._zone = undefined; + } + result._zone = Zone.current; + return result; + }; + + Observable.prototype.lift = function() { + const observable = lift.apply(this, arguments); + observable._zone = Zone.current; + return observable; } const Subscriber = rxjs.Subscriber; @@ -107,4 +136,20 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { return unsubscribe.apply(this, arguments); } } + + /*const Subscription = rxjs.Subscription; + const add = Subscription.prototype.add; + + Subscription.prototype.add = function() { + const tearDownLogic = arguments.length > 0 ? arguments[0] : undefined; + if (!tearDownLogic) { + return add(this, arguments); + } + const zone = tearDownLogic._zone; + if (zone && zone !== Zone.current) { + return zone.run(add, this, arguments); + } else { + return add.apply(this, arguments); + } + }*/ }); \ No newline at end of file diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index ce2ff9a25..113a8b4c4 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -66,7 +66,7 @@ describe('Zone interaction', () => { expect(log).toEqual(['setup', 'error', 'cleanup']); }); - xit('should run methods in the zone of declaration when nexting synchronously', () => { + it('should run methods in the zone of declaration when nexting synchronously', () => { const log: string[] = []; const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); @@ -99,7 +99,7 @@ describe('Zone interaction', () => { expect(log).toEqual(['next', 'complete', 'cleanup']); }); - xit('should run operators in the zone of declaration', () => { + it('should run operators in the zone of declaration', () => { const log: string[] = []; const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); From 9b651e1144e28388f4aab97343c04c985a31f16d Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 16 Jul 2017 14:07:07 +0900 Subject: [PATCH 03/17] add karma config --- karma-base.conf.js | 4 +- lib/rxjs/rxjs.ts | 49 ++++-------- test/main.ts | 2 +- test/rxjs/rxjs.spec.ts | 172 ++++++++++++++++++++--------------------- 4 files changed, 106 insertions(+), 121 deletions(-) diff --git a/karma-base.conf.js b/karma-base.conf.js index 8a64ac605..e29b4b13f 100644 --- a/karma-base.conf.js +++ b/karma-base.conf.js @@ -13,7 +13,7 @@ module.exports = function (config) { 'node_modules/systemjs/dist/system-polyfills.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/whatwg-fetch/fetch.js', - {pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true}, + 'node_modules/rxjs/bundles/Rx.js', {pattern: 'test/assets/**/*.*', watched: true, served: true, included: false}, {pattern: 'build/**/*.js.map', watched: true, served: true, included: false}, {pattern: 'build/**/*.js', watched: true, served: true, included: false} @@ -25,7 +25,7 @@ module.exports = function (config) { require('karma-sourcemap-loader') ], - preprocessors: { + preprocessors: { '**/*.js': ['sourcemap'] }, diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index cb0d9802a..de346e536 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -22,7 +22,7 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const Observable = rxjs.Observable; - rxjs.Observable = function () { + rxjs.Observable = function() { Observable.apply(this, arguments); this._zone = Zone.current; return this; @@ -46,7 +46,8 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { if (subscriber && !subscriber._zone) { subscriber._zone = currentZone; } - const tearDownLogic = _zone !== Zone.current ? _zone.run(_subscribe, this, arguments) : _subscribe.apply(this, arguments); + const tearDownLogic = _zone !== Zone.current ? _zone.run(_subscribe, this, arguments) : + _subscribe.apply(this, arguments); if (tearDownLogic && typeof tearDownLogic === 'function') { const patchedTeadDownLogic = function() { if (_zone && _zone !== Zone.current) { @@ -54,13 +55,13 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } else { return tearDownLogic.apply(this, arguments); } - } + }; return patchedTeadDownLogic; } return tearDownLogic; - } + }; } - } + } if (this.operator && _zone && _zone !== currentZone) { const call = this.operator.call; @@ -70,12 +71,12 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { subscriber._zone = currentZone; } return _zone.run(call, this, arguments); - } + }; } const result = subscribe.apply(this, arguments); if (this._subscribe) { this._subscribe._zone = undefined; - } + } result._zone = Zone.current; return result; }; @@ -84,7 +85,7 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const observable = lift.apply(this, arguments); observable._zone = Zone.current; return observable; - } + }; const Subscriber = rxjs.Subscriber; @@ -93,7 +94,7 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const complete = Subscriber.prototype.complete; const unsubscribe = Subscriber.prototype.unsubscribe; - Subscriber.prototype.next = function () { + Subscriber.prototype.next = function() { const currentZone = Zone.current; const observableZone = this._zone; @@ -102,9 +103,9 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } else { return next.apply(this, arguments); } - } + }; - Subscriber.prototype.error = function () { + Subscriber.prototype.error = function() { const currentZone = Zone.current; const observableZone = this._zone; @@ -113,9 +114,9 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } else { return error.apply(this, arguments); } - } + }; - Subscriber.prototype.complete = function () { + Subscriber.prototype.complete = function() { const currentZone = Zone.current; const observableZone = this._zone; @@ -124,9 +125,9 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } else { return complete.apply(this, arguments); } - } + }; - Subscriber.prototype.unsubscribe = function () { + Subscriber.prototype.unsubscribe = function() { const currentZone = Zone.current; const observableZone = this._zone; @@ -135,21 +136,5 @@ Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } else { return unsubscribe.apply(this, arguments); } - } - - /*const Subscription = rxjs.Subscription; - const add = Subscription.prototype.add; - - Subscription.prototype.add = function() { - const tearDownLogic = arguments.length > 0 ? arguments[0] : undefined; - if (!tearDownLogic) { - return add(this, arguments); - } - const zone = tearDownLogic._zone; - if (zone && zone !== Zone.current) { - return zone.run(add, this, arguments); - } else { - return add.apply(this, arguments); - } - }*/ + }; }); \ No newline at end of file diff --git a/test/main.ts b/test/main.ts index aa4f8bd2b..f41b45bba 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,7 +15,7 @@ declare const __karma__: { __karma__.loaded = function() {}; (window as any).global = window; -System.config({defaultJSExtensions: true}); +System.config({defaultJSExtensions: true, map: {'rxjs': 'base/node_modules/rxjs/bundles/Rx.js'}}); let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { browserPatchedPromise = Promise.resolve('browserPatched'); diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 113a8b4c4..71a40b0dc 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -5,7 +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 {Subscriber, Observable} from 'rxjs'; +import {Observable, Subscriber} from 'rxjs'; /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active @@ -22,44 +22,40 @@ import {Subscriber, Observable} from 'rxjs'; describe('Zone interaction', () => { it('should run methods in the zone of declaration', () => { const log: string[] = []; - const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); - const subscriptionZone: Zone = Zone.current.fork({ name: 'Subscription Zone'}); + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); let subscriber: Subscriber = null; - const observable = constructorZone.run(() => new Observable((_subscriber) => { - subscriber = _subscriber; - log.push('setup'); - expect(Zone.current.name).toEqual(constructorZone.name); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })) as Observable; - subscriptionZone.run(() => observable.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next'); - }, - () => null, - () => { - (process as any)._rawDebug('complete callback'); - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('complete'); - } - )); + const observable = + constructorZone.run(() => new Observable((_subscriber) => { + subscriber = _subscriber; + log.push('setup'); + expect(Zone.current.name).toEqual(constructorZone.name); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })) as Observable; + subscriptionZone.run( + () => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + () => null, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + })); subscriber.next('MyValue'); subscriber.complete(); expect(log).toEqual(['setup', 'next', 'complete', 'cleanup']); log.length = 0; - subscriptionZone.run(() => observable.subscribe( - () => null, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('error'); - }, - () => null - )); + subscriptionZone.run(() => observable.subscribe(() => null, () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('error'); + }, () => null)); subscriber.next('MyValue'); subscriber.error('MyError'); @@ -69,32 +65,34 @@ describe('Zone interaction', () => { it('should run methods in the zone of declaration when nexting synchronously', () => { const log: string[] = []; const rootZone: Zone = Zone.current; - const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); - const subscriptionZone: Zone = Zone.current.fork({ name: 'Subscription Zone'}); - const observable = constructorZone.run(() => new Observable((subscriber) => { - // Execute the `next`/`complete` in different zone, and assert that correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })) as Observable; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + const observable = + constructorZone.run(() => new Observable((subscriber) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })) as Observable; - subscriptionZone.run(() => observable.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next'); - }, - () => null, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('complete'); - } - )); + subscriptionZone.run( + () => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + () => null, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + })); expect(log).toEqual(['next', 'complete', 'cleanup']); }); @@ -102,21 +100,23 @@ describe('Zone interaction', () => { it('should run operators in the zone of declaration', () => { const log: string[] = []; const rootZone: Zone = Zone.current; - const constructorZone: Zone = Zone.current.fork({ name: 'Constructor Zone'}); - const operatorZone: Zone = Zone.current.fork({ name: 'Operator Zone'}); - const subscriptionZone: Zone = Zone.current.fork({ name: 'Subscription Zone'}); - let observable = constructorZone.run(() => new Observable((subscriber) => { - // Execute the `next`/`complete` in different zone, and assert that correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })) as Observable; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + let observable = + constructorZone.run(() => new Observable((subscriber) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })) as Observable; observable = operatorZone.run(() => observable.map((value) => { expect(Zone.current.name).toEqual(operatorZone.name); @@ -124,20 +124,20 @@ describe('Zone interaction', () => { return value; })) as Observable; - subscriptionZone.run(() => observable.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next'); - }, - (e) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('error: ' + e); - }, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('complete'); - } - )); + subscriptionZone.run( + () => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + (e) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('error: ' + e); + }, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + })); expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); }); From 093ffb7053ec43feace71a69ed608a9655792ab4 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 16 Jul 2017 15:02:03 +0900 Subject: [PATCH 04/17] pass compile-esm --- gulpfile.js | 12 +++++++++++- lib/node/rollup-main.ts | 3 +-- test/rxjs/rxjs.spec.ts | 39 +++++++++++++++++++++------------------ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 6d77c543f..a1c1c7f57 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -199,6 +199,14 @@ gulp.task('build/sync-test.js', ['compile-esm'], function(cb) { return generateScript('./lib/zone-spec/sync-test.ts', 'sync-test.js', false, cb); }); +gulp.task('build/rxjs.js', ['compile-esm'], function(cb) { + return generateScript('./lib/rxjs/rxjs.ts', 'zone-rxjs.js', false, cb); +}); + +gulp.task('build/rxjs.min.js', ['compile-esm'], function(cb) { + return generateScript('./lib/rxjs/rxjs.ts', 'zone-rxjs.min.js', true, cb); +}); + gulp.task('build', [ 'build/zone.js', 'build/zone.js.d.ts', @@ -231,7 +239,9 @@ gulp.task('build', [ 'build/wtf.min.js', 'build/async-test.js', 'build/fake-async-test.js', - 'build/sync-test.js' + 'build/sync-test.js', + 'build/rxjs.js', + 'build/rxjs.min.js' ]); gulp.task('test/node', ['compile'], function(cb) { diff --git a/lib/node/rollup-main.ts b/lib/node/rollup-main.ts index 4566fde47..136714b1e 100644 --- a/lib/node/rollup-main.ts +++ b/lib/node/rollup-main.ts @@ -9,5 +9,4 @@ import '../zone'; import '../common/promise'; import '../common/to-string'; -import './node'; -import '../rxjs/rxjs'; \ No newline at end of file +import './node'; \ No newline at end of file diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 71a40b0dc..08c1b0fe9 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -5,7 +5,10 @@ * 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 {Observable, Subscriber} from 'rxjs'; + +const rxjs = require('rxjs'); +const Observable = rxjs.Observable; +const Subscriber = rxjs.Subscriber; /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active @@ -24,9 +27,9 @@ describe('Zone interaction', () => { const log: string[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let subscriber: Subscriber = null; - const observable = - constructorZone.run(() => new Observable((_subscriber) => { + let subscriber: any = null; + const observable: any = + constructorZone.run(() => new Observable((_subscriber: any) => { subscriber = _subscriber; log.push('setup'); expect(Zone.current.name).toEqual(constructorZone.name); @@ -34,14 +37,14 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); log.push('cleanup'); }; - })) as Observable; + })); subscriptionZone.run( () => observable.subscribe( () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('next'); }, - () => null, + (): any => null, () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('complete'); @@ -52,10 +55,10 @@ describe('Zone interaction', () => { expect(log).toEqual(['setup', 'next', 'complete', 'cleanup']); log.length = 0; - subscriptionZone.run(() => observable.subscribe(() => null, () => { + subscriptionZone.run(() => observable.subscribe((): any => null, () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('error'); - }, () => null)); + }, (): any => null)); subscriber.next('MyValue'); subscriber.error('MyError'); @@ -67,8 +70,8 @@ describe('Zone interaction', () => { const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - const observable = - constructorZone.run(() => new Observable((subscriber) => { + const observable: any = + constructorZone.run(() => new Observable((subscriber: any) => { // Execute the `next`/`complete` in different zone, and assert that // correct zone // is restored. @@ -80,7 +83,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); log.push('cleanup'); }; - })) as Observable; + })); subscriptionZone.run( () => observable.subscribe( @@ -88,7 +91,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('next'); }, - () => null, + (): any => null, () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('complete'); @@ -103,8 +106,8 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let observable = - constructorZone.run(() => new Observable((subscriber) => { + let observable: any = + constructorZone.run(() => new Observable((subscriber: any) => { // Execute the `next`/`complete` in different zone, and assert that // correct zone // is restored. @@ -116,13 +119,13 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); log.push('cleanup'); }; - })) as Observable; + })); - observable = operatorZone.run(() => observable.map((value) => { + observable = operatorZone.run(() => observable.map((value: any) => { expect(Zone.current.name).toEqual(operatorZone.name); log.push('map: ' + value); return value; - })) as Observable; + })); subscriptionZone.run( () => observable.subscribe( @@ -130,7 +133,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('next'); }, - (e) => { + (e: any) => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('error: ' + e); }, From b129e41880776c85123a3f5e762282e234d05836 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 19 Jul 2017 01:57:44 +0900 Subject: [PATCH 05/17] change rxjs to umd --- gulpfile.js | 4 +- lib/rxjs/rxjs.ts | 255 +++++++++++++++++++++-------------------- test/main.ts | 2 +- test/rxjs/rxjs.spec.ts | 2 +- 4 files changed, 136 insertions(+), 127 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index a1c1c7f57..881abac07 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -200,11 +200,11 @@ gulp.task('build/sync-test.js', ['compile-esm'], function(cb) { }); gulp.task('build/rxjs.js', ['compile-esm'], function(cb) { - return generateScript('./lib/rxjs/rxjs.ts', 'zone-rxjs.js', false, cb); + return generateScript('./lib/rxjs/rxjs.ts', 'zone-patch-rxjs.js', false, cb); }); gulp.task('build/rxjs.min.js', ['compile-esm'], function(cb) { - return generateScript('./lib/rxjs/rxjs.ts', 'zone-rxjs.min.js', true, cb); + return generateScript('./lib/rxjs/rxjs.ts', 'zone-patch-rxjs.min.js', true, cb); }); gulp.task('build', [ diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index de346e536..b5cb5c280 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -5,136 +5,145 @@ * 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 */ -Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { - let rxjs; - - const subscribeSource = 'rxjs.subscribe'; - const nextSource = 'rxjs.Subscriber.next'; - const errorSource = 'rxjs.Subscriber.error'; - const completeSource = 'rxjs.Subscriber.complete'; - const unsubscribeSource = 'rxjs.Subscriber.unsubscribe'; - - try { - rxjs = require('rxjs'); - } catch (error) { - return; - } - - const Observable = rxjs.Observable; - rxjs.Observable = function() { - Observable.apply(this, arguments); - this._zone = Zone.current; - return this; - }; +declare let define: any; +(function(root: any, factory: (Rx: any) => any) { + if (typeof define === 'function' && define.amd) { + // AMD + define(['Rx'], factory); + } else if (typeof exports === 'object') { + // Node, CommonJS-like + module.exports = factory(require('rxjs')); + } else { + root.returnExports = factory(root.Rx); + } +}(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global, + (Rx: any) => { + Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { + const subscribeSource = 'rxjs.subscribe'; + const nextSource = 'rxjs.Subscriber.next'; + const errorSource = 'rxjs.Subscriber.error'; + const completeSource = 'rxjs.Subscriber.complete'; + const unsubscribeSource = 'rxjs.Subscriber.unsubscribe'; + + const Observable = Rx.Observable; + + Rx.Observable = function() { + Observable.apply(this, arguments); + this._zone = Zone.current; + return this; + }; - rxjs.Observable.prototype = Observable.prototype; + Rx.Observable.prototype = Observable.prototype; - const subscribe = Observable.prototype.subscribe; - const lift = Observable.prototype.lift; + const subscribe = Observable.prototype.subscribe; + const lift = Observable.prototype.lift; - Observable.prototype.subscribe = function() { - const _zone = this._zone; - const currentZone = Zone.current; + Observable.prototype.subscribe = function() { + const _zone = this._zone; + const currentZone = Zone.current; - if (this._subscribe && typeof this._subscribe === 'function') { - this._subscribe._zone = this._zone; - const _subscribe = this._subscribe; - if (_zone) { - this._subscribe = function() { - const subscriber = arguments.length > 0 ? arguments[0] : undefined; - if (subscriber && !subscriber._zone) { - subscriber._zone = currentZone; - } - const tearDownLogic = _zone !== Zone.current ? _zone.run(_subscribe, this, arguments) : - _subscribe.apply(this, arguments); - if (tearDownLogic && typeof tearDownLogic === 'function') { - const patchedTeadDownLogic = function() { - if (_zone && _zone !== Zone.current) { - return _zone.run(tearDownLogic, this, arguments); - } else { - return tearDownLogic.apply(this, arguments); + if (this._subscribe && typeof this._subscribe === 'function') { + this._subscribe._zone = this._zone; + const _subscribe = this._subscribe; + if (_zone) { + this._subscribe = function() { + const args = Array.prototype.slice.call(arguments); + const subscriber = args.length > 0 ? args[0] : undefined; + if (subscriber && !subscriber._zone) { + subscriber._zone = currentZone; + } + const tearDownLogic = _zone !== Zone.current ? _zone.run(_subscribe, this, args) : + _subscribe.apply(this, args); + if (tearDownLogic && typeof tearDownLogic === 'function') { + const patchedTeadDownLogic = function() { + if (_zone && _zone !== Zone.current) { + return _zone.run(tearDownLogic, this, arguments); + } else { + return tearDownLogic.apply(this, arguments); + } + }; + return patchedTeadDownLogic; } + return tearDownLogic; }; - return patchedTeadDownLogic; } - return tearDownLogic; - }; - } - } - - if (this.operator && _zone && _zone !== currentZone) { - const call = this.operator.call; - this.operator.call = function() { - const subscriber = arguments.length > 0 ? arguments[0] : undefined; - if (!subscriber._zone) { - subscriber._zone = currentZone; } - return _zone.run(call, this, arguments); + + if (this.operator && _zone && _zone !== currentZone) { + const call = this.operator.call; + this.operator.call = function() { + const args = Array.prototype.slice.call(arguments); + const subscriber = args.length > 0 ? args[0] : undefined; + if (!subscriber._zone) { + subscriber._zone = currentZone; + } + return _zone.run(call, this, args); + }; + } + const result = subscribe.apply(this, arguments); + if (this._subscribe) { + this._subscribe._zone = undefined; + } + result._zone = Zone.current; + return result; + }; + + Observable.prototype.lift = function() { + const observable = lift.apply(this, arguments); + observable._zone = Zone.current; + return observable; + }; + + const Subscriber = Rx.Subscriber; + + const next = Subscriber.prototype.next; + const error = Subscriber.prototype.error; + const complete = Subscriber.prototype.complete; + const unsubscribe = Subscriber.prototype.unsubscribe; + + Subscriber.prototype.next = function() { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(next, this, arguments, nextSource); + } else { + return next.apply(this, arguments); + } + }; + + Subscriber.prototype.error = function() { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(error, this, arguments, errorSource); + } else { + return error.apply(this, arguments); + } + }; + + Subscriber.prototype.complete = function() { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(complete, this, arguments, completeSource); + } else { + return complete.apply(this, arguments); + } + }; + + Subscriber.prototype.unsubscribe = function() { + const currentZone = Zone.current; + const observableZone = this._zone; + + if (observableZone && observableZone !== currentZone) { + return observableZone.run(unsubscribe, this, arguments, unsubscribeSource); + } else { + return unsubscribe.apply(this, arguments); + } }; - } - const result = subscribe.apply(this, arguments); - if (this._subscribe) { - this._subscribe._zone = undefined; - } - result._zone = Zone.current; - return result; - }; - - Observable.prototype.lift = function() { - const observable = lift.apply(this, arguments); - observable._zone = Zone.current; - return observable; - }; - - const Subscriber = rxjs.Subscriber; - - const next = Subscriber.prototype.next; - const error = Subscriber.prototype.error; - const complete = Subscriber.prototype.complete; - const unsubscribe = Subscriber.prototype.unsubscribe; - - Subscriber.prototype.next = function() { - const currentZone = Zone.current; - const observableZone = this._zone; - - if (observableZone && observableZone !== currentZone) { - return observableZone.run(next, this, arguments, nextSource); - } else { - return next.apply(this, arguments); - } - }; - - Subscriber.prototype.error = function() { - const currentZone = Zone.current; - const observableZone = this._zone; - - if (observableZone && observableZone !== currentZone) { - return observableZone.run(error, this, arguments, errorSource); - } else { - return error.apply(this, arguments); - } - }; - - Subscriber.prototype.complete = function() { - const currentZone = Zone.current; - const observableZone = this._zone; - - if (observableZone && observableZone !== currentZone) { - return observableZone.run(complete, this, arguments, completeSource); - } else { - return complete.apply(this, arguments); - } - }; - - Subscriber.prototype.unsubscribe = function() { - const currentZone = Zone.current; - const observableZone = this._zone; - - if (observableZone && observableZone !== currentZone) { - return observableZone.run(unsubscribe, this, arguments, unsubscribeSource); - } else { - return unsubscribe.apply(this, arguments); - } - }; -}); \ No newline at end of file + }); + })); \ No newline at end of file diff --git a/test/main.ts b/test/main.ts index f41b45bba..b6d02d39a 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,7 +15,7 @@ declare const __karma__: { __karma__.loaded = function() {}; (window as any).global = window; -System.config({defaultJSExtensions: true, map: {'rxjs': 'base/node_modules/rxjs/bundles/Rx.js'}}); +System.config({defaultJSExtensions: true, map: {'Rx': 'base/node_modules/rxjs/bundles/Rx.js'}}); let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { browserPatchedPromise = Promise.resolve('browserPatched'); diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 08c1b0fe9..67ad2d2e3 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -const rxjs = require('rxjs'); +const rxjs = require('Rx'); const Observable = rxjs.Observable; const Subscriber = rxjs.Subscriber; From 2fc5d46507c2a566787f2923ac56b91f70409661 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 19 Jul 2017 02:21:22 +0900 Subject: [PATCH 06/17] change spec to pass test --- lib/rxjs/rxjs.ts | 10 +++++----- test/main.ts | 2 +- test/rxjs/rxjs.spec.ts | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index b5cb5c280..2fe07e50d 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -8,14 +8,14 @@ declare let define: any; (function(root: any, factory: (Rx: any) => any) { - if (typeof define === 'function' && define.amd) { - // AMD - define(['Rx'], factory); - } else if (typeof exports === 'object') { + if (typeof exports === 'object' && typeof module !== 'undefined') { // Node, CommonJS-like module.exports = factory(require('rxjs')); + } else if (typeof define === 'function' && define.amd) { + // AMD + define(['rxjs'], factory); } else { - root.returnExports = factory(root.Rx); + factory(root.Rx); } }(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global, (Rx: any) => { diff --git a/test/main.ts b/test/main.ts index b6d02d39a..f41b45bba 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,7 +15,7 @@ declare const __karma__: { __karma__.loaded = function() {}; (window as any).global = window; -System.config({defaultJSExtensions: true, map: {'Rx': 'base/node_modules/rxjs/bundles/Rx.js'}}); +System.config({defaultJSExtensions: true, map: {'rxjs': 'base/node_modules/rxjs/bundles/Rx.js'}}); let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { browserPatchedPromise = Promise.resolve('browserPatched'); diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 67ad2d2e3..5f38778e9 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -const rxjs = require('Rx'); +let rxjs; +if (typeof exports === 'object') { + rxjs = require('rxjs'); +} else { + rxjs = (window as any).Rx; +} const Observable = rxjs.Observable; const Subscriber = rxjs.Subscriber; From da92051c4e6efab4f2f4849667f51f227e8a865f Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 19 Jul 2017 08:33:20 +0900 Subject: [PATCH 07/17] add comment, patch Observable.create --- lib/rxjs/rxjs.ts | 73 +++++++++++++++++++++++++++++++++--------- test/rxjs/rxjs.spec.ts | 45 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 15 deletions(-) diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index 2fe07e50d..cd74bf402 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -28,6 +28,8 @@ declare let define: any; const Observable = Rx.Observable; + // monkey-patch Observable to save the + // current zone as ConstructorZone Rx.Observable = function() { Observable.apply(this, arguments); this._zone = Zone.current; @@ -38,11 +40,19 @@ declare let define: any; const subscribe = Observable.prototype.subscribe; const lift = Observable.prototype.lift; + const create = Observable.create; + // patch Observable.prototype.subscribe + // if SubscripitionZone is different with ConstructorZone + // we should run _subscribe in ConstructorZone and + // create sinke in SubscriptionZone, + // and tearDown should also run into ConstructorZone Observable.prototype.subscribe = function() { const _zone = this._zone; const currentZone = Zone.current; + // patch inner function this._subscribe to check + // SubscriptionZone is same with ConstuctorZone or not if (this._subscribe && typeof this._subscribe === 'function') { this._subscribe._zone = this._zone; const _subscribe = this._subscribe; @@ -50,26 +60,36 @@ declare let define: any; this._subscribe = function() { const args = Array.prototype.slice.call(arguments); const subscriber = args.length > 0 ? args[0] : undefined; + // also keep currentZone in Subscriber + // for later Subscriber.next/error/complete method if (subscriber && !subscriber._zone) { subscriber._zone = currentZone; } + // _subscribe should run in ConstructorZone + // but for performance concern, we should check + // whether ConsturctorZone === Zone.current here const tearDownLogic = _zone !== Zone.current ? _zone.run(_subscribe, this, args) : _subscribe.apply(this, args); if (tearDownLogic && typeof tearDownLogic === 'function') { - const patchedTeadDownLogic = function() { + const patchedTearDownLogic = function() { + // tearDownLogic should also run in ConstructorZone + // but for performance concern, we should check + // whether ConsturctorZone === Zone.current here if (_zone && _zone !== Zone.current) { return _zone.run(tearDownLogic, this, arguments); } else { return tearDownLogic.apply(this, arguments); } }; - return patchedTeadDownLogic; + return patchedTearDownLogic; } return tearDownLogic; }; } } + // if operator is involved, we should also + // patch the call method to save the Subscription zone if (this.operator && _zone && _zone !== currentZone) { const call = this.operator.call; this.operator.call = function() { @@ -82,19 +102,32 @@ declare let define: any; }; } const result = subscribe.apply(this, arguments); + // clean up _subscribe._zone to prevent + // the same _subscribe being used in multiple + // Observable instances. if (this._subscribe) { this._subscribe._zone = undefined; } - result._zone = Zone.current; + // the result is the subscriber sink, + // we save the current Zone here + result._zone = currentZone; return result; }; + // patch lift method to save ConstructorZone of Observable Observable.prototype.lift = function() { const observable = lift.apply(this, arguments); observable._zone = Zone.current; return observable; }; + // patch create method to save ConstructorZone of Observable + Rx.Observable.create = function() { + const observable = create.apply(this, arguments); + observable._zone = Zone.current; + return observable; + }; + const Subscriber = Rx.Subscriber; const next = Subscriber.prototype.next; @@ -102,12 +135,16 @@ declare let define: any; const complete = Subscriber.prototype.complete; const unsubscribe = Subscriber.prototype.unsubscribe; + // patch Subscriber.next to make sure it run + // into SubscriptionZone Subscriber.prototype.next = function() { const currentZone = Zone.current; - const observableZone = this._zone; + const subscriptionZone = this._zone; - if (observableZone && observableZone !== currentZone) { - return observableZone.run(next, this, arguments, nextSource); + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(next, this, arguments, nextSource); } else { return next.apply(this, arguments); } @@ -115,10 +152,12 @@ declare let define: any; Subscriber.prototype.error = function() { const currentZone = Zone.current; - const observableZone = this._zone; + const subscriptionZone = this._zone; - if (observableZone && observableZone !== currentZone) { - return observableZone.run(error, this, arguments, errorSource); + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(error, this, arguments, nextSource); } else { return error.apply(this, arguments); } @@ -126,10 +165,12 @@ declare let define: any; Subscriber.prototype.complete = function() { const currentZone = Zone.current; - const observableZone = this._zone; + const subscriptionZone = this._zone; - if (observableZone && observableZone !== currentZone) { - return observableZone.run(complete, this, arguments, completeSource); + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(complete, this, arguments, nextSource); } else { return complete.apply(this, arguments); } @@ -137,10 +178,12 @@ declare let define: any; Subscriber.prototype.unsubscribe = function() { const currentZone = Zone.current; - const observableZone = this._zone; + const subscriptionZone = this._zone; - if (observableZone && observableZone !== currentZone) { - return observableZone.run(unsubscribe, this, arguments, unsubscribeSource); + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(unsubscribe, this, arguments, nextSource); } else { return unsubscribe.apply(this, arguments); } diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 5f38778e9..906d3906f 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -150,4 +150,49 @@ describe('Zone interaction', () => { expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); }); + it('should run operators in the zone of declaration with Observable.create', () => { + const log: string[] = []; + const rootZone: Zone = Zone.current; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + let observable: any = constructorZone.run(() => Observable.create((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); + + observable = operatorZone.run(() => observable.map((value: any) => { + expect(Zone.current.name).toEqual(operatorZone.name); + log.push('map: ' + value); + return value; + })); + + subscriptionZone.run( + () => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + (e: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('error: ' + e); + }, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + })); + + expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); + }); + + }); \ No newline at end of file From 0e5ad2242c471da4d7fd13748f000e3fe2d623db Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 19 Jul 2017 09:28:33 +0900 Subject: [PATCH 08/17] refactor, not create a new closure _subscribe everytime --- karma-build.conf.js | 1 + karma-dist.conf.js | 1 + lib/rxjs/rxjs.ts | 99 +++++++++++++++++++++----------------- test/browser-zone-setup.ts | 3 +- test/rxjs/rxjs.spec.ts | 6 +-- 5 files changed, 62 insertions(+), 48 deletions(-) diff --git a/karma-build.conf.js b/karma-build.conf.js index 87c87a5ea..aa725a75b 100644 --- a/karma-build.conf.js +++ b/karma-build.conf.js @@ -13,5 +13,6 @@ module.exports = function (config) { config.files.push('build/lib/zone.js'); config.files.push('build/lib/common/promise.js'); config.files.push('build/lib/common/error-rewrite.js'); + config.files.push('build/lib/rxjs/rxjs.js'); config.files.push('build/test/main.js'); }; diff --git a/karma-dist.conf.js b/karma-dist.conf.js index 844aef697..7304f4c91 100644 --- a/karma-dist.conf.js +++ b/karma-dist.conf.js @@ -18,5 +18,6 @@ module.exports = function (config) { config.files.push('dist/sync-test.js'); config.files.push('dist/task-tracking.js'); config.files.push('dist/wtf.js'); + config.files.push('dist/zone-patch-rxjs.js'); config.files.push('build/test/main.js'); }; diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index cd74bf402..f1525fb84 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -19,6 +19,7 @@ declare let define: any; } }(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global, (Rx: any) => { + 'use strict'; Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const subscribeSource = 'rxjs.subscribe'; const nextSource = 'rxjs.Subscriber.next'; @@ -33,6 +34,14 @@ declare let define: any; Rx.Observable = function() { Observable.apply(this, arguments); this._zone = Zone.current; + + // patch inner function this._subscribe to check + // SubscriptionZone is same with ConstuctorZone or not + if (this._subscribe && typeof this._subscribe === 'function' && !this._originalSubscribe) { + this._originalSubscribe = this._subscribe; + this._subscribe = _patchedSubscribe; + } + return this; }; @@ -42,6 +51,39 @@ declare let define: any; const lift = Observable.prototype.lift; const create = Observable.create; + const _patchedSubscribe = function() { + const currentZone = Zone.current; + const _zone = this._zone; + + const args = Array.prototype.slice.call(arguments); + const subscriber = args.length > 0 ? args[0] : undefined; + // also keep currentZone in Subscriber + // for later Subscriber.next/error/complete method + if (subscriber && !subscriber._zone) { + subscriber._zone = currentZone; + } + // _subscribe should run in ConstructorZone + // but for performance concern, we should check + // whether ConsturctorZone === Zone.current here + const tearDownLogic = _zone !== Zone.current ? + _zone.run(this._originalSubscribe, this, args) : + this._originalSubscribe.apply(this, args); + if (tearDownLogic && typeof tearDownLogic === 'function') { + const patchedTearDownLogic = function() { + // tearDownLogic should also run in ConstructorZone + // but for performance concern, we should check + // whether ConsturctorZone === Zone.current here + if (_zone && _zone !== Zone.current) { + return _zone.run(tearDownLogic, this, arguments); + } else { + return tearDownLogic.apply(this, arguments); + } + }; + return patchedTearDownLogic; + } + return tearDownLogic; + }; + // patch Observable.prototype.subscribe // if SubscripitionZone is different with ConstructorZone // we should run _subscribe in ConstructorZone and @@ -51,43 +93,6 @@ declare let define: any; const _zone = this._zone; const currentZone = Zone.current; - // patch inner function this._subscribe to check - // SubscriptionZone is same with ConstuctorZone or not - if (this._subscribe && typeof this._subscribe === 'function') { - this._subscribe._zone = this._zone; - const _subscribe = this._subscribe; - if (_zone) { - this._subscribe = function() { - const args = Array.prototype.slice.call(arguments); - const subscriber = args.length > 0 ? args[0] : undefined; - // also keep currentZone in Subscriber - // for later Subscriber.next/error/complete method - if (subscriber && !subscriber._zone) { - subscriber._zone = currentZone; - } - // _subscribe should run in ConstructorZone - // but for performance concern, we should check - // whether ConsturctorZone === Zone.current here - const tearDownLogic = _zone !== Zone.current ? _zone.run(_subscribe, this, args) : - _subscribe.apply(this, args); - if (tearDownLogic && typeof tearDownLogic === 'function') { - const patchedTearDownLogic = function() { - // tearDownLogic should also run in ConstructorZone - // but for performance concern, we should check - // whether ConsturctorZone === Zone.current here - if (_zone && _zone !== Zone.current) { - return _zone.run(tearDownLogic, this, arguments); - } else { - return tearDownLogic.apply(this, arguments); - } - }; - return patchedTearDownLogic; - } - return tearDownLogic; - }; - } - } - // if operator is involved, we should also // patch the call method to save the Subscription zone if (this.operator && _zone && _zone !== currentZone) { @@ -102,12 +107,6 @@ declare let define: any; }; } const result = subscribe.apply(this, arguments); - // clean up _subscribe._zone to prevent - // the same _subscribe being used in multiple - // Observable instances. - if (this._subscribe) { - this._subscribe._zone = undefined; - } // the result is the subscriber sink, // we save the current Zone here result._zone = currentZone; @@ -118,6 +117,13 @@ declare let define: any; Observable.prototype.lift = function() { const observable = lift.apply(this, arguments); observable._zone = Zone.current; + // patch inner function this._subscribe to check + // SubscriptionZone is same with ConstuctorZone or not + if (this._subscribe && typeof this._subscribe === 'function' && !this._originalSubscribe) { + this._originalSubscribe = this._subscribe; + this._subscribe = _patchedSubscribe; + } + return observable; }; @@ -125,6 +131,13 @@ declare let define: any; Rx.Observable.create = function() { const observable = create.apply(this, arguments); observable._zone = Zone.current; + // patch inner function this._subscribe to check + // SubscriptionZone is same with ConstuctorZone or not + if (this._subscribe && typeof this._subscribe === 'function' && !this._originalSubscribe) { + this._originalSubscribe = this._subscribe; + this._subscribe = _patchedSubscribe; + } + return observable; }; diff --git a/test/browser-zone-setup.ts b/test/browser-zone-setup.ts index 57c641595..1c16e96a8 100644 --- a/test/browser-zone-setup.ts +++ b/test/browser-zone-setup.ts @@ -17,5 +17,4 @@ import '../lib/zone-spec/proxy'; import '../lib/zone-spec/sync-test'; import '../lib/zone-spec/task-tracking'; import '../lib/zone-spec/wtf'; -import '../lib/extra/cordova'; -import '../lib/rxjs/rxjs'; \ No newline at end of file +import '../lib/extra/cordova'; \ No newline at end of file diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 906d3906f..fffc360cb 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -7,10 +7,10 @@ */ let rxjs; -if (typeof exports === 'object') { - rxjs = require('rxjs'); -} else { +if (typeof window !== 'undefined') { rxjs = (window as any).Rx; +} else if (typeof exports === 'object' && typeof module !== undefined) { + rxjs = require('rxjs'); } const Observable = rxjs.Observable; const Subscriber = rxjs.Subscriber; From f56c2135b54159b84cb47270ef178cf0679e806b Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 19 Jul 2017 20:06:18 +0900 Subject: [PATCH 09/17] fix set wrong object --- lib/rxjs/rxjs.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index f1525fb84..8a76d0ce8 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -119,9 +119,10 @@ declare let define: any; observable._zone = Zone.current; // patch inner function this._subscribe to check // SubscriptionZone is same with ConstuctorZone or not - if (this._subscribe && typeof this._subscribe === 'function' && !this._originalSubscribe) { - this._originalSubscribe = this._subscribe; - this._subscribe = _patchedSubscribe; + if (observable._subscribe && typeof observable._subscribe === 'function' && + !observable._originalSubscribe) { + observable._originalSubscribe = observable._subscribe; + observable._subscribe = _patchedSubscribe; } return observable; @@ -133,9 +134,10 @@ declare let define: any; observable._zone = Zone.current; // patch inner function this._subscribe to check // SubscriptionZone is same with ConstuctorZone or not - if (this._subscribe && typeof this._subscribe === 'function' && !this._originalSubscribe) { - this._originalSubscribe = this._subscribe; - this._subscribe = _patchedSubscribe; + if (observable._subscribe && typeof observable._subscribe === 'function' && + !observable._originalSubscribe) { + observable._originalSubscribe = observable._subscribe; + observable._subscribe = _patchedSubscribe; } return observable; From b238b0999a964877b9d9ae0fb9e4f06530a60c43 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 20 Jul 2017 01:28:42 +0900 Subject: [PATCH 10/17] begin to apply observable methods, 1: bindCallback --- lib/rxjs/rxjs.ts | 277 +++++++++++++++++++++++------------------ test/main.ts | 3 +- test/rxjs/rxjs.spec.ts | 192 +++++++++++++++++----------- 3 files changed, 277 insertions(+), 195 deletions(-) diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index 8a76d0ce8..03cfba2e8 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -27,30 +27,17 @@ declare let define: any; const completeSource = 'rxjs.Subscriber.complete'; const unsubscribeSource = 'rxjs.Subscriber.unsubscribe'; - const Observable = Rx.Observable; - - // monkey-patch Observable to save the - // current zone as ConstructorZone - Rx.Observable = function() { - Observable.apply(this, arguments); - this._zone = Zone.current; - + const patchObservableInstance = function(observable: any) { + observable._zone = Zone.current; // patch inner function this._subscribe to check // SubscriptionZone is same with ConstuctorZone or not - if (this._subscribe && typeof this._subscribe === 'function' && !this._originalSubscribe) { - this._originalSubscribe = this._subscribe; - this._subscribe = _patchedSubscribe; + if (observable._subscribe && typeof observable._subscribe === 'function' && + !observable._originalSubscribe) { + observable._originalSubscribe = observable._subscribe; + observable._subscribe = _patchedSubscribe; } - - return this; }; - Rx.Observable.prototype = Observable.prototype; - - const subscribe = Observable.prototype.subscribe; - const lift = Observable.prototype.lift; - const create = Observable.create; - const _patchedSubscribe = function() { const currentZone = Zone.current; const _zone = this._zone; @@ -84,124 +71,174 @@ declare let define: any; return tearDownLogic; }; - // patch Observable.prototype.subscribe - // if SubscripitionZone is different with ConstructorZone - // we should run _subscribe in ConstructorZone and - // create sinke in SubscriptionZone, - // and tearDown should also run into ConstructorZone - Observable.prototype.subscribe = function() { - const _zone = this._zone; - const currentZone = Zone.current; + const patchObservable = function(Rx: any, observableType: string) { + const symbol = Zone.__symbol__(observableType); + if (Rx[symbol]) { + // has patched + return; + } + + const Observable = Rx[symbol] = Rx[observableType]; + if (!Observable) { + // the subclass of Observable not loaded + return; + } - // if operator is involved, we should also - // patch the call method to save the Subscription zone - if (this.operator && _zone && _zone !== currentZone) { - const call = this.operator.call; - this.operator.call = function() { - const args = Array.prototype.slice.call(arguments); - const subscriber = args.length > 0 ? args[0] : undefined; - if (!subscriber._zone) { - subscriber._zone = currentZone; + // monkey-patch Observable to save the + // current zone as ConstructorZone + const patchedObservable: any = Rx[observableType] = function() { + Observable.apply(this, arguments); + patchObservableInstance(this); + return this; + }; + + patchedObservable.prototype = Observable.prototype; + Object.keys(Observable).forEach(key => { + patchedObservable[key] = Observable[key]; + }); + + const ObservablePrototype: any = Observable.prototype; + const symbolSubscribe = Zone.__symbol__('subscribe'); + + if (!ObservablePrototype[symbolSubscribe]) { + const subscribe = ObservablePrototype[symbolSubscribe] = ObservablePrototype.subscribe; + // patch Observable.prototype.subscribe + // if SubscripitionZone is different with ConstructorZone + // we should run _subscribe in ConstructorZone and + // create sinke in SubscriptionZone, + // and tearDown should also run into ConstructorZone + Observable.prototype.subscribe = function() { + const _zone = this._zone; + const currentZone = Zone.current; + + // if operator is involved, we should also + // patch the call method to save the Subscription zone + if (this.operator && _zone && _zone !== currentZone) { + const call = this.operator.call; + this.operator.call = function() { + const args = Array.prototype.slice.call(arguments); + const subscriber = args.length > 0 ? args[0] : undefined; + if (!subscriber._zone) { + subscriber._zone = currentZone; + } + return _zone.run(call, this, args); + }; } - return _zone.run(call, this, args); + const result = subscribe.apply(this, arguments); + // the result is the subscriber sink, + // we save the current Zone here + result._zone = currentZone; + return result; }; } - const result = subscribe.apply(this, arguments); - // the result is the subscriber sink, - // we save the current Zone here - result._zone = currentZone; - return result; - }; - // patch lift method to save ConstructorZone of Observable - Observable.prototype.lift = function() { - const observable = lift.apply(this, arguments); - observable._zone = Zone.current; - // patch inner function this._subscribe to check - // SubscriptionZone is same with ConstuctorZone or not - if (observable._subscribe && typeof observable._subscribe === 'function' && - !observable._originalSubscribe) { - observable._originalSubscribe = observable._subscribe; - observable._subscribe = _patchedSubscribe; - } + const symbolLift = Zone.__symbol__('lift'); + if (!ObservablePrototype[symbolLift]) { + const lift = ObservablePrototype[symbolLift] = ObservablePrototype.lift; - return observable; - }; + // patch lift method to save ConstructorZone of Observable + Observable.prototype.lift = function() { + const observable = lift.apply(this, arguments); + patchObservableInstance(observable); - // patch create method to save ConstructorZone of Observable - Rx.Observable.create = function() { - const observable = create.apply(this, arguments); - observable._zone = Zone.current; - // patch inner function this._subscribe to check - // SubscriptionZone is same with ConstuctorZone or not - if (observable._subscribe && typeof observable._subscribe === 'function' && - !observable._originalSubscribe) { - observable._originalSubscribe = observable._subscribe; - observable._subscribe = _patchedSubscribe; + return observable; + }; } - return observable; - }; - - const Subscriber = Rx.Subscriber; - - const next = Subscriber.prototype.next; - const error = Subscriber.prototype.error; - const complete = Subscriber.prototype.complete; - const unsubscribe = Subscriber.prototype.unsubscribe; + const symbolCreate = Zone.__symbol__('create'); + if (!Observable[symbolCreate]) { + const create = Observable[symbolCreate] = Observable.create; + // patch create method to save ConstructorZone of Observable + Rx.Observable.create = function() { + const observable = create.apply(this, arguments); + patchObservableInstance(observable); - // patch Subscriber.next to make sure it run - // into SubscriptionZone - Subscriber.prototype.next = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(next, this, arguments, nextSource); - } else { - return next.apply(this, arguments); + return observable; + }; } }; - Subscriber.prototype.error = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(error, this, arguments, nextSource); - } else { - return error.apply(this, arguments); - } + const patchSubscriber = function() { + const Subscriber = Rx.Subscriber; + + const next = Subscriber.prototype.next; + const error = Subscriber.prototype.error; + const complete = Subscriber.prototype.complete; + const unsubscribe = Subscriber.prototype.unsubscribe; + + // patch Subscriber.next to make sure it run + // into SubscriptionZone + Subscriber.prototype.next = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(next, this, arguments, nextSource); + } else { + return next.apply(this, arguments); + } + }; + + Subscriber.prototype.error = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(error, this, arguments, nextSource); + } else { + return error.apply(this, arguments); + } + }; + + Subscriber.prototype.complete = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(complete, this, arguments, nextSource); + } else { + return complete.apply(this, arguments); + } + }; + + Subscriber.prototype.unsubscribe = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(unsubscribe, this, arguments, nextSource); + } else { + return unsubscribe.apply(this, arguments); + } + }; }; - Subscriber.prototype.complete = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(complete, this, arguments, nextSource); - } else { - return complete.apply(this, arguments); + const patchObservableFactoryCreator = function(obj: any, factoryName: string) { + const symbol = Zone.__symbol__(factoryName); + if (obj[symbol]) { + return; } + const factoryCreator: any = obj[symbol] = obj[factoryName]; + obj[factoryName] = function() { + const factory: any = factoryCreator.apply(this, arguments); + return function() { + const observable = factory.apply(this, arguments); + patchObservableInstance(observable); + return observable; + }; + }; }; - Subscriber.prototype.unsubscribe = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(unsubscribe, this, arguments, nextSource); - } else { - return unsubscribe.apply(this, arguments); - } - }; + patchObservable(Rx, 'Observable'); + patchSubscriber(); + patchObservableFactoryCreator(Rx.Observable, 'bindCallback'); }); })); \ No newline at end of file diff --git a/test/main.ts b/test/main.ts index f41b45bba..6322d46b4 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,7 +15,8 @@ declare const __karma__: { __karma__.loaded = function() {}; (window as any).global = window; -System.config({defaultJSExtensions: true, map: {'rxjs': 'base/node_modules/rxjs/bundles/Rx.js'}}); +System.config( + {defaultJSExtensions: true, map: {'rxjs/Rx': 'base/node_modules/rxjs/bundles/Rx.js'}}); let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { browserPatchedPromise = Promise.resolve('browserPatched'); diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index fffc360cb..5be34adf9 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -6,14 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -let rxjs; +let rxjs: any; if (typeof window !== 'undefined') { rxjs = (window as any).Rx; } else if (typeof exports === 'object' && typeof module !== undefined) { - rxjs = require('rxjs'); + rxjs = require('rxjs/Rx'); } -const Observable = rxjs.Observable; -const Subscriber = rxjs.Subscriber; /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active @@ -33,16 +31,15 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); let subscriber: any = null; - const observable: any = - constructorZone.run(() => new Observable((_subscriber: any) => { - subscriber = _subscriber; - log.push('setup'); - expect(Zone.current.name).toEqual(constructorZone.name); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + const observable: any = constructorZone.run(() => new rxjs.Observable((_subscriber: any) => { + subscriber = _subscriber; + log.push('setup'); + expect(Zone.current.name).toEqual(constructorZone.name); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); subscriptionZone.run( () => observable.subscribe( () => { @@ -75,20 +72,19 @@ describe('Zone interaction', () => { const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - const observable: any = - constructorZone.run(() => new Observable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + const observable: any = constructorZone.run(() => new rxjs.Observable((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); subscriptionZone.run( () => observable.subscribe( @@ -111,52 +107,7 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let observable: any = - constructorZone.run(() => new Observable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); - - observable = operatorZone.run(() => observable.map((value: any) => { - expect(Zone.current.name).toEqual(operatorZone.name); - log.push('map: ' + value); - return value; - })); - - subscriptionZone.run( - () => observable.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next'); - }, - (e: any) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('error: ' + e); - }, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('complete'); - })); - - expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); - }); - - it('should run operators in the zone of declaration with Observable.create', () => { - const log: string[] = []; - const rootZone: Zone = Zone.current; - const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); - const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let observable: any = constructorZone.run(() => Observable.create((subscriber: any) => { + let observable: any = constructorZone.run(() => new rxjs.Observable((subscriber: any) => { // Execute the `next`/`complete` in different zone, and assert that // correct zone // is restored. @@ -194,5 +145,98 @@ describe('Zone interaction', () => { expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); }); + it('should run subscribe in zone of declaration with Observable.create', () => { + const log: string[] = []; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + let observable: any = constructorZone.run(() => rxjs.Observable.create((subscriber: any) => { + subscriber.next(1); + subscriber.complete(); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); + + observable.subscribe(() => { + log.push('next'); + }); + expect(log).toEqual(['next', 'cleanup']); + }); + + it('should run in the zone when subscribe is called to the same Subject', () => { + const log: string[] = []; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const subscriptionZone1: Zone = Zone.current.fork({name: 'Subscription Zone 1'}); + const subscriptionZone2: Zone = Zone.current.fork({name: 'Subscription Zone 2'}); + + let subject: any; + + constructorZone.run(() => { + subject = new rxjs.Subject(); + }); + + let subscription1: any; + let subscription2: any; + + subscriptionZone1.run(() => { + subscription1 = subject.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone1.name); + log.push('next1'); + }, + () => {}, + () => { + expect(Zone.current.name).toEqual(subscriptionZone1.name); + log.push('complete1'); + }); + }); + + subscriptionZone2.run(() => { + subscription2 = subject.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone2.name); + log.push('next2'); + }, + () => {}, + () => { + expect(Zone.current.name).toEqual(subscriptionZone2.name); + log.push('complete2'); + }); + }); + + subject.next(1); + subject.complete(); + + expect(log).toEqual(['next1', 'next2', 'complete1', 'complete2']); + }); + + it('bindCallback func callback should run in the correct zone', () => { + let log: string[] = []; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const triggerZone: Zone = Zone.current.fork({name: 'Trigger Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + + let func: any; + let boundFunc: any; + let observable: any; + + constructorZone.run(() => { + func = function(arg0: any, callback: Function) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(arg0); + }; + boundFunc = rxjs.Observable.bindCallback(func); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + }); + }); + + expect(log).toEqual(['nexttest']); + }); }); \ No newline at end of file From 38893796f38b847b74ddad1bf95d582bdd161ee6 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 20 Jul 2017 08:32:27 +0900 Subject: [PATCH 11/17] fix async scheduler reset zone issue --- lib/rxjs/rxjs.ts | 4 +++- test/rxjs/rxjs.spec.ts | 43 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index 03cfba2e8..a9268946a 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -127,7 +127,9 @@ declare let define: any; const result = subscribe.apply(this, arguments); // the result is the subscriber sink, // we save the current Zone here - result._zone = currentZone; + if (!result._zone) { + result._zone = currentZone; + } return result; }; } diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 5be34adf9..49141e97b 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -211,7 +211,7 @@ describe('Zone interaction', () => { expect(log).toEqual(['next1', 'next2', 'complete1', 'complete2']); }); - it('bindCallback func callback should run in the correct zone', () => { + it('bindCallback func callback should run in the correct zone', (done: any) => { let log: string[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const triggerZone: Zone = Zone.current.fork({name: 'Trigger Zone'}); @@ -238,5 +238,46 @@ describe('Zone interaction', () => { }); expect(log).toEqual(['nexttest']); + log.length = 0; + + constructorZone.run(() => { + func = function(arg0: any, callback: Function) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(arg0); + }; + boundFunc = rxjs.Observable.bindCallback(func, (arg: any) => { + return 'selector' + arg; + }); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + }); + }); + + expect(log).toEqual(['nextselectortest']); + log.length = 0; + + constructorZone.run(() => { + func = function(arg0: any, callback: Function) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(arg0); + }; + boundFunc = rxjs.Observable.bindCallback(func, null, rxjs.Scheduler.async); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + done(); + }); + }); + + expect(log).toEqual([]); }); }); \ No newline at end of file From eaebc3a81ad6343d85292f083db3bf02ae766545 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 20 Jul 2017 13:04:24 +0900 Subject: [PATCH 12/17] change require to import --- karma-build.conf.js | 1 - karma-dist.conf.js | 1 - lib/rxjs/rxjs.ts | 429 ++++++++++++++++++------------------- package.json | 4 +- test/browser-zone-setup.ts | 3 +- test/rxjs/rxjs.spec.ts | 25 +-- tsconfig-esm.json | 1 + 7 files changed, 224 insertions(+), 240 deletions(-) diff --git a/karma-build.conf.js b/karma-build.conf.js index aa725a75b..87c87a5ea 100644 --- a/karma-build.conf.js +++ b/karma-build.conf.js @@ -13,6 +13,5 @@ module.exports = function (config) { config.files.push('build/lib/zone.js'); config.files.push('build/lib/common/promise.js'); config.files.push('build/lib/common/error-rewrite.js'); - config.files.push('build/lib/rxjs/rxjs.js'); config.files.push('build/test/main.js'); }; diff --git a/karma-dist.conf.js b/karma-dist.conf.js index 7304f4c91..844aef697 100644 --- a/karma-dist.conf.js +++ b/karma-dist.conf.js @@ -18,6 +18,5 @@ module.exports = function (config) { config.files.push('dist/sync-test.js'); config.files.push('dist/task-tracking.js'); config.files.push('dist/wtf.js'); - config.files.push('dist/zone-patch-rxjs.js'); config.files.push('build/test/main.js'); }; diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index a9268946a..c7dcbdbaa 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -6,241 +6,228 @@ * found in the LICENSE file at https://angular.io/license */ -declare let define: any; -(function(root: any, factory: (Rx: any) => any) { - if (typeof exports === 'object' && typeof module !== 'undefined') { - // Node, CommonJS-like - module.exports = factory(require('rxjs')); - } else if (typeof define === 'function' && define.amd) { - // AMD - define(['rxjs'], factory); - } else { - factory(root.Rx); - } -}(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global, - (Rx: any) => { - 'use strict'; - Zone.__load_patch('rxjs', (global: any, Zone: ZoneType, api: _ZonePrivate) => { - const subscribeSource = 'rxjs.subscribe'; - const nextSource = 'rxjs.Subscriber.next'; - const errorSource = 'rxjs.Subscriber.error'; - const completeSource = 'rxjs.Subscriber.complete'; - const unsubscribeSource = 'rxjs.Subscriber.unsubscribe'; - - const patchObservableInstance = function(observable: any) { - observable._zone = Zone.current; - // patch inner function this._subscribe to check - // SubscriptionZone is same with ConstuctorZone or not - if (observable._subscribe && typeof observable._subscribe === 'function' && - !observable._originalSubscribe) { - observable._originalSubscribe = observable._subscribe; - observable._subscribe = _patchedSubscribe; - } - }; - - const _patchedSubscribe = function() { - const currentZone = Zone.current; - const _zone = this._zone; - - const args = Array.prototype.slice.call(arguments); - const subscriber = args.length > 0 ? args[0] : undefined; - // also keep currentZone in Subscriber - // for later Subscriber.next/error/complete method - if (subscriber && !subscriber._zone) { - subscriber._zone = currentZone; - } - // _subscribe should run in ConstructorZone +import * as Rx from 'rxjs/Rx'; + +(Zone as any).__load_patch('rxjs', (global: any, Zone: ZoneType, api: any) => { + const symbol: (symbolString: string) => string = (Zone as any).__symbol__; + const subscribeSource = 'rxjs.subscribe'; + const nextSource = 'rxjs.Subscriber.next'; + const errorSource = 'rxjs.Subscriber.error'; + const completeSource = 'rxjs.Subscriber.complete'; + const unsubscribeSource = 'rxjs.Subscriber.unsubscribe'; + + const patchObservableInstance = function(observable: any) { + observable._zone = Zone.current; + // patch inner function this._subscribe to check + // SubscriptionZone is same with ConstuctorZone or not + if (observable._subscribe && typeof observable._subscribe === 'function' && + !observable._originalSubscribe) { + observable._originalSubscribe = observable._subscribe; + observable._subscribe = _patchedSubscribe; + } + }; + + const _patchedSubscribe = function() { + const currentZone = Zone.current; + const _zone = this._zone; + + const args = Array.prototype.slice.call(arguments); + const subscriber = args.length > 0 ? args[0] : undefined; + // also keep currentZone in Subscriber + // for later Subscriber.next/error/complete method + if (subscriber && !subscriber._zone) { + subscriber._zone = currentZone; + } + // _subscribe should run in ConstructorZone + // but for performance concern, we should check + // whether ConsturctorZone === Zone.current here + const tearDownLogic = _zone !== Zone.current ? _zone.run(this._originalSubscribe, this, args) : + this._originalSubscribe.apply(this, args); + if (tearDownLogic && typeof tearDownLogic === 'function') { + const patchedTearDownLogic = function() { + // tearDownLogic should also run in ConstructorZone // but for performance concern, we should check // whether ConsturctorZone === Zone.current here - const tearDownLogic = _zone !== Zone.current ? - _zone.run(this._originalSubscribe, this, args) : - this._originalSubscribe.apply(this, args); - if (tearDownLogic && typeof tearDownLogic === 'function') { - const patchedTearDownLogic = function() { - // tearDownLogic should also run in ConstructorZone - // but for performance concern, we should check - // whether ConsturctorZone === Zone.current here - if (_zone && _zone !== Zone.current) { - return _zone.run(tearDownLogic, this, arguments); - } else { - return tearDownLogic.apply(this, arguments); - } - }; - return patchedTearDownLogic; + if (_zone && _zone !== Zone.current) { + return _zone.run(tearDownLogic, this, arguments); + } else { + return tearDownLogic.apply(this, arguments); } - return tearDownLogic; }; + return patchedTearDownLogic; + } + return tearDownLogic; + }; + + const patchObservable = function(Rx: any, observableType: string) { + const symbolObservable = symbol(observableType); + if (Rx[symbolObservable]) { + // has patched + return; + } + + const Observable = Rx[symbolObservable] = Rx[observableType]; + if (!Observable) { + // the subclass of Observable not loaded + return; + } + + // monkey-patch Observable to save the + // current zone as ConstructorZone + const patchedObservable: any = Rx[observableType] = function() { + Observable.apply(this, arguments); + patchObservableInstance(this); + return this; + }; + + patchedObservable.prototype = Observable.prototype; + Object.keys(Observable).forEach(key => { + patchedObservable[key] = Observable[key]; + }); - const patchObservable = function(Rx: any, observableType: string) { - const symbol = Zone.__symbol__(observableType); - if (Rx[symbol]) { - // has patched - return; - } - - const Observable = Rx[symbol] = Rx[observableType]; - if (!Observable) { - // the subclass of Observable not loaded - return; - } + const ObservablePrototype: any = Observable.prototype; + const symbolSubscribe = symbol('subscribe'); + + if (!ObservablePrototype[symbolSubscribe]) { + const subscribe = ObservablePrototype[symbolSubscribe] = ObservablePrototype.subscribe; + // patch Observable.prototype.subscribe + // if SubscripitionZone is different with ConstructorZone + // we should run _subscribe in ConstructorZone and + // create sinke in SubscriptionZone, + // and tearDown should also run into ConstructorZone + Observable.prototype.subscribe = function() { + const _zone = this._zone; + const currentZone = Zone.current; - // monkey-patch Observable to save the - // current zone as ConstructorZone - const patchedObservable: any = Rx[observableType] = function() { - Observable.apply(this, arguments); - patchObservableInstance(this); - return this; - }; - - patchedObservable.prototype = Observable.prototype; - Object.keys(Observable).forEach(key => { - patchedObservable[key] = Observable[key]; - }); - - const ObservablePrototype: any = Observable.prototype; - const symbolSubscribe = Zone.__symbol__('subscribe'); - - if (!ObservablePrototype[symbolSubscribe]) { - const subscribe = ObservablePrototype[symbolSubscribe] = ObservablePrototype.subscribe; - // patch Observable.prototype.subscribe - // if SubscripitionZone is different with ConstructorZone - // we should run _subscribe in ConstructorZone and - // create sinke in SubscriptionZone, - // and tearDown should also run into ConstructorZone - Observable.prototype.subscribe = function() { - const _zone = this._zone; - const currentZone = Zone.current; - - // if operator is involved, we should also - // patch the call method to save the Subscription zone - if (this.operator && _zone && _zone !== currentZone) { - const call = this.operator.call; - this.operator.call = function() { - const args = Array.prototype.slice.call(arguments); - const subscriber = args.length > 0 ? args[0] : undefined; - if (!subscriber._zone) { - subscriber._zone = currentZone; - } - return _zone.run(call, this, args); - }; + // if operator is involved, we should also + // patch the call method to save the Subscription zone + if (this.operator && _zone && _zone !== currentZone) { + const call = this.operator.call; + this.operator.call = function() { + const args = Array.prototype.slice.call(arguments); + const subscriber = args.length > 0 ? args[0] : undefined; + if (!subscriber._zone) { + subscriber._zone = currentZone; } - const result = subscribe.apply(this, arguments); - // the result is the subscriber sink, - // we save the current Zone here - if (!result._zone) { - result._zone = currentZone; - } - return result; + return _zone.run(call, this, args); }; } - - const symbolLift = Zone.__symbol__('lift'); - if (!ObservablePrototype[symbolLift]) { - const lift = ObservablePrototype[symbolLift] = ObservablePrototype.lift; - - // patch lift method to save ConstructorZone of Observable - Observable.prototype.lift = function() { - const observable = lift.apply(this, arguments); - patchObservableInstance(observable); - - return observable; - }; + const result = subscribe.apply(this, arguments); + // the result is the subscriber sink, + // we save the current Zone here + if (!result._zone) { + result._zone = currentZone; } + return result; + }; + } - const symbolCreate = Zone.__symbol__('create'); - if (!Observable[symbolCreate]) { - const create = Observable[symbolCreate] = Observable.create; - // patch create method to save ConstructorZone of Observable - Rx.Observable.create = function() { - const observable = create.apply(this, arguments); - patchObservableInstance(observable); + const symbolLift = symbol('lift'); + if (!ObservablePrototype[symbolLift]) { + const lift = ObservablePrototype[symbolLift] = ObservablePrototype.lift; - return observable; - }; - } - }; + // patch lift method to save ConstructorZone of Observable + Observable.prototype.lift = function() { + const observable = lift.apply(this, arguments); + patchObservableInstance(observable); - const patchSubscriber = function() { - const Subscriber = Rx.Subscriber; - - const next = Subscriber.prototype.next; - const error = Subscriber.prototype.error; - const complete = Subscriber.prototype.complete; - const unsubscribe = Subscriber.prototype.unsubscribe; - - // patch Subscriber.next to make sure it run - // into SubscriptionZone - Subscriber.prototype.next = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(next, this, arguments, nextSource); - } else { - return next.apply(this, arguments); - } - }; - - Subscriber.prototype.error = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(error, this, arguments, nextSource); - } else { - return error.apply(this, arguments); - } - }; - - Subscriber.prototype.complete = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(complete, this, arguments, nextSource); - } else { - return complete.apply(this, arguments); - } - }; - - Subscriber.prototype.unsubscribe = function() { - const currentZone = Zone.current; - const subscriptionZone = this._zone; - - // for performance concern, check Zone.current - // equal with this._zone(SubscriptionZone) or not - if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(unsubscribe, this, arguments, nextSource); - } else { - return unsubscribe.apply(this, arguments); - } - }; + return observable; }; + } - const patchObservableFactoryCreator = function(obj: any, factoryName: string) { - const symbol = Zone.__symbol__(factoryName); - if (obj[symbol]) { - return; - } - const factoryCreator: any = obj[symbol] = obj[factoryName]; - obj[factoryName] = function() { - const factory: any = factoryCreator.apply(this, arguments); - return function() { - const observable = factory.apply(this, arguments); - patchObservableInstance(observable); - return observable; - }; - }; + const symbolCreate = symbol('create'); + if (!Observable[symbolCreate]) { + const create = Observable[symbolCreate] = Observable.create; + // patch create method to save ConstructorZone of Observable + Rx.Observable.create = function() { + const observable = create.apply(this, arguments); + patchObservableInstance(observable); + + return observable; + }; + } + }; + + const patchSubscriber = function() { + const Subscriber = Rx.Subscriber; + + const next = Subscriber.prototype.next; + const error = Subscriber.prototype.error; + const complete = Subscriber.prototype.complete; + const unsubscribe = Subscriber.prototype.unsubscribe; + + // patch Subscriber.next to make sure it run + // into SubscriptionZone + Subscriber.prototype.next = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(next, this, arguments, nextSource); + } else { + return next.apply(this, arguments); + } + }; + + Subscriber.prototype.error = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(error, this, arguments, nextSource); + } else { + return error.apply(this, arguments); + } + }; + + Subscriber.prototype.complete = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(complete, this, arguments, nextSource); + } else { + return complete.apply(this, arguments); + } + }; + + Subscriber.prototype.unsubscribe = function() { + const currentZone = Zone.current; + const subscriptionZone = this._zone; + + // for performance concern, check Zone.current + // equal with this._zone(SubscriptionZone) or not + if (subscriptionZone && subscriptionZone !== currentZone) { + return subscriptionZone.run(unsubscribe, this, arguments, nextSource); + } else { + return unsubscribe.apply(this, arguments); + } + }; + }; + + const patchObservableFactoryCreator = function(obj: any, factoryName: string) { + const symbolFactory: string = symbol(factoryName); + if (obj[symbolFactory]) { + return; + } + const factoryCreator: any = obj[symbolFactory] = obj[factoryName]; + obj[factoryName] = function() { + const factory: any = factoryCreator.apply(this, arguments); + return function() { + const observable = factory.apply(this, arguments); + patchObservableInstance(observable); + return observable; }; + }; + }; - patchObservable(Rx, 'Observable'); - patchSubscriber(); - patchObservableFactoryCreator(Rx.Observable, 'bindCallback'); - }); - })); \ No newline at end of file + patchObservable(Rx, 'Observable'); + patchSubscriber(); + patchObservableFactoryCreator(Rx.Observable, 'bindCallback'); +}); \ No newline at end of file diff --git a/package.json b/package.json index 8e3cd15f6..885b39088 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "webdriver-sauce-test": "node test/webdriver/test.sauce.js", "ws-client": "node ./test/ws-client.js", "ws-server": "node ./test/ws-server.js", - "tsc": "tsc", - "tsc:w": "tsc -w", + "tsc": "tsc -p .", + "tsc:w": "tsc -w -p .", "test": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine\"", "test:phantomjs": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:phantomjs\"", "test:phantomjs-single": "concurrently \"npm run ws-server\" \"npm run karma-jasmine-phantomjs:autoclose\"", diff --git a/test/browser-zone-setup.ts b/test/browser-zone-setup.ts index 1c16e96a8..57c641595 100644 --- a/test/browser-zone-setup.ts +++ b/test/browser-zone-setup.ts @@ -17,4 +17,5 @@ import '../lib/zone-spec/proxy'; import '../lib/zone-spec/sync-test'; import '../lib/zone-spec/task-tracking'; import '../lib/zone-spec/wtf'; -import '../lib/extra/cordova'; \ No newline at end of file +import '../lib/extra/cordova'; +import '../lib/rxjs/rxjs'; \ No newline at end of file diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 49141e97b..09def684f 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -6,12 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -let rxjs: any; -if (typeof window !== 'undefined') { - rxjs = (window as any).Rx; -} else if (typeof exports === 'object' && typeof module !== undefined) { - rxjs = require('rxjs/Rx'); -} +import * as Rx from 'rxjs/Rx'; /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active @@ -31,7 +26,7 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); let subscriber: any = null; - const observable: any = constructorZone.run(() => new rxjs.Observable((_subscriber: any) => { + const observable: any = constructorZone.run(() => new Rx.Observable((_subscriber: any) => { subscriber = _subscriber; log.push('setup'); expect(Zone.current.name).toEqual(constructorZone.name); @@ -72,7 +67,7 @@ describe('Zone interaction', () => { const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - const observable: any = constructorZone.run(() => new rxjs.Observable((subscriber: any) => { + const observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { // Execute the `next`/`complete` in different zone, and assert that // correct zone // is restored. @@ -107,7 +102,7 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let observable: any = constructorZone.run(() => new rxjs.Observable((subscriber: any) => { + let observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { // Execute the `next`/`complete` in different zone, and assert that // correct zone // is restored. @@ -148,7 +143,7 @@ describe('Zone interaction', () => { it('should run subscribe in zone of declaration with Observable.create', () => { const log: string[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - let observable: any = constructorZone.run(() => rxjs.Observable.create((subscriber: any) => { + let observable: any = constructorZone.run(() => Rx.Observable.create((subscriber: any) => { subscriber.next(1); subscriber.complete(); return () => { @@ -173,7 +168,7 @@ describe('Zone interaction', () => { let subject: any; constructorZone.run(() => { - subject = new rxjs.Subject(); + subject = new Rx.Subject(); }); let subscription1: any; @@ -226,7 +221,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = rxjs.Observable.bindCallback(func); + boundFunc = Rx.Observable.bindCallback(func); observable = boundFunc('test'); }); @@ -245,7 +240,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = rxjs.Observable.bindCallback(func, (arg: any) => { + boundFunc = Rx.Observable.bindCallback(func, (arg: any) => { return 'selector' + arg; }); observable = boundFunc('test'); @@ -266,7 +261,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = rxjs.Observable.bindCallback(func, null, rxjs.Scheduler.async); + boundFunc = Rx.Observable.bindCallback(func, null, Rx.Scheduler.async); observable = boundFunc('test'); }); @@ -280,4 +275,6 @@ describe('Zone interaction', () => { expect(log).toEqual([]); }); + + }); \ No newline at end of file diff --git a/tsconfig-esm.json b/tsconfig-esm.json index 81532b656..ef36952ad 100644 --- a/tsconfig-esm.json +++ b/tsconfig-esm.json @@ -12,6 +12,7 @@ "noEmitOnError": false, "stripInternal": true, "sourceMap": true, + "moduleResolution": "node", "lib": ["es5", "dom", "es2015.promise"] }, "exclude": [ From f85592797ad09ac5d1e397eaaa2ad8ca20f3bdb6 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 20 Jul 2017 13:28:23 +0900 Subject: [PATCH 13/17] still need require in rxjs.spec --- karma-dist.conf.js | 1 + test/rxjs/rxjs.spec.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/karma-dist.conf.js b/karma-dist.conf.js index 844aef697..7304f4c91 100644 --- a/karma-dist.conf.js +++ b/karma-dist.conf.js @@ -18,5 +18,6 @@ module.exports = function (config) { config.files.push('dist/sync-test.js'); config.files.push('dist/task-tracking.js'); config.files.push('dist/wtf.js'); + config.files.push('dist/zone-patch-rxjs.js'); config.files.push('build/test/main.js'); }; diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 09def684f..8be38f36d 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import * as Rx from 'rxjs/Rx'; +let Rx: any; +if (typeof window !== 'undefined') { + Rx = (window as any).Rx; +} else if (typeof exports === 'object' && typeof module !== undefined) { + Rx = require('rxjs/Rx'); +} /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active From 1b4d50cca4f32a5a5e72220a902a6fb6b43cfb4a Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 20 Jul 2017 16:08:37 +0900 Subject: [PATCH 14/17] make build and dist pass test --- lib/rxjs/rxjs.ts | 32 +++++++------- test/main.ts | 7 ++- test/rxjs/rxjs.spec.ts | 98 ++++++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index c7dcbdbaa..cb8097fc7 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -15,6 +15,7 @@ import * as Rx from 'rxjs/Rx'; const errorSource = 'rxjs.Subscriber.error'; const completeSource = 'rxjs.Subscriber.complete'; const unsubscribeSource = 'rxjs.Subscriber.unsubscribe'; + const teardownSource = 'rxjs.Subscriber.teardownLogic'; const patchObservableInstance = function(observable: any) { observable._zone = Zone.current; @@ -41,15 +42,16 @@ import * as Rx from 'rxjs/Rx'; // _subscribe should run in ConstructorZone // but for performance concern, we should check // whether ConsturctorZone === Zone.current here - const tearDownLogic = _zone !== Zone.current ? _zone.run(this._originalSubscribe, this, args) : - this._originalSubscribe.apply(this, args); + const tearDownLogic = _zone !== Zone.current ? + _zone.run(this._originalSubscribe, this, args, subscribeSource) : + this._originalSubscribe.apply(this, args); if (tearDownLogic && typeof tearDownLogic === 'function') { const patchedTearDownLogic = function() { // tearDownLogic should also run in ConstructorZone // but for performance concern, we should check // whether ConsturctorZone === Zone.current here if (_zone && _zone !== Zone.current) { - return _zone.run(tearDownLogic, this, arguments); + return _zone.run(tearDownLogic, this, arguments, teardownSource); } else { return tearDownLogic.apply(this, arguments); } @@ -61,14 +63,10 @@ import * as Rx from 'rxjs/Rx'; const patchObservable = function(Rx: any, observableType: string) { const symbolObservable = symbol(observableType); - if (Rx[symbolObservable]) { - // has patched - return; - } - const Observable = Rx[symbolObservable] = Rx[observableType]; - if (!Observable) { - // the subclass of Observable not loaded + const Observable = Rx[observableType]; + if (!Observable || Observable[symbolObservable]) { + // the subclass of Observable not loaded or have been patched return; } @@ -81,6 +79,8 @@ import * as Rx from 'rxjs/Rx'; }; patchedObservable.prototype = Observable.prototype; + patchedObservable[symbolObservable] = Observable; + Object.keys(Observable).forEach(key => { patchedObservable[key] = Observable[key]; }); @@ -109,7 +109,7 @@ import * as Rx from 'rxjs/Rx'; if (!subscriber._zone) { subscriber._zone = currentZone; } - return _zone.run(call, this, args); + return _zone.run(call, this, args, subscribeSource); }; } const result = subscribe.apply(this, arguments); @@ -136,8 +136,8 @@ import * as Rx from 'rxjs/Rx'; } const symbolCreate = symbol('create'); - if (!Observable[symbolCreate]) { - const create = Observable[symbolCreate] = Observable.create; + if (!patchedObservable[symbolCreate]) { + const create = patchedObservable[symbolCreate] = Observable.create; // patch create method to save ConstructorZone of Observable Rx.Observable.create = function() { const observable = create.apply(this, arguments); @@ -178,7 +178,7 @@ import * as Rx from 'rxjs/Rx'; // for performance concern, check Zone.current // equal with this._zone(SubscriptionZone) or not if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(error, this, arguments, nextSource); + return subscriptionZone.run(error, this, arguments, errorSource); } else { return error.apply(this, arguments); } @@ -191,7 +191,7 @@ import * as Rx from 'rxjs/Rx'; // for performance concern, check Zone.current // equal with this._zone(SubscriptionZone) or not if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(complete, this, arguments, nextSource); + return subscriptionZone.run(complete, this, arguments, completeSource); } else { return complete.apply(this, arguments); } @@ -204,7 +204,7 @@ import * as Rx from 'rxjs/Rx'; // for performance concern, check Zone.current // equal with this._zone(SubscriptionZone) or not if (subscriptionZone && subscriptionZone !== currentZone) { - return subscriptionZone.run(unsubscribe, this, arguments, nextSource); + return subscriptionZone.run(unsubscribe, this, arguments, unsubscribeSource); } else { return unsubscribe.apply(this, arguments); } diff --git a/test/main.ts b/test/main.ts index 6322d46b4..a73d6124a 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,8 +15,11 @@ declare const __karma__: { __karma__.loaded = function() {}; (window as any).global = window; -System.config( - {defaultJSExtensions: true, map: {'rxjs/Rx': 'base/node_modules/rxjs/bundles/Rx.js'}}); +System.config({ + defaultJSExtensions: true, + map: {'rxjs/Rx': 'base/node_modules/rxjs/bundles/Rx.js'}, + meta: {'node_modules/rxjs/bundles/Rx.js': {format: 'global'}} +}); let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { browserPatchedPromise = Promise.resolve('browserPatched'); diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 8be38f36d..26e5a1eaa 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -6,12 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -let Rx: any; -if (typeof window !== 'undefined') { - Rx = (window as any).Rx; -} else if (typeof exports === 'object' && typeof module !== undefined) { - Rx = require('rxjs/Rx'); -} +import * as Rx from 'rxjs/Rx'; + +// @JiaLiPassion, TODO: find how to config systemjs to remove this hack +const globalRx = (typeof window !== 'undefined') ? (window as any).Rx : undefined; +const isGlobalRx = !!globalRx && globalRx.Observable[Zone.__symbol__('Observable')] != undefined; + +const RxObservable = isGlobalRx ? globalRx.Observable : Rx.Observable; +const RxSubject = isGlobalRx ? globalRx.Subject : Rx.Subject; +const RxScheduler = isGlobalRx ? globalRx.Scheduler : Rx.Scheduler; /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active @@ -31,15 +34,16 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); let subscriber: any = null; - const observable: any = constructorZone.run(() => new Rx.Observable((_subscriber: any) => { - subscriber = _subscriber; - log.push('setup'); - expect(Zone.current.name).toEqual(constructorZone.name); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + const observable: any = + constructorZone.run(() => new RxObservable((_subscriber: any) => { + subscriber = _subscriber; + log.push('setup'); + expect(Zone.current.name).toEqual(constructorZone.name); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); subscriptionZone.run( () => observable.subscribe( () => { @@ -72,19 +76,20 @@ describe('Zone interaction', () => { const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - const observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + const observable: any = + constructorZone.run(() => new RxObservable((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); subscriptionZone.run( () => observable.subscribe( @@ -107,19 +112,20 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + let observable: any = + constructorZone.run(() => new RxObservable((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); observable = operatorZone.run(() => observable.map((value: any) => { expect(Zone.current.name).toEqual(operatorZone.name); @@ -148,7 +154,7 @@ describe('Zone interaction', () => { it('should run subscribe in zone of declaration with Observable.create', () => { const log: string[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - let observable: any = constructorZone.run(() => Rx.Observable.create((subscriber: any) => { + let observable: any = constructorZone.run(() => RxObservable.create((subscriber: any) => { subscriber.next(1); subscriber.complete(); return () => { @@ -173,7 +179,7 @@ describe('Zone interaction', () => { let subject: any; constructorZone.run(() => { - subject = new Rx.Subject(); + subject = new RxSubject(); }); let subscription1: any; @@ -226,7 +232,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = Rx.Observable.bindCallback(func); + boundFunc = RxObservable.bindCallback(func); observable = boundFunc('test'); }); @@ -245,7 +251,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = Rx.Observable.bindCallback(func, (arg: any) => { + boundFunc = RxObservable.bindCallback(func, (arg: any) => { return 'selector' + arg; }); observable = boundFunc('test'); @@ -266,7 +272,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = Rx.Observable.bindCallback(func, null, Rx.Scheduler.async); + boundFunc = RxObservable.bindCallback(func, null, RxScheduler.async); observable = boundFunc('test'); }); From 18d996522288eede3d2747cb04064b9a4b76cd97 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 20 Jul 2017 16:10:50 +0900 Subject: [PATCH 15/17] remove meta def --- test/main.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/main.ts b/test/main.ts index a73d6124a..6322d46b4 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,11 +15,8 @@ declare const __karma__: { __karma__.loaded = function() {}; (window as any).global = window; -System.config({ - defaultJSExtensions: true, - map: {'rxjs/Rx': 'base/node_modules/rxjs/bundles/Rx.js'}, - meta: {'node_modules/rxjs/bundles/Rx.js': {format: 'global'}} -}); +System.config( + {defaultJSExtensions: true, map: {'rxjs/Rx': 'base/node_modules/rxjs/bundles/Rx.js'}}); let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { browserPatchedPromise = Promise.resolve('browserPatched'); From 389472e23916d48315ec4288f5effbab15ccfde1 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 20 Jul 2017 18:58:41 +0900 Subject: [PATCH 16/17] make import work on both build and dist --- karma-base.conf.js | 2 +- karma-dist.conf.js | 1 + test/global-rxjs.ts | 11 +++++ test/main.ts | 10 ++++- test/rxjs/rxjs.spec.ts | 91 +++++++++++++++++++----------------------- 5 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 test/global-rxjs.ts diff --git a/karma-base.conf.js b/karma-base.conf.js index e29b4b13f..c4fb05a91 100644 --- a/karma-base.conf.js +++ b/karma-base.conf.js @@ -13,7 +13,7 @@ module.exports = function (config) { 'node_modules/systemjs/dist/system-polyfills.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/whatwg-fetch/fetch.js', - 'node_modules/rxjs/bundles/Rx.js', + {pattern: 'node_modules/rxjs/bundles/Rx.js', watched: true, served: true, included: false}, {pattern: 'test/assets/**/*.*', watched: true, served: true, included: false}, {pattern: 'build/**/*.js.map', watched: true, served: true, included: false}, {pattern: 'build/**/*.js', watched: true, served: true, included: false} diff --git a/karma-dist.conf.js b/karma-dist.conf.js index 7304f4c91..0632f8d5d 100644 --- a/karma-dist.conf.js +++ b/karma-dist.conf.js @@ -18,6 +18,7 @@ module.exports = function (config) { config.files.push('dist/sync-test.js'); config.files.push('dist/task-tracking.js'); config.files.push('dist/wtf.js'); + config.files.push('node_modules/rxjs/bundles/Rx.js'); config.files.push('dist/zone-patch-rxjs.js'); config.files.push('build/test/main.js'); }; diff --git a/test/global-rxjs.ts b/test/global-rxjs.ts new file mode 100644 index 000000000..f012a136a --- /dev/null +++ b/test/global-rxjs.ts @@ -0,0 +1,11 @@ +/** + * @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 + */ +const globalRx: any = (window as any).Rx; +exports.Observable = globalRx.Observable; +exports.Subject = globalRx.Subject; +exports.Scheduler = globalRx.Scheduler; \ No newline at end of file diff --git a/test/main.ts b/test/main.ts index 6322d46b4..871994246 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,12 +15,18 @@ declare const __karma__: { __karma__.loaded = function() {}; (window as any).global = window; -System.config( - {defaultJSExtensions: true, map: {'rxjs/Rx': 'base/node_modules/rxjs/bundles/Rx.js'}}); let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { + System.config({ + defaultJSExtensions: true, + map: {'rxjs/Rx': 'base/build/test/global-rxjs.js'}, + }); browserPatchedPromise = Promise.resolve('browserPatched'); } else { + System.config({ + defaultJSExtensions: true, + map: {'rxjs/Rx': 'base/node_modules/rxjs/bundles/Rx.js'}, + }); // this means that Zone has not patched the browser yet, which means we must be running in // build mode and need to load the browser patch. browserPatchedPromise = System.import('/base/build/test/browser-zone-setup'); diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 26e5a1eaa..09def684f 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -8,14 +8,6 @@ import * as Rx from 'rxjs/Rx'; -// @JiaLiPassion, TODO: find how to config systemjs to remove this hack -const globalRx = (typeof window !== 'undefined') ? (window as any).Rx : undefined; -const isGlobalRx = !!globalRx && globalRx.Observable[Zone.__symbol__('Observable')] != undefined; - -const RxObservable = isGlobalRx ? globalRx.Observable : Rx.Observable; -const RxSubject = isGlobalRx ? globalRx.Subject : Rx.Subject; -const RxScheduler = isGlobalRx ? globalRx.Scheduler : Rx.Scheduler; - /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active * when the callback was passed into the Rx. @@ -34,16 +26,15 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); let subscriber: any = null; - const observable: any = - constructorZone.run(() => new RxObservable((_subscriber: any) => { - subscriber = _subscriber; - log.push('setup'); - expect(Zone.current.name).toEqual(constructorZone.name); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + const observable: any = constructorZone.run(() => new Rx.Observable((_subscriber: any) => { + subscriber = _subscriber; + log.push('setup'); + expect(Zone.current.name).toEqual(constructorZone.name); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); subscriptionZone.run( () => observable.subscribe( () => { @@ -76,20 +67,19 @@ describe('Zone interaction', () => { const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - const observable: any = - constructorZone.run(() => new RxObservable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + const observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); subscriptionZone.run( () => observable.subscribe( @@ -112,20 +102,19 @@ describe('Zone interaction', () => { const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let observable: any = - constructorZone.run(() => new RxObservable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); + let observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); observable = operatorZone.run(() => observable.map((value: any) => { expect(Zone.current.name).toEqual(operatorZone.name); @@ -154,7 +143,7 @@ describe('Zone interaction', () => { it('should run subscribe in zone of declaration with Observable.create', () => { const log: string[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - let observable: any = constructorZone.run(() => RxObservable.create((subscriber: any) => { + let observable: any = constructorZone.run(() => Rx.Observable.create((subscriber: any) => { subscriber.next(1); subscriber.complete(); return () => { @@ -179,7 +168,7 @@ describe('Zone interaction', () => { let subject: any; constructorZone.run(() => { - subject = new RxSubject(); + subject = new Rx.Subject(); }); let subscription1: any; @@ -232,7 +221,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = RxObservable.bindCallback(func); + boundFunc = Rx.Observable.bindCallback(func); observable = boundFunc('test'); }); @@ -251,7 +240,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = RxObservable.bindCallback(func, (arg: any) => { + boundFunc = Rx.Observable.bindCallback(func, (arg: any) => { return 'selector' + arg; }); observable = boundFunc('test'); @@ -272,7 +261,7 @@ describe('Zone interaction', () => { expect(Zone.current.name).toEqual(constructorZone.name); callback(arg0); }; - boundFunc = RxObservable.bindCallback(func, null, RxScheduler.async); + boundFunc = Rx.Observable.bindCallback(func, null, Rx.Scheduler.async); observable = boundFunc('test'); }); From 696f6cb49b9a3f9c471c08cb94421f91947e7144 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 21 Jul 2017 00:54:28 +0900 Subject: [PATCH 17/17] continue to add cases --- lib/rxjs/rxjs.ts | 1 + test/rxjs/rxjs.bindCallback.spec.ts | 86 ++++++++ test/rxjs/rxjs.bindNodeCallback.spec.ts | 110 ++++++++++ test/rxjs/rxjs.combineLatest.spec.ts | 57 +++++ test/rxjs/rxjs.common.spec.ts | 208 ++++++++++++++++++ test/rxjs/rxjs.spec.ts | 277 +----------------------- test/test-util.ts | 12 +- 7 files changed, 477 insertions(+), 274 deletions(-) create mode 100644 test/rxjs/rxjs.bindCallback.spec.ts create mode 100644 test/rxjs/rxjs.bindNodeCallback.spec.ts create mode 100644 test/rxjs/rxjs.combineLatest.spec.ts create mode 100644 test/rxjs/rxjs.common.spec.ts diff --git a/lib/rxjs/rxjs.ts b/lib/rxjs/rxjs.ts index cb8097fc7..0732c1dba 100644 --- a/lib/rxjs/rxjs.ts +++ b/lib/rxjs/rxjs.ts @@ -230,4 +230,5 @@ import * as Rx from 'rxjs/Rx'; patchObservable(Rx, 'Observable'); patchSubscriber(); patchObservableFactoryCreator(Rx.Observable, 'bindCallback'); + patchObservableFactoryCreator(Rx.Observable, 'bindNodeCallback'); }); \ No newline at end of file diff --git a/test/rxjs/rxjs.bindCallback.spec.ts b/test/rxjs/rxjs.bindCallback.spec.ts new file mode 100644 index 000000000..014a7b503 --- /dev/null +++ b/test/rxjs/rxjs.bindCallback.spec.ts @@ -0,0 +1,86 @@ +/** + * @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 * as Rx from 'rxjs/Rx'; +import {asyncTest} from '../test-util'; + +describe('Observable.bindCallback', () => { + let log: string[]; + const constructorZone: Zone = Zone.root.fork({name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.root.fork({name: 'Subscription Zone'}); + let func: any; + let boundFunc: any; + let observable: any; + + beforeEach(() => { + log = []; + }); + + it('bindCallback func callback should run in the correct zone', () => { + constructorZone.run(() => { + func = function(arg0: any, callback: Function) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(arg0); + }; + boundFunc = Rx.Observable.bindCallback(func); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + }); + }); + + expect(log).toEqual(['nexttest']); + }); + + it('bindCallback with selector should run in correct zone', () => { + constructorZone.run(() => { + func = function(arg0: any, callback: Function) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(arg0); + }; + boundFunc = Rx.Observable.bindCallback(func, (arg: any) => { + return 'selector' + arg; + }); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + }); + }); + + expect(log).toEqual(['nextselectortest']); + }); + + xit('bindCallback with async scheduler should run in correct zone', asyncTest((done: any) => { + constructorZone.run(() => { + func = function(arg0: any, callback: Function) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(arg0); + }; + boundFunc = Rx.Observable.bindCallback(func, null, Rx.Scheduler.asap); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + done(); + }); + }); + + expect(log).toEqual([]); + }, Zone.root)); +}); \ No newline at end of file diff --git a/test/rxjs/rxjs.bindNodeCallback.spec.ts b/test/rxjs/rxjs.bindNodeCallback.spec.ts new file mode 100644 index 000000000..26ea5e1b2 --- /dev/null +++ b/test/rxjs/rxjs.bindNodeCallback.spec.ts @@ -0,0 +1,110 @@ +/** + * @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 * as Rx from 'rxjs/Rx'; +import {asyncTest} from '../test-util'; + +describe('Observable.bindNodeCallback', () => { + let log: string[]; + const constructorZone: Zone = Zone.root.fork({name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.root.fork({name: 'Subscription Zone'}); + let func: any; + let boundFunc: any; + let observable: any; + + beforeEach(() => { + log = []; + }); + + it('bindNodeCallback func callback should run in the correct zone', () => { + constructorZone.run(() => { + func = function(arg: any, callback: (error: any, result: any) => any) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(null, arg); + }; + boundFunc = Rx.Observable.bindNodeCallback(func); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + }); + }); + + expect(log).toEqual(['nexttest']); + }); + + it('bindNodeCallback with selector should run in correct zone', () => { + constructorZone.run(() => { + func = function(arg: any, callback: (error: any, result: any) => any) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(null, arg); + }; + boundFunc = Rx.Observable.bindNodeCallback(func, (arg: any) => { + return 'selector' + arg; + }); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + }); + }); + + expect(log).toEqual(['nextselectortest']); + }); + + xit('bindNodeCallback with async scheduler should run in correct zone', asyncTest((done: any) => { + constructorZone.run(() => { + func = function(arg: any, callback: (error: any, result: any) => any) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(null, arg); + }; + boundFunc = Rx.Observable.bindCallback(func, null, Rx.Scheduler.asap); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe((arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + done(); + }); + }); + + expect(log).toEqual([]); + })); + + it('bindNodeCallback call with error should run in correct zone', () => { + constructorZone.run(() => { + func = function(arg: any, callback: (error: any, result: any) => any) { + expect(Zone.current.name).toEqual(constructorZone.name); + callback(arg, null); + }; + boundFunc = Rx.Observable.bindCallback(func); + observable = boundFunc('test'); + }); + + subscriptionZone.run(() => { + observable.subscribe( + (arg: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next' + arg); + }, + (error: any) => { + log.push('error' + error); + }); + }); + + expect(log).toEqual(['nexttest,']); + }); +}); \ No newline at end of file diff --git a/test/rxjs/rxjs.combineLatest.spec.ts b/test/rxjs/rxjs.combineLatest.spec.ts new file mode 100644 index 000000000..e4d6e249e --- /dev/null +++ b/test/rxjs/rxjs.combineLatest.spec.ts @@ -0,0 +1,57 @@ +/** + * @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 * as Rx from 'rxjs/Rx'; + +describe('Observable.combineLatest', () => { + let log: string[]; + const constructorZone1: Zone = Zone.current.fork({name: 'Constructor Zone1'}); + const constructorZone2: Zone = Zone.current.fork({name: 'Constructor Zone2'}); + const constructorZone3: Zone = Zone.current.fork({name: 'Constructor Zone3'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + let observable1: any; + let observable2: any; + let subscriber1: any; + let subscriber2: any; + + let combinedObservable: any; + + beforeEach(() => { + log = []; + }); + + it('bindCallback func callback should run in the correct zone', () => { + observable1 = constructorZone1.run(() => new Rx.Observable((_subscriber) => { + subscriber1 = _subscriber; + expect(Zone.current.name).toEqual(constructorZone1.name); + log.push('setup1'); + })); + observable2 = constructorZone2.run(() => new Rx.Observable((_subscriber) => { + subscriber2 = _subscriber; + expect(Zone.current.name).toEqual(constructorZone2.name); + log.push('setup2'); + })); + + constructorZone3.run(() => { + combinedObservable = Rx.Observable.combineLatest(observable1, observable2); + }); + + subscriptionZone.run(() => { + combinedObservable.subscribe((combined: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push(combined); + }); + }); + + subscriber1.next(1); + subscriber2.next(2); + subscriber2.next(3); + + expect(log).toEqual(['setup1', 'setup2', [1, 2], [1, 3]]); + }); +}); \ No newline at end of file diff --git a/test/rxjs/rxjs.common.spec.ts b/test/rxjs/rxjs.common.spec.ts new file mode 100644 index 000000000..106321d96 --- /dev/null +++ b/test/rxjs/rxjs.common.spec.ts @@ -0,0 +1,208 @@ +/** + * @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 * as Rx from 'rxjs/Rx'; + +/** + * The point of these tests, is to ensure that all callbacks execute in the Zone which was active + * when the callback was passed into the Rx. + * + * The implications are: + * - Observable callback passed into `Observable` executes in the same Zone as when the + * `new Observable` was invoked. + * - The subscription callbacks passed into `subscribe` execute in the same Zone as when the + * `subscribe` method was invoked. + * - The operator callbacks passe into `map`, etc..., execute in the same Zone as when the + * `operator` (`lift`) method was invoked. + */ +describe('Zone interaction', () => { + it('should run methods in the zone of declaration', () => { + const log: string[] = []; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + let subscriber: any = null; + const observable: any = constructorZone.run(() => new Rx.Observable((_subscriber: any) => { + subscriber = _subscriber; + log.push('setup'); + expect(Zone.current.name).toEqual(constructorZone.name); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); + subscriptionZone.run( + () => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + (): any => null, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + })); + subscriber.next('MyValue'); + subscriber.complete(); + + expect(log).toEqual(['setup', 'next', 'complete', 'cleanup']); + log.length = 0; + + subscriptionZone.run(() => observable.subscribe((): any => null, () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('error'); + }, (): any => null)); + subscriber.next('MyValue'); + subscriber.error('MyError'); + + expect(log).toEqual(['setup', 'error', 'cleanup']); + }); + + it('should run methods in the zone of declaration when nexting synchronously', () => { + const log: string[] = []; + const rootZone: Zone = Zone.current; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + const observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); + + subscriptionZone.run( + () => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + (): any => null, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + })); + + expect(log).toEqual(['next', 'complete', 'cleanup']); + }); + + it('should run operators in the zone of declaration', () => { + const log: string[] = []; + const rootZone: Zone = Zone.current; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + let observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { + // Execute the `next`/`complete` in different zone, and assert that + // correct zone + // is restored. + rootZone.run(() => { + subscriber.next('MyValue'); + subscriber.complete(); + }); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); + + observable = operatorZone.run(() => observable.map((value: any) => { + expect(Zone.current.name).toEqual(operatorZone.name); + log.push('map: ' + value); + return value; + })); + + subscriptionZone.run( + () => observable.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('next'); + }, + (e: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('error: ' + e); + }, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('complete'); + })); + + expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); + }); + + it('should run subscribe in zone of declaration with Observable.create', () => { + const log: string[] = []; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + let observable: any = constructorZone.run(() => Rx.Observable.create((subscriber: any) => { + subscriber.next(1); + subscriber.complete(); + return () => { + expect(Zone.current.name).toEqual(constructorZone.name); + log.push('cleanup'); + }; + })); + + observable.subscribe(() => { + log.push('next'); + }); + + expect(log).toEqual(['next', 'cleanup']); + }); + + it('should run in the zone when subscribe is called to the same Subject', () => { + const log: string[] = []; + const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); + const subscriptionZone1: Zone = Zone.current.fork({name: 'Subscription Zone 1'}); + const subscriptionZone2: Zone = Zone.current.fork({name: 'Subscription Zone 2'}); + + let subject: any; + + constructorZone.run(() => { + subject = new Rx.Subject(); + }); + + let subscription1: any; + let subscription2: any; + + subscriptionZone1.run(() => { + subscription1 = subject.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone1.name); + log.push('next1'); + }, + () => {}, + () => { + expect(Zone.current.name).toEqual(subscriptionZone1.name); + log.push('complete1'); + }); + }); + + subscriptionZone2.run(() => { + subscription2 = subject.subscribe( + () => { + expect(Zone.current.name).toEqual(subscriptionZone2.name); + log.push('next2'); + }, + () => {}, + () => { + expect(Zone.current.name).toEqual(subscriptionZone2.name); + log.push('complete2'); + }); + }); + + subject.next(1); + subject.complete(); + + expect(log).toEqual(['next1', 'next2', 'complete1', 'complete2']); + }); +}); \ No newline at end of file diff --git a/test/rxjs/rxjs.spec.ts b/test/rxjs/rxjs.spec.ts index 09def684f..0b93d66b2 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/rxjs/rxjs.spec.ts @@ -5,276 +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 * as Rx from 'rxjs/Rx'; - -/** - * The point of these tests, is to ensure that all callbacks execute in the Zone which was active - * when the callback was passed into the Rx. - * - * The implications are: - * - Observable callback passed into `Observable` executes in the same Zone as when the - * `new Observable` was invoked. - * - The subscription callbacks passed into `subscribe` execute in the same Zone as when the - * `subscribe` method was invoked. - * - The operator callbacks passe into `map`, etc..., execute in the same Zone as when the - * `operator` (`lift`) method was invoked. - */ -describe('Zone interaction', () => { - it('should run methods in the zone of declaration', () => { - const log: string[] = []; - const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let subscriber: any = null; - const observable: any = constructorZone.run(() => new Rx.Observable((_subscriber: any) => { - subscriber = _subscriber; - log.push('setup'); - expect(Zone.current.name).toEqual(constructorZone.name); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); - subscriptionZone.run( - () => observable.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next'); - }, - (): any => null, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('complete'); - })); - subscriber.next('MyValue'); - subscriber.complete(); - - expect(log).toEqual(['setup', 'next', 'complete', 'cleanup']); - log.length = 0; - - subscriptionZone.run(() => observable.subscribe((): any => null, () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('error'); - }, (): any => null)); - subscriber.next('MyValue'); - subscriber.error('MyError'); - - expect(log).toEqual(['setup', 'error', 'cleanup']); - }); - - it('should run methods in the zone of declaration when nexting synchronously', () => { - const log: string[] = []; - const rootZone: Zone = Zone.current; - const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - const observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); - - subscriptionZone.run( - () => observable.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next'); - }, - (): any => null, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('complete'); - })); - - expect(log).toEqual(['next', 'complete', 'cleanup']); - }); - - it('should run operators in the zone of declaration', () => { - const log: string[] = []; - const rootZone: Zone = Zone.current; - const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); - const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - let observable: any = constructorZone.run(() => new Rx.Observable((subscriber: any) => { - // Execute the `next`/`complete` in different zone, and assert that - // correct zone - // is restored. - rootZone.run(() => { - subscriber.next('MyValue'); - subscriber.complete(); - }); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); - - observable = operatorZone.run(() => observable.map((value: any) => { - expect(Zone.current.name).toEqual(operatorZone.name); - log.push('map: ' + value); - return value; - })); - - subscriptionZone.run( - () => observable.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next'); - }, - (e: any) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('error: ' + e); - }, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('complete'); - })); - - expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); - }); - - it('should run subscribe in zone of declaration with Observable.create', () => { - const log: string[] = []; - const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - let observable: any = constructorZone.run(() => Rx.Observable.create((subscriber: any) => { - subscriber.next(1); - subscriber.complete(); - return () => { - expect(Zone.current.name).toEqual(constructorZone.name); - log.push('cleanup'); - }; - })); - - observable.subscribe(() => { - log.push('next'); - }); - - expect(log).toEqual(['next', 'cleanup']); - }); - - it('should run in the zone when subscribe is called to the same Subject', () => { - const log: string[] = []; - const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - const subscriptionZone1: Zone = Zone.current.fork({name: 'Subscription Zone 1'}); - const subscriptionZone2: Zone = Zone.current.fork({name: 'Subscription Zone 2'}); - - let subject: any; - - constructorZone.run(() => { - subject = new Rx.Subject(); - }); - - let subscription1: any; - let subscription2: any; - - subscriptionZone1.run(() => { - subscription1 = subject.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone1.name); - log.push('next1'); - }, - () => {}, - () => { - expect(Zone.current.name).toEqual(subscriptionZone1.name); - log.push('complete1'); - }); - }); - - subscriptionZone2.run(() => { - subscription2 = subject.subscribe( - () => { - expect(Zone.current.name).toEqual(subscriptionZone2.name); - log.push('next2'); - }, - () => {}, - () => { - expect(Zone.current.name).toEqual(subscriptionZone2.name); - log.push('complete2'); - }); - }); - - subject.next(1); - subject.complete(); - - expect(log).toEqual(['next1', 'next2', 'complete1', 'complete2']); - }); - - it('bindCallback func callback should run in the correct zone', (done: any) => { - let log: string[] = []; - const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); - const triggerZone: Zone = Zone.current.fork({name: 'Trigger Zone'}); - const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); - - let func: any; - let boundFunc: any; - let observable: any; - - constructorZone.run(() => { - func = function(arg0: any, callback: Function) { - expect(Zone.current.name).toEqual(constructorZone.name); - callback(arg0); - }; - boundFunc = Rx.Observable.bindCallback(func); - observable = boundFunc('test'); - }); - - subscriptionZone.run(() => { - observable.subscribe((arg: any) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next' + arg); - }); - }); - - expect(log).toEqual(['nexttest']); - log.length = 0; - - constructorZone.run(() => { - func = function(arg0: any, callback: Function) { - expect(Zone.current.name).toEqual(constructorZone.name); - callback(arg0); - }; - boundFunc = Rx.Observable.bindCallback(func, (arg: any) => { - return 'selector' + arg; - }); - observable = boundFunc('test'); - }); - - subscriptionZone.run(() => { - observable.subscribe((arg: any) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next' + arg); - }); - }); - - expect(log).toEqual(['nextselectortest']); - log.length = 0; - - constructorZone.run(() => { - func = function(arg0: any, callback: Function) { - expect(Zone.current.name).toEqual(constructorZone.name); - callback(arg0); - }; - boundFunc = Rx.Observable.bindCallback(func, null, Rx.Scheduler.async); - observable = boundFunc('test'); - }); - - subscriptionZone.run(() => { - observable.subscribe((arg: any) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('next' + arg); - done(); - }); - }); - - expect(log).toEqual([]); - }); - - -}); \ No newline at end of file +import './rxjs.common.spec'; +import './rxjs.bindCallback.spec'; +import './rxjs.bindNodeCallback.spec'; +import './rxjs.combineLatest.spec'; \ No newline at end of file diff --git a/test/test-util.ts b/test/test-util.ts index c970c7b84..86781bc83 100644 --- a/test/test-util.ts +++ b/test/test-util.ts @@ -85,4 +85,14 @@ export function isSupportSetErrorStack() { return supportSetErrorStack; } -(isSupportSetErrorStack as any).message = 'supportSetErrorStack'; \ No newline at end of file +(isSupportSetErrorStack as any).message = 'supportSetErrorStack'; + +export function asyncTest(testFn: Function, zone: Zone = Zone.current) { + const AsyncTestZoneSpec = (Zone as any)['AsyncTestZoneSpec']; + return (done: Function) => { + let asyncTestZone: Zone = zone.fork(new AsyncTestZoneSpec(done, (error: Error) => { + fail(error); + }, 'asyncTest')); + asyncTestZone.run(testFn, this, [done]); + }; +} \ No newline at end of file