diff --git a/package.json b/package.json index 66252b7102..d765539816 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "clean_spec": "Clean up existing test spec build output", "clean_dist_cjs": "Clean up existing CJS package output", "clean_dist_es6": "Clean up existing ES6 package output", + "clean_dist_global": "Clean up existing Global package output", "commit": "Run git commit wizard", "compile_dist_cjs": "Compile codebase into CJS module", "compile_dist_es6": "Compile codebase into ES6", @@ -56,7 +57,7 @@ "build_es6": "npm-run-all clean_dist_es6 copy_src_es6 compile_dist_es6", "build_es6_for_docs": "npm-run-all clean_dist_es6 copy_src_es6 compile_dist_es6_for_docs", "build_closure_core": "node ./tools/make-closure-core.js", - "build_global": "shx rm -rf ./dist/global && mkdirp ./dist/global && node tools/make-umd-bundle.js && npm-run-all build_closure_core", + "build_global": "npm-run-all clean_dist_global build_es6 && mkdirp ./dist/global && node tools/make-umd-bundle.js && npm-run-all build_closure_core", "build_perf": "webdriver-manager update && npm-run-all build_cjs build_global perf", "build_test": "shx rm -rf ./dist/ && npm-run-all build_cjs clean_spec build_spec test_mocha", "build_cover": "shx rm -rf ./dist/ && npm-run-all build_cjs build_spec cover", @@ -67,6 +68,7 @@ "clean_spec": "shx rm -rf spec-js", "clean_dist_cjs": "shx rm -rf ./dist/cjs", "clean_dist_es6": "shx rm -rf ./dist/es6", + "clean_dist_global": "shx rm -rf ./dist/global", "copy_src_cjs": "mkdirp ./dist/cjs/src && shx cp -r ./src/* ./dist/cjs/src", "copy_src_es6": "mkdirp ./dist/es6/src && shx cp -r ./src/* ./dist/es6/src", "commit": "git-cz", @@ -142,10 +144,11 @@ }, "homepage": "https://github.com/ReactiveX/RxJS", "devDependencies": { - "babel-polyfill": "6.9.1", + "babel-core": "6.17.0", + "babel-polyfill": "6.16.0", + "babel-preset-es2015": "6.16.0", "benchmark": "^2.1.0", "benchpress": "2.0.0-beta.1", - "browserify": "13.0.1", "chai": "^3.5.0", "color": "^0.11.1", "colors": "1.1.2", @@ -153,6 +156,7 @@ "coveralls": "^2.11.13", "cz-conventional-changelog": "^1.2.0", "doctoc": "^1.0.0", + "escape-string-regexp": "^1.0.5 ", "esdoc": "^0.4.7", "eslint": "^2.12.0", "fs-extra": "^0.30.0", @@ -177,6 +181,7 @@ "platform": "^1.3.1", "promise": "^7.1.1", "protractor": "^3.1.1", + "rollup": "0.36.3", "rx": "latest", "shx": "^0.1.4", "sinon": "^2.0.0-pre", @@ -187,7 +192,6 @@ "typings": "^1.3.3", "validate-commit-msg": "^2.3.1", "watch": "^0.18.0", - "watchify": "3.7.0", "webpack": "^1.13.1", "xmlhttprequest": "1.8.0" }, diff --git a/publish_docs.sh b/publish_docs.sh index 3bf78d46e9..10653697f3 100755 --- a/publish_docs.sh +++ b/publish_docs.sh @@ -3,4 +3,13 @@ # Generates documentation and pushes it up to the site # WARNING: Do NOT run this script unless you have remote `upstream` set properly # -rm -rf tmp/docs && npm run build_docs && git checkout gh-pages && git fetch upstream && git rebase upstream/gh-pages && cp -r ./tmp/docs/ ./ && rm -rf tmp/ && git add . && git commit -am "chore(docs): docs generated automatically" && git push upstream gh-pages +rm -rf tmp/docs && \ + npm run build_docs && \ + git checkout gh-pages && \ + git fetch upstream && \ + git rebase upstream/gh-pages && \ + cp -r ./tmp/docs/ ./ && \ + rm -rf tmp/ && \ + git add . && \ + git commit -am "chore(docs): docs generated automatically" && \ + git push upstream gh-pages diff --git a/spec/helpers/ajax-helper.ts b/spec/helpers/ajax-helper.ts index 9c112c8635..60ae10113a 100644 --- a/spec/helpers/ajax-helper.ts +++ b/spec/helpers/ajax-helper.ts @@ -135,6 +135,12 @@ export class MockXMLHttpRequest { requestHeaders: any = {}; withCredentials: boolean = false; + onreadystatechange: (e: ProgressEvent) => any; + onerror: (e: ErrorEvent) => any; + onprogress: (e: ProgressEvent) => any; + ontimeout: (e: ProgressEvent) => any; + upload: XMLHttpRequestUpload; + constructor() { this.previousRequest = MockXMLHttpRequest.recentRequest; MockXMLHttpRequest.recentRequest = this; diff --git a/spec/observables/dom/ajax-spec.ts b/spec/observables/dom/ajax-spec.ts index 6b36531984..fd55f5e914 100644 --- a/spec/observables/dom/ajax-spec.ts +++ b/spec/observables/dom/ajax-spec.ts @@ -598,4 +598,166 @@ describe('Observable.ajax', () => { }); }); + + it('should work fine when XMLHttpRequest onreadystatechange property is monkey patched', function() { + Object.defineProperty(root.XMLHttpRequest.prototype, 'onreadystatechange', { + set: function (fn: (e: ProgressEvent) => any) { + const wrapFn = (ev: ProgressEvent) => { + const result = fn.call(this, ev); + if (result === false) { + ev.preventDefault(); + } + }; + this['_onreadystatechange'] = wrapFn; + }, + get() { + return this['_onreadystatechange']; + }, + configurable: true + }); + + Rx.Observable.ajax({ + url: '/flibbertyJibbet' + }) + .subscribe(); + + const request = MockXMLHttpRequest.mostRecent; + expect(() => { + request.onreadystatechange(('onreadystatechange')); + }).not.throw(); + + delete root.XMLHttpRequest.prototype.onreadystatechange; + }); + + it('should work fine when XMLHttpRequest ontimeout property is monkey patched', function() { + Object.defineProperty(root.XMLHttpRequest.prototype, 'ontimeout', { + set: function (fn: (e: ProgressEvent) => any) { + const wrapFn = (ev: ProgressEvent) => { + const result = fn.call(this, ev); + if (result === false) { + ev.preventDefault(); + } + }; + this['_ontimeout'] = wrapFn; + }, + get() { + return this['_ontimeout']; + }, + configurable: true + }); + + const ajaxRequest = { + url: '/flibbertyJibbet' + }; + + Rx.Observable.ajax(ajaxRequest) + .subscribe(); + + const request = MockXMLHttpRequest.mostRecent; + try { + request.ontimeout(('ontimeout')); + } catch (e) { + expect(e.message).to.equal(new Rx.AjaxTimeoutError((request), ajaxRequest).message); + } + delete root.XMLHttpRequest.prototype.ontimeout; + }); + + it('should work fine when XMLHttpRequest onprogress property is monkey patched', function() { + Object.defineProperty(root.XMLHttpRequest.prototype, 'onprogress', { + set: function (fn: (e: ProgressEvent) => any) { + const wrapFn = (ev: ProgressEvent) => { + const result = fn.call(this, ev); + if (result === false) { + ev.preventDefault(); + } + }; + this['_onprogress'] = wrapFn; + }, + get() { + return this['_onprogress']; + }, + configurable: true + }); + + Object.defineProperty(root.XMLHttpRequest.prototype, 'upload', { + get() { + return true; + }, + configurable: true + }); + + // mock for onprogress + root.XDomainRequest = true; + + Rx.Observable.ajax({ + url: '/flibbertyJibbet', + progressSubscriber: ({ + next: () => { + // noop + }, + error: () => { + // noop + }, + complete: () => { + // noop + } + }) + }) + .subscribe(); + + const request = MockXMLHttpRequest.mostRecent; + + expect(() => { + request.onprogress(('onprogress')); + }).not.throw(); + + delete root.XMLHttpRequest.prototype.onprogress; + delete root.XMLHttpRequest.prototype.upload; + delete root.XDomainRequest; + }); + + it('should work fine when XMLHttpRequest onerror property is monkey patched', function() { + Object.defineProperty(root.XMLHttpRequest.prototype, 'onerror', { + set: function (fn: (e: ProgressEvent) => any) { + const wrapFn = (ev: ProgressEvent) => { + const result = fn.call(this, ev); + if (result === false) { + ev.preventDefault(); + } + }; + this['_onerror'] = wrapFn; + }, + get() { + return this['_onerror']; + }, + configurable: true + }); + + Object.defineProperty(root.XMLHttpRequest.prototype, 'upload', { + get() { + return true; + }, + configurable: true + }); + + // mock for onprogress + root.XDomainRequest = true; + + Rx.Observable.ajax({ + url: '/flibbertyJibbet' + }) + .subscribe(); + + const request = MockXMLHttpRequest.mostRecent; + + try { + request.onerror(('onerror')); + } catch (e) { + expect(e.message).to.equal('ajax error'); + } + + delete root.XMLHttpRequest.prototype.onerror; + delete root.XMLHttpRequest.prototype.upload; + delete root.XDomainRequest; + }); }); \ No newline at end of file diff --git a/spec/operators/groupBy-spec.ts b/spec/operators/groupBy-spec.ts index e9298c4c7c..8dbdab6aae 100644 --- a/spec/operators/groupBy-spec.ts +++ b/spec/operators/groupBy-spec.ts @@ -5,6 +5,7 @@ declare const {hot, cold, asDiagram, expectObservable, expectSubscriptions}; declare const rxTestScheduler: Rx.TestScheduler; const Observable = Rx.Observable; +const ReplaySubject = Rx.ReplaySubject; /** @test {groupBy} */ describe('Observable.prototype.groupBy', () => { @@ -98,6 +99,27 @@ describe('Observable.prototype.groupBy', () => { expect(resultingGroups).to.deep.equal(expectedGroups); }); + it('should group values with a subject selector', (done: MochaDone) => { + const expectedGroups = [ + { key: 1, values: [3] }, + { key: 0, values: [2] } + ]; + + Observable.of(1, 2, 3) + .groupBy((x: number) => x % 2, null, null, () => new ReplaySubject(1)) + // Ensure each inner group reaches the destination after the first event + // has been next'd to the group + .delay(5) + .subscribe((g: any) => { + const expectedGroup = expectedGroups.shift(); + expect(g.key).to.equal(expectedGroup.key); + + g.subscribe((x: any) => { + expect(x).to.deep.equal(expectedGroup.values.shift()); + }); + }, null, done); + }); + it('should handle an empty Observable', () => { const e1 = cold('|'); const e1subs = '(^!)'; diff --git a/spec/operators/multicast-spec.ts b/spec/operators/multicast-spec.ts index 925d19215f..acc199a031 100644 --- a/spec/operators/multicast-spec.ts +++ b/spec/operators/multicast-spec.ts @@ -4,6 +4,7 @@ declare const {hot, cold, asDiagram, expectObservable, expectSubscriptions, time const Observable = Rx.Observable; const Subject = Rx.Subject; +const ReplaySubject = Rx.ReplaySubject; /** @test {multicast} */ describe('Observable.prototype.multicast', () => { @@ -89,6 +90,26 @@ describe('Observable.prototype.multicast', () => { expectSubscriptions(source.subscriptions).toBe(sourceSubs); }); + it('should accept a multicast selector and respect the subject\'s messaging semantics', () => { + const source = cold('-1-2-3----4-|'); + const sourceSubs = ['^ !', + ' ^ !', + ' ^ !']; + const multicasted = source.multicast(() => new ReplaySubject(1), + x => x.concat(x.takeLast(1))); + const expected1 = '-1-2-3----4-(4|)'; + const expected2 = ' -1-2-3----4-(4|)'; + const expected3 = ' -1-2-3----4-(4|)'; + const subscriber1 = hot('a| ').mergeMapTo(multicasted); + const subscriber2 = hot(' b| ').mergeMapTo(multicasted); + const subscriber3 = hot(' c| ').mergeMapTo(multicasted); + + expectObservable(subscriber1).toBe(expected1); + expectObservable(subscriber2).toBe(expected2); + expectObservable(subscriber3).toBe(expected3); + expectSubscriptions(source.subscriptions).toBe(sourceSubs); + }); + it('should do nothing if connect is not called, despite subscriptions', () => { const source = cold('--1-2---3-4--5-|'); const sourceSubs = []; diff --git a/src/observable/dom/AjaxObservable.ts b/src/observable/dom/AjaxObservable.ts index 506589ed1d..09bc9cdf4c 100644 --- a/src/observable/dom/AjaxObservable.ts +++ b/src/observable/dom/AjaxObservable.ts @@ -293,39 +293,42 @@ export class AjaxSubscriber extends Subscriber { private setupEvents(xhr: XMLHttpRequest, request: AjaxRequest) { const progressSubscriber = request.progressSubscriber; - xhr.ontimeout = function xhrTimeout(e) { + function xhrTimeout(e: ProgressEvent) { const {subscriber, progressSubscriber, request } = (xhrTimeout); if (progressSubscriber) { progressSubscriber.error(e); } subscriber.error(new AjaxTimeoutError(this, request)); //TODO: Make betterer. }; - (xhr.ontimeout).request = request; - (xhr.ontimeout).subscriber = this; - (xhr.ontimeout).progressSubscriber = progressSubscriber; - + xhr.ontimeout = xhrTimeout; + (xhrTimeout).request = request; + (xhrTimeout).subscriber = this; + (xhrTimeout).progressSubscriber = progressSubscriber; if (xhr.upload && 'withCredentials' in xhr && root.XDomainRequest) { if (progressSubscriber) { - xhr.onprogress = function xhrProgress(e) { + let xhrProgress: (e: ProgressEvent) => void; + xhrProgress = function(e: ProgressEvent) { const { progressSubscriber } = (xhrProgress); progressSubscriber.next(e); }; - (xhr.onprogress).progressSubscriber = progressSubscriber; + xhr.onprogress = xhrProgress; + (xhrProgress).progressSubscriber = progressSubscriber; } - - xhr.onerror = function xhrError(e) { + let xhrError: (e: ErrorEvent) => void; + xhrError = function(e: ErrorEvent) { const { progressSubscriber, subscriber, request } = (xhrError); if (progressSubscriber) { progressSubscriber.error(e); } subscriber.error(new AjaxError('ajax error', this, request)); }; - (xhr.onerror).request = request; - (xhr.onerror).subscriber = this; - (xhr.onerror).progressSubscriber = progressSubscriber; + xhr.onerror = xhrError; + (xhrError).request = request; + (xhrError).subscriber = this; + (xhrError).progressSubscriber = progressSubscriber; } - xhr.onreadystatechange = function xhrReadyStateChange(e) { + function xhrReadyStateChange(e: ProgressEvent) { const { subscriber, progressSubscriber, request } = (xhrReadyStateChange); if (this.readyState === 4) { // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) @@ -354,9 +357,10 @@ export class AjaxSubscriber extends Subscriber { } } }; - (xhr.onreadystatechange).subscriber = this; - (xhr.onreadystatechange).progressSubscriber = progressSubscriber; - (xhr.onreadystatechange).request = request; + xhr.onreadystatechange = xhrReadyStateChange; + (xhrReadyStateChange).subscriber = this; + (xhrReadyStateChange).progressSubscriber = progressSubscriber; + (xhrReadyStateChange).request = request; } unsubscribe() { diff --git a/src/operator/groupBy.ts b/src/operator/groupBy.ts index c0feeaa207..a23d462cc4 100644 --- a/src/operator/groupBy.ts +++ b/src/operator/groupBy.ts @@ -31,11 +31,13 @@ import { FastMap } from '../util/FastMap'; export function groupBy(this: Observable, keySelector: (value: T) => K): Observable>; export function groupBy(this: Observable, keySelector: (value: T) => K, elementSelector: void, durationSelector: (grouped: GroupedObservable) => Observable): Observable>; export function groupBy(this: Observable, keySelector: (value: T) => K, elementSelector?: (value: T) => R, durationSelector?: (grouped: GroupedObservable) => Observable): Observable>; +export function groupBy(this: Observable, keySelector: (value: T) => K, elementSelector?: (value: T) => R, durationSelector?: (grouped: GroupedObservable) => Observable, subjectSelector?: () => Subject): Observable>; /* tslint:disable:max-line-length */ export function groupBy(this: Observable, keySelector: (value: T) => K, elementSelector?: ((value: T) => R) | void, - durationSelector?: (grouped: GroupedObservable) => Observable): Observable> { - return this.lift(new GroupByOperator(this, keySelector, elementSelector, durationSelector)); + durationSelector?: (grouped: GroupedObservable) => Observable, + subjectSelector?: () => Subject): Observable> { + return this.lift(new GroupByOperator(keySelector, elementSelector, durationSelector, subjectSelector)); } export interface RefCountSubscription { @@ -46,15 +48,15 @@ export interface RefCountSubscription { } class GroupByOperator implements Operator> { - constructor(public source: Observable, - private keySelector: (value: T) => K, + constructor(private keySelector: (value: T) => K, private elementSelector?: ((value: T) => R) | void, - private durationSelector?: (grouped: GroupedObservable) => Observable) { + private durationSelector?: (grouped: GroupedObservable) => Observable, + private subjectSelector?: () => Subject) { } call(subscriber: Subscriber>, source: any): any { return source._subscribe(new GroupBySubscriber( - subscriber, this.keySelector, this.elementSelector, this.durationSelector + subscriber, this.keySelector, this.elementSelector, this.durationSelector, this.subjectSelector )); } } @@ -72,7 +74,8 @@ class GroupBySubscriber extends Subscriber implements RefCountSubscr constructor(destination: Subscriber>, private keySelector: (value: T) => K, private elementSelector?: ((value: T) => R) | void, - private durationSelector?: (grouped: GroupedObservable) => Observable) { + private durationSelector?: (grouped: GroupedObservable) => Observable, + private subjectSelector?: () => Subject) { super(destination); } @@ -109,7 +112,8 @@ class GroupBySubscriber extends Subscriber implements RefCountSubscr } if (!group) { - groups.set(key, group = new Subject()); + group = this.subjectSelector ? this.subjectSelector() : new Subject(); + groups.set(key, group); const groupedObservable = new GroupedObservable(key, group, this); this.destination.next(groupedObservable); if (this.durationSelector) { diff --git a/src/operator/multicast.ts b/src/operator/multicast.ts index f0302f17bd..de06574900 100644 --- a/src/operator/multicast.ts +++ b/src/operator/multicast.ts @@ -56,11 +56,11 @@ export class MulticastOperator implements Operator { constructor(private subjectFactory: () => Subject, private selector: (source: Observable) => Observable) { } - call(subscriber: Subscriber, self: any): any { + call(subscriber: Subscriber, source: any): any { const { selector } = this; - const connectable = new ConnectableObservable(self.source, this.subjectFactory); - const subscription = selector(connectable).subscribe(subscriber); - subscription.add(connectable.connect()); + const subject = this.subjectFactory(); + const subscription = selector(subject).subscribe(subscriber); + subscription.add(source._subscribe(subject)); return subscription; } } diff --git a/tools/custom-esdoc-plugin.js b/tools/custom-esdoc-plugin.js index 36fac63f32..3f4ce0c098 100644 --- a/tools/custom-esdoc-plugin.js +++ b/tools/custom-esdoc-plugin.js @@ -1,3 +1,4 @@ + function getTagValue(tag, tagName) { var unknownTags = tag.unknown; if (!unknownTags) { @@ -47,9 +48,9 @@ exports.onHandleTag = function onHandleTag(ev) { delete tag.importStyle; } if (isHidden) { - ev.data.tag[i] = {name: '', longname: ''}; - ev.data.tag[i]['export'] = false; - } else if (owner) { + ev.data.tag[i].builtinExternal = true; + } + if (owner && owner === 'Observable') { var ownerLongname = getLongname(ev, owner); tag.kind = 'method'; tag.static = false; @@ -61,4 +62,5 @@ exports.onHandleTag = function onHandleTag(ev) { delete tag.importStyle; } } + return ev; }; diff --git a/tools/make-umd-bundle.js b/tools/make-umd-bundle.js index ba65b684b4..f85cd63fa6 100644 --- a/tools/make-umd-bundle.js +++ b/tools/make-umd-bundle.js @@ -1,8 +1,21 @@ -var browserify = require('browserify'); +var rollup = require('rollup'); var fs = require('fs'); -var b = browserify('dist/cjs/Rx.js', { - baseDir: 'dist/cjs', - standalone: 'Rx' +var babel = require('babel-core'); + +rollup.rollup({ + entry: 'dist/es6/Rx.js' +}).then(function (bundle) { + var result = bundle.generate({ + format: 'es' + }); + + var out = babel.transform(result.code, { + compact: false, + presets: [ + ['es2015', {loose: true}] + ], + }); + + fs.writeFileSync('dist/global/Rx.js', out.code); }); -b.bundle().pipe(fs.createWriteStream('dist/global/Rx.js'));