531 lines
20 KiB
JavaScript
531 lines
20 KiB
JavaScript
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||
|
|
||
|
;(function (factory) {
|
||
|
var objectTypes = {
|
||
|
'boolean': false,
|
||
|
'function': true,
|
||
|
'object': true,
|
||
|
'number': false,
|
||
|
'string': false,
|
||
|
'undefined': false
|
||
|
};
|
||
|
|
||
|
var root = (objectTypes[typeof window] && window) || this,
|
||
|
freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports,
|
||
|
freeModule = objectTypes[typeof module] && module && !module.nodeType && module,
|
||
|
moduleExports = freeModule && freeModule.exports === freeExports && freeExports,
|
||
|
freeGlobal = objectTypes[typeof global] && global;
|
||
|
|
||
|
if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
|
||
|
root = freeGlobal;
|
||
|
}
|
||
|
|
||
|
// Because of build optimizers
|
||
|
if (typeof define === 'function' && define.amd) {
|
||
|
define(['rx.virtualtime', 'exports'], function (Rx, exports) {
|
||
|
root.Rx = factory(root, exports, Rx);
|
||
|
return root.Rx;
|
||
|
});
|
||
|
} else if (typeof module === 'object' && module && module.exports === freeExports) {
|
||
|
module.exports = factory(root, module.exports, require('./rx.all'));
|
||
|
} else {
|
||
|
root.Rx = factory(root, {}, root.Rx);
|
||
|
}
|
||
|
}.call(this, function (root, exp, Rx, undefined) {
|
||
|
|
||
|
// Defaults
|
||
|
var Observer = Rx.Observer,
|
||
|
Observable = Rx.Observable,
|
||
|
Notification = Rx.Notification,
|
||
|
VirtualTimeScheduler = Rx.VirtualTimeScheduler,
|
||
|
Disposable = Rx.Disposable,
|
||
|
disposableEmpty = Disposable.empty,
|
||
|
disposableCreate = Disposable.create,
|
||
|
CompositeDisposable = Rx.CompositeDisposable,
|
||
|
SingleAssignmentDisposable = Rx.SingleAssignmentDisposable,
|
||
|
slice = Array.prototype.slice,
|
||
|
inherits = Rx.internals.inherits,
|
||
|
defaultComparer = Rx.internals.isEqual;
|
||
|
|
||
|
function argsOrArray(args, idx) {
|
||
|
return args.length === 1 && Array.isArray(args[idx]) ?
|
||
|
args[idx] :
|
||
|
slice.call(args);
|
||
|
}
|
||
|
|
||
|
function OnNextPredicate(predicate) {
|
||
|
this.predicate = predicate;
|
||
|
};
|
||
|
|
||
|
OnNextPredicate.prototype.equals = function (other) {
|
||
|
if (other === this) { return true; }
|
||
|
if (other == null) { return false; }
|
||
|
if (other.kind !== 'N') { return false; }
|
||
|
return this.predicate(other.value);
|
||
|
};
|
||
|
|
||
|
function OnErrorPredicate(predicate) {
|
||
|
this.predicate = predicate;
|
||
|
};
|
||
|
|
||
|
OnErrorPredicate.prototype.equals = function (other) {
|
||
|
if (other === this) { return true; }
|
||
|
if (other == null) { return false; }
|
||
|
if (other.kind !== 'E') { return false; }
|
||
|
return this.predicate(other.exception);
|
||
|
};
|
||
|
|
||
|
var ReactiveTest = Rx.ReactiveTest = {
|
||
|
/** Default virtual time used for creation of observable sequences in unit tests. */
|
||
|
created: 100,
|
||
|
/** Default virtual time used to subscribe to observable sequences in unit tests. */
|
||
|
subscribed: 200,
|
||
|
/** Default virtual time used to dispose subscriptions in unit tests. */
|
||
|
disposed: 1000,
|
||
|
|
||
|
/**
|
||
|
* Factory method for an OnNext notification record at a given time with a given value or a predicate function.
|
||
|
*
|
||
|
* 1 - ReactiveTest.onNext(200, 42);
|
||
|
* 2 - ReactiveTest.onNext(200, function (x) { return x.length == 2; });
|
||
|
*
|
||
|
* @param ticks Recorded virtual time the OnNext notification occurs.
|
||
|
* @param value Recorded value stored in the OnNext notification or a predicate.
|
||
|
* @return Recorded OnNext notification.
|
||
|
*/
|
||
|
onNext: function (ticks, value) {
|
||
|
return typeof value === 'function' ?
|
||
|
new Recorded(ticks, new OnNextPredicate(value)) :
|
||
|
new Recorded(ticks, Notification.createOnNext(value));
|
||
|
},
|
||
|
/**
|
||
|
* Factory method for an OnError notification record at a given time with a given error.
|
||
|
*
|
||
|
* 1 - ReactiveTest.onNext(200, new Error('error'));
|
||
|
* 2 - ReactiveTest.onNext(200, function (e) { return e.message === 'error'; });
|
||
|
*
|
||
|
* @param ticks Recorded virtual time the OnError notification occurs.
|
||
|
* @param exception Recorded exception stored in the OnError notification.
|
||
|
* @return Recorded OnError notification.
|
||
|
*/
|
||
|
onError: function (ticks, error) {
|
||
|
return typeof error === 'function' ?
|
||
|
new Recorded(ticks, new OnErrorPredicate(error)) :
|
||
|
new Recorded(ticks, Notification.createOnError(error));
|
||
|
},
|
||
|
/**
|
||
|
* Factory method for an OnCompleted notification record at a given time.
|
||
|
*
|
||
|
* @param ticks Recorded virtual time the OnCompleted notification occurs.
|
||
|
* @return Recorded OnCompleted notification.
|
||
|
*/
|
||
|
onCompleted: function (ticks) {
|
||
|
return new Recorded(ticks, Notification.createOnCompleted());
|
||
|
},
|
||
|
/**
|
||
|
* Factory method for a subscription record based on a given subscription and disposal time.
|
||
|
*
|
||
|
* @param start Virtual time indicating when the subscription was created.
|
||
|
* @param end Virtual time indicating when the subscription was disposed.
|
||
|
* @return Subscription object.
|
||
|
*/
|
||
|
subscribe: function (start, end) {
|
||
|
return new Subscription(start, end);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a new object recording the production of the specified value at the given virtual time.
|
||
|
*
|
||
|
* @constructor
|
||
|
* @param {Number} time Virtual time the value was produced on.
|
||
|
* @param {Mixed} value Value that was produced.
|
||
|
* @param {Function} comparer An optional comparer.
|
||
|
*/
|
||
|
var Recorded = Rx.Recorded = function (time, value, comparer) {
|
||
|
this.time = time;
|
||
|
this.value = value;
|
||
|
this.comparer = comparer || defaultComparer;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Checks whether the given recorded object is equal to the current instance.
|
||
|
*
|
||
|
* @param {Recorded} other Recorded object to check for equality.
|
||
|
* @returns {Boolean} true if both objects are equal; false otherwise.
|
||
|
*/
|
||
|
Recorded.prototype.equals = function (other) {
|
||
|
return this.time === other.time && this.comparer(this.value, other.value);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns a string representation of the current Recorded value.
|
||
|
*
|
||
|
* @returns {String} String representation of the current Recorded value.
|
||
|
*/
|
||
|
Recorded.prototype.toString = function () {
|
||
|
return this.value.toString() + '@' + this.time;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a new subscription object with the given virtual subscription and unsubscription time.
|
||
|
*
|
||
|
* @constructor
|
||
|
* @param {Number} subscribe Virtual time at which the subscription occurred.
|
||
|
* @param {Number} unsubscribe Virtual time at which the unsubscription occurred.
|
||
|
*/
|
||
|
var Subscription = Rx.Subscription = function (start, end) {
|
||
|
this.subscribe = start;
|
||
|
this.unsubscribe = end || Number.MAX_VALUE;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Checks whether the given subscription is equal to the current instance.
|
||
|
* @param other Subscription object to check for equality.
|
||
|
* @returns {Boolean} true if both objects are equal; false otherwise.
|
||
|
*/
|
||
|
Subscription.prototype.equals = function (other) {
|
||
|
return this.subscribe === other.subscribe && this.unsubscribe === other.unsubscribe;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns a string representation of the current Subscription value.
|
||
|
* @returns {String} String representation of the current Subscription value.
|
||
|
*/
|
||
|
Subscription.prototype.toString = function () {
|
||
|
return '(' + this.subscribe + ', ' + (this.unsubscribe === Number.MAX_VALUE ? 'Infinite' : this.unsubscribe) + ')';
|
||
|
};
|
||
|
|
||
|
/** @private */
|
||
|
var MockDisposable = Rx.MockDisposable = function (scheduler) {
|
||
|
this.scheduler = scheduler;
|
||
|
this.disposes = [];
|
||
|
this.disposes.push(this.scheduler.clock);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* @memberOf MockDisposable#
|
||
|
* @prviate
|
||
|
*/
|
||
|
MockDisposable.prototype.dispose = function () {
|
||
|
this.disposes.push(this.scheduler.clock);
|
||
|
};
|
||
|
|
||
|
var MockObserver = (function (__super__) {
|
||
|
inherits(MockObserver, __super__);
|
||
|
|
||
|
function MockObserver(scheduler) {
|
||
|
__super__.call(this);
|
||
|
this.scheduler = scheduler;
|
||
|
this.messages = [];
|
||
|
}
|
||
|
|
||
|
var MockObserverPrototype = MockObserver.prototype;
|
||
|
|
||
|
MockObserverPrototype.onNext = function (value) {
|
||
|
this.messages.push(new Recorded(this.scheduler.clock, Notification.createOnNext(value)));
|
||
|
};
|
||
|
|
||
|
MockObserverPrototype.onError = function (exception) {
|
||
|
this.messages.push(new Recorded(this.scheduler.clock, Notification.createOnError(exception)));
|
||
|
};
|
||
|
|
||
|
MockObserverPrototype.onCompleted = function () {
|
||
|
this.messages.push(new Recorded(this.scheduler.clock, Notification.createOnCompleted()));
|
||
|
};
|
||
|
|
||
|
return MockObserver;
|
||
|
})(Observer);
|
||
|
|
||
|
function MockPromise(scheduler, messages) {
|
||
|
var self = this;
|
||
|
this.scheduler = scheduler;
|
||
|
this.messages = messages;
|
||
|
this.subscriptions = [];
|
||
|
this.observers = [];
|
||
|
for (var i = 0, len = this.messages.length; i < len; i++) {
|
||
|
var message = this.messages[i],
|
||
|
notification = message.value;
|
||
|
(function (innerNotification) {
|
||
|
scheduler.scheduleAbsoluteWithState(null, message.time, function () {
|
||
|
var obs = self.observers.slice(0);
|
||
|
|
||
|
for (var j = 0, jLen = obs.length; j < jLen; j++) {
|
||
|
innerNotification.accept(obs[j]);
|
||
|
}
|
||
|
return disposableEmpty;
|
||
|
});
|
||
|
})(notification);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MockPromise.prototype.then = function (onResolved, onRejected) {
|
||
|
var self = this;
|
||
|
|
||
|
this.subscriptions.push(new Subscription(this.scheduler.clock));
|
||
|
var index = this.subscriptions.length - 1;
|
||
|
|
||
|
var newPromise;
|
||
|
|
||
|
var observer = Rx.Observer.create(
|
||
|
function (x) {
|
||
|
var retValue = onResolved(x);
|
||
|
if (retValue && typeof retValue.then === 'function') {
|
||
|
newPromise = retValue;
|
||
|
} else {
|
||
|
var ticks = self.scheduler.clock;
|
||
|
newPromise = new MockPromise(self.scheduler, [Rx.ReactiveTest.onNext(ticks, undefined), Rx.ReactiveTest.onCompleted(ticks)]);
|
||
|
}
|
||
|
var idx = self.observers.indexOf(observer);
|
||
|
self.observers.splice(idx, 1);
|
||
|
self.subscriptions[index] = new Subscription(self.subscriptions[index].subscribe, self.scheduler.clock);
|
||
|
},
|
||
|
function (err) {
|
||
|
onRejected(err);
|
||
|
var idx = self.observers.indexOf(observer);
|
||
|
self.observers.splice(idx, 1);
|
||
|
self.subscriptions[index] = new Subscription(self.subscriptions[index].subscribe, self.scheduler.clock);
|
||
|
}
|
||
|
);
|
||
|
this.observers.push(observer);
|
||
|
|
||
|
return newPromise || new MockPromise(this.scheduler, this.messages);
|
||
|
};
|
||
|
|
||
|
var HotObservable = (function (__super__) {
|
||
|
|
||
|
function subscribe(observer) {
|
||
|
var observable = this;
|
||
|
this.observers.push(observer);
|
||
|
this.subscriptions.push(new Subscription(this.scheduler.clock));
|
||
|
var index = this.subscriptions.length - 1;
|
||
|
return disposableCreate(function () {
|
||
|
var idx = observable.observers.indexOf(observer);
|
||
|
observable.observers.splice(idx, 1);
|
||
|
observable.subscriptions[index] = new Subscription(observable.subscriptions[index].subscribe, observable.scheduler.clock);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
inherits(HotObservable, __super__);
|
||
|
|
||
|
function HotObservable(scheduler, messages) {
|
||
|
__super__.call(this, subscribe);
|
||
|
var message, notification, observable = this;
|
||
|
this.scheduler = scheduler;
|
||
|
this.messages = messages;
|
||
|
this.subscriptions = [];
|
||
|
this.observers = [];
|
||
|
for (var i = 0, len = this.messages.length; i < len; i++) {
|
||
|
message = this.messages[i];
|
||
|
notification = message.value;
|
||
|
(function (innerNotification) {
|
||
|
scheduler.scheduleAbsoluteWithState(null, message.time, function () {
|
||
|
var obs = observable.observers.slice(0);
|
||
|
|
||
|
for (var j = 0, jLen = obs.length; j < jLen; j++) {
|
||
|
innerNotification.accept(obs[j]);
|
||
|
}
|
||
|
return disposableEmpty;
|
||
|
});
|
||
|
})(notification);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return HotObservable;
|
||
|
})(Observable);
|
||
|
|
||
|
var ColdObservable = (function (__super__) {
|
||
|
|
||
|
function subscribe(observer) {
|
||
|
var message, notification, observable = this;
|
||
|
this.subscriptions.push(new Subscription(this.scheduler.clock));
|
||
|
var index = this.subscriptions.length - 1;
|
||
|
var d = new CompositeDisposable();
|
||
|
for (var i = 0, len = this.messages.length; i < len; i++) {
|
||
|
message = this.messages[i];
|
||
|
notification = message.value;
|
||
|
(function (innerNotification) {
|
||
|
d.add(observable.scheduler.scheduleRelativeWithState(null, message.time, function () {
|
||
|
innerNotification.accept(observer);
|
||
|
return disposableEmpty;
|
||
|
}));
|
||
|
})(notification);
|
||
|
}
|
||
|
return disposableCreate(function () {
|
||
|
observable.subscriptions[index] = new Subscription(observable.subscriptions[index].subscribe, observable.scheduler.clock);
|
||
|
d.dispose();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
inherits(ColdObservable, __super__);
|
||
|
|
||
|
function ColdObservable(scheduler, messages) {
|
||
|
__super__.call(this, subscribe);
|
||
|
this.scheduler = scheduler;
|
||
|
this.messages = messages;
|
||
|
this.subscriptions = [];
|
||
|
}
|
||
|
|
||
|
return ColdObservable;
|
||
|
})(Observable);
|
||
|
|
||
|
/** Virtual time scheduler used for testing applications and libraries built using Reactive Extensions. */
|
||
|
Rx.TestScheduler = (function (__super__) {
|
||
|
inherits(TestScheduler, __super__);
|
||
|
|
||
|
function baseComparer(x, y) {
|
||
|
return x > y ? 1 : (x < y ? -1 : 0);
|
||
|
}
|
||
|
|
||
|
function TestScheduler() {
|
||
|
__super__.call(this, 0, baseComparer);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Schedules an action to be executed at the specified virtual time.
|
||
|
*
|
||
|
* @param state State passed to the action to be executed.
|
||
|
* @param dueTime Absolute virtual time at which to execute the action.
|
||
|
* @param action Action to be executed.
|
||
|
* @return Disposable object used to cancel the scheduled action (best effort).
|
||
|
*/
|
||
|
TestScheduler.prototype.scheduleAbsoluteWithState = function (state, dueTime, action) {
|
||
|
dueTime <= this.clock && (dueTime = this.clock + 1);
|
||
|
return __super__.prototype.scheduleAbsoluteWithState.call(this, state, dueTime, action);
|
||
|
};
|
||
|
/**
|
||
|
* Adds a relative virtual time to an absolute virtual time value.
|
||
|
*
|
||
|
* @param absolute Absolute virtual time value.
|
||
|
* @param relative Relative virtual time value to add.
|
||
|
* @return Resulting absolute virtual time sum value.
|
||
|
*/
|
||
|
TestScheduler.prototype.add = function (absolute, relative) {
|
||
|
return absolute + relative;
|
||
|
};
|
||
|
/**
|
||
|
* Converts the absolute virtual time value to a DateTimeOffset value.
|
||
|
*
|
||
|
* @param absolute Absolute virtual time value to convert.
|
||
|
* @return Corresponding DateTimeOffset value.
|
||
|
*/
|
||
|
TestScheduler.prototype.toDateTimeOffset = function (absolute) {
|
||
|
return new Date(absolute).getTime();
|
||
|
};
|
||
|
/**
|
||
|
* Converts the TimeSpan value to a relative virtual time value.
|
||
|
*
|
||
|
* @param timeSpan TimeSpan value to convert.
|
||
|
* @return Corresponding relative virtual time value.
|
||
|
*/
|
||
|
TestScheduler.prototype.toRelative = function (timeSpan) {
|
||
|
return timeSpan;
|
||
|
};
|
||
|
/**
|
||
|
* Starts the test scheduler and uses the specified virtual times to invoke the factory function, subscribe to the resulting sequence, and dispose the subscription.
|
||
|
*
|
||
|
* @param create Factory method to create an observable sequence.
|
||
|
* @param created Virtual time at which to invoke the factory to create an observable sequence.
|
||
|
* @param subscribed Virtual time at which to subscribe to the created observable sequence.
|
||
|
* @param disposed Virtual time at which to dispose the subscription.
|
||
|
* @return Observer with timestamped recordings of notification messages that were received during the virtual time window when the subscription to the source sequence was active.
|
||
|
*/
|
||
|
TestScheduler.prototype.startWithTiming = function (create, created, subscribed, disposed) {
|
||
|
var observer = this.createObserver(), source, subscription;
|
||
|
|
||
|
this.scheduleAbsoluteWithState(null, created, function () {
|
||
|
source = create();
|
||
|
return disposableEmpty;
|
||
|
});
|
||
|
|
||
|
this.scheduleAbsoluteWithState(null, subscribed, function () {
|
||
|
subscription = source.subscribe(observer);
|
||
|
return disposableEmpty;
|
||
|
});
|
||
|
|
||
|
this.scheduleAbsoluteWithState(null, disposed, function () {
|
||
|
subscription.dispose();
|
||
|
return disposableEmpty;
|
||
|
});
|
||
|
|
||
|
this.start();
|
||
|
|
||
|
return observer;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Starts the test scheduler and uses the specified virtual time to dispose the subscription to the sequence obtained through the factory function.
|
||
|
* Default virtual times are used for factory invocation and sequence subscription.
|
||
|
*
|
||
|
* @param create Factory method to create an observable sequence.
|
||
|
* @param disposed Virtual time at which to dispose the subscription.
|
||
|
* @return Observer with timestamped recordings of notification messages that were received during the virtual time window when the subscription to the source sequence was active.
|
||
|
*/
|
||
|
TestScheduler.prototype.startWithDispose = function (create, disposed) {
|
||
|
return this.startWithTiming(create, ReactiveTest.created, ReactiveTest.subscribed, disposed);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Starts the test scheduler and uses default virtual times to invoke the factory function, to subscribe to the resulting sequence, and to dispose the subscription.
|
||
|
*
|
||
|
* @param create Factory method to create an observable sequence.
|
||
|
* @return Observer with timestamped recordings of notification messages that were received during the virtual time window when the subscription to the source sequence was active.
|
||
|
*/
|
||
|
TestScheduler.prototype.startWithCreate = function (create) {
|
||
|
return this.startWithTiming(create, ReactiveTest.created, ReactiveTest.subscribed, ReactiveTest.disposed);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a hot observable using the specified timestamped notification messages either as an array or arguments.
|
||
|
* @param messages Notifications to surface through the created sequence at their specified absolute virtual times.
|
||
|
* @return Hot observable sequence that can be used to assert the timing of subscriptions and notifications.
|
||
|
*/
|
||
|
TestScheduler.prototype.createHotObservable = function () {
|
||
|
var messages = argsOrArray(arguments, 0);
|
||
|
return new HotObservable(this, messages);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a cold observable using the specified timestamped notification messages either as an array or arguments.
|
||
|
* @param messages Notifications to surface through the created sequence at their specified virtual time offsets from the sequence subscription time.
|
||
|
* @return Cold observable sequence that can be used to assert the timing of subscriptions and notifications.
|
||
|
*/
|
||
|
TestScheduler.prototype.createColdObservable = function () {
|
||
|
var messages = argsOrArray(arguments, 0);
|
||
|
return new ColdObservable(this, messages);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a resolved promise with the given value and ticks
|
||
|
* @param {Number} ticks The absolute time of the resolution.
|
||
|
* @param {Any} value The value to yield at the given tick.
|
||
|
* @returns {MockPromise} A mock Promise which fulfills with the given value.
|
||
|
*/
|
||
|
TestScheduler.prototype.createResolvedPromise = function (ticks, value) {
|
||
|
return new MockPromise(this, [Rx.ReactiveTest.onNext(ticks, value), Rx.ReactiveTest.onCompleted(ticks)]);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a rejected promise with the given reason and ticks
|
||
|
* @param {Number} ticks The absolute time of the resolution.
|
||
|
* @param {Any} reason The reason for rejection to yield at the given tick.
|
||
|
* @returns {MockPromise} A mock Promise which rejects with the given reason.
|
||
|
*/
|
||
|
TestScheduler.prototype.createRejectedPromise = function (ticks, reason) {
|
||
|
return new MockPromise(this, [Rx.ReactiveTest.onError(ticks, reason)]);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates an observer that records received notification messages and timestamps those.
|
||
|
* @return Observer that can be used to assert the timing of received notifications.
|
||
|
*/
|
||
|
TestScheduler.prototype.createObserver = function () {
|
||
|
return new MockObserver(this);
|
||
|
};
|
||
|
|
||
|
return TestScheduler;
|
||
|
})(VirtualTimeScheduler);
|
||
|
|
||
|
return Rx;
|
||
|
}));
|