rx.testing.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. // Copyright (c) Microsoft, All rights reserved. See License.txt in the project root for license information.
  2. ;(function (factory) {
  3. var objectTypes = {
  4. 'function': true,
  5. 'object': true
  6. };
  7. function checkGlobal(value) {
  8. return (value && value.Object === Object) ? value : null;
  9. }
  10. var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType) ? exports : null;
  11. var freeModule = (objectTypes[typeof module] && module && !module.nodeType) ? module : null;
  12. var freeGlobal = checkGlobal(freeExports && freeModule && typeof global === 'object' && global);
  13. var freeSelf = checkGlobal(objectTypes[typeof self] && self);
  14. var freeWindow = checkGlobal(objectTypes[typeof window] && window);
  15. var moduleExports = (freeModule && freeModule.exports === freeExports) ? freeExports : null;
  16. var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
  17. var root = freeGlobal || ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) || freeSelf || thisGlobal || Function('return this')();
  18. // Because of build optimizers
  19. if (typeof define === 'function' && define.amd) {
  20. define(['./rx.virtualtime', 'exports'], function (Rx, exports) {
  21. root.Rx = factory(root, exports, Rx);
  22. return root.Rx;
  23. });
  24. } else if (typeof module === 'object' && module && module.exports === freeExports) {
  25. module.exports = factory(root, module.exports, require('./rx'));
  26. } else {
  27. root.Rx = factory(root, {}, root.Rx);
  28. }
  29. }.call(this, function (root, exp, Rx, undefined) {
  30. // Defaults
  31. var Observer = Rx.Observer,
  32. Observable = Rx.Observable,
  33. Notification = Rx.Notification,
  34. VirtualTimeScheduler = Rx.VirtualTimeScheduler,
  35. Disposable = Rx.Disposable,
  36. disposableEmpty = Disposable.empty,
  37. disposableCreate = Disposable.create,
  38. CompositeDisposable = Rx.CompositeDisposable,
  39. inherits = Rx.internals.inherits,
  40. defaultComparer = Rx.internals.isEqual;
  41. function OnNextPredicate(predicate) {
  42. this.predicate = predicate;
  43. }
  44. OnNextPredicate.prototype.equals = function (other) {
  45. if (other === this) { return true; }
  46. if (other == null) { return false; }
  47. if (other.kind !== 'N') { return false; }
  48. return this.predicate(other.value);
  49. };
  50. function OnErrorPredicate(predicate) {
  51. this.predicate = predicate;
  52. }
  53. OnErrorPredicate.prototype.equals = function (other) {
  54. if (other === this) { return true; }
  55. if (other == null) { return false; }
  56. if (other.kind !== 'E') { return false; }
  57. return this.predicate(other.error);
  58. };
  59. var ReactiveTest = Rx.ReactiveTest = {
  60. /** Default virtual time used for creation of observable sequences in unit tests. */
  61. created: 100,
  62. /** Default virtual time used to subscribe to observable sequences in unit tests. */
  63. subscribed: 200,
  64. /** Default virtual time used to dispose subscriptions in unit tests. */
  65. disposed: 1000,
  66. /**
  67. * Factory method for an OnNext notification record at a given time with a given value or a predicate function.
  68. *
  69. * 1 - ReactiveTest.onNext(200, 42);
  70. * 2 - ReactiveTest.onNext(200, function (x) { return x.length == 2; });
  71. *
  72. * @param ticks Recorded virtual time the OnNext notification occurs.
  73. * @param value Recorded value stored in the OnNext notification or a predicate.
  74. * @return Recorded OnNext notification.
  75. */
  76. onNext: function (ticks, value) {
  77. return typeof value === 'function' ?
  78. new Recorded(ticks, new OnNextPredicate(value)) :
  79. new Recorded(ticks, Notification.createOnNext(value));
  80. },
  81. /**
  82. * Factory method for an OnError notification record at a given time with a given error.
  83. *
  84. * 1 - ReactiveTest.onNext(200, new Error('error'));
  85. * 2 - ReactiveTest.onNext(200, function (e) { return e.message === 'error'; });
  86. *
  87. * @param ticks Recorded virtual time the OnError notification occurs.
  88. * @param exception Recorded exception stored in the OnError notification.
  89. * @return Recorded OnError notification.
  90. */
  91. onError: function (ticks, error) {
  92. return typeof error === 'function' ?
  93. new Recorded(ticks, new OnErrorPredicate(error)) :
  94. new Recorded(ticks, Notification.createOnError(error));
  95. },
  96. /**
  97. * Factory method for an OnCompleted notification record at a given time.
  98. *
  99. * @param ticks Recorded virtual time the OnCompleted notification occurs.
  100. * @return Recorded OnCompleted notification.
  101. */
  102. onCompleted: function (ticks) {
  103. return new Recorded(ticks, Notification.createOnCompleted());
  104. },
  105. /**
  106. * Factory method for a subscription record based on a given subscription and disposal time.
  107. *
  108. * @param start Virtual time indicating when the subscription was created.
  109. * @param end Virtual time indicating when the subscription was disposed.
  110. * @return Subscription object.
  111. */
  112. subscribe: function (start, end) {
  113. return new Subscription(start, end);
  114. }
  115. };
  116. /**
  117. * Creates a new object recording the production of the specified value at the given virtual time.
  118. *
  119. * @constructor
  120. * @param {Number} time Virtual time the value was produced on.
  121. * @param {Mixed} value Value that was produced.
  122. * @param {Function} comparer An optional comparer.
  123. */
  124. var Recorded = Rx.Recorded = function (time, value, comparer) {
  125. this.time = time;
  126. this.value = value;
  127. this.comparer = comparer || defaultComparer;
  128. };
  129. /**
  130. * Checks whether the given recorded object is equal to the current instance.
  131. *
  132. * @param {Recorded} other Recorded object to check for equality.
  133. * @returns {Boolean} true if both objects are equal; false otherwise.
  134. */
  135. Recorded.prototype.equals = function (other) {
  136. return this.time === other.time && this.comparer(this.value, other.value);
  137. };
  138. /**
  139. * Returns a string representation of the current Recorded value.
  140. *
  141. * @returns {String} String representation of the current Recorded value.
  142. */
  143. Recorded.prototype.toString = function () {
  144. return this.value.toString() + '@' + this.time;
  145. };
  146. /**
  147. * Creates a new subscription object with the given virtual subscription and unsubscription time.
  148. *
  149. * @constructor
  150. * @param {Number} subscribe Virtual time at which the subscription occurred.
  151. * @param {Number} unsubscribe Virtual time at which the unsubscription occurred.
  152. */
  153. var Subscription = Rx.Subscription = function (start, end) {
  154. this.subscribe = start;
  155. this.unsubscribe = end || Number.MAX_VALUE;
  156. };
  157. /**
  158. * Checks whether the given subscription is equal to the current instance.
  159. * @param other Subscription object to check for equality.
  160. * @returns {Boolean} true if both objects are equal; false otherwise.
  161. */
  162. Subscription.prototype.equals = function (other) {
  163. return this.subscribe === other.subscribe && this.unsubscribe === other.unsubscribe;
  164. };
  165. /**
  166. * Returns a string representation of the current Subscription value.
  167. * @returns {String} String representation of the current Subscription value.
  168. */
  169. Subscription.prototype.toString = function () {
  170. return '(' + this.subscribe + ', ' + (this.unsubscribe === Number.MAX_VALUE ? 'Infinite' : this.unsubscribe) + ')';
  171. };
  172. var MockDisposable = Rx.MockDisposable = function (scheduler) {
  173. this.scheduler = scheduler;
  174. this.disposes = [];
  175. this.disposes.push(this.scheduler.clock);
  176. };
  177. MockDisposable.prototype.dispose = function () {
  178. this.disposes.push(this.scheduler.clock);
  179. };
  180. var MockObserver = (function (__super__) {
  181. inherits(MockObserver, __super__);
  182. function MockObserver(scheduler) {
  183. __super__.call(this);
  184. this.scheduler = scheduler;
  185. this.messages = [];
  186. }
  187. var MockObserverPrototype = MockObserver.prototype;
  188. MockObserverPrototype.onNext = function (value) {
  189. this.messages.push(new Recorded(this.scheduler.clock, Notification.createOnNext(value)));
  190. };
  191. MockObserverPrototype.onError = function (e) {
  192. this.messages.push(new Recorded(this.scheduler.clock, Notification.createOnError(e)));
  193. };
  194. MockObserverPrototype.onCompleted = function () {
  195. this.messages.push(new Recorded(this.scheduler.clock, Notification.createOnCompleted()));
  196. };
  197. return MockObserver;
  198. })(Observer);
  199. function MockPromise(scheduler, messages) {
  200. var self = this;
  201. this.scheduler = scheduler;
  202. this.messages = messages;
  203. this.subscriptions = [];
  204. this.observers = [];
  205. for (var i = 0, len = this.messages.length; i < len; i++) {
  206. var message = this.messages[i],
  207. notification = message.value;
  208. (function (innerNotification) {
  209. scheduler.scheduleAbsolute(null, message.time, function () {
  210. var obs = self.observers.slice(0);
  211. for (var j = 0, jLen = obs.length; j < jLen; j++) {
  212. innerNotification.accept(obs[j]);
  213. }
  214. return disposableEmpty;
  215. });
  216. })(notification);
  217. }
  218. }
  219. MockPromise.prototype.then = function (onResolved, onRejected) {
  220. var self = this;
  221. this.subscriptions.push(new Subscription(this.scheduler.clock));
  222. var index = this.subscriptions.length - 1;
  223. var newPromise;
  224. var observer = Rx.Observer.create(
  225. function (x) {
  226. var retValue = onResolved(x);
  227. if (retValue && typeof retValue.then === 'function') {
  228. newPromise = retValue;
  229. } else {
  230. var ticks = self.scheduler.clock;
  231. newPromise = new MockPromise(self.scheduler, [Rx.ReactiveTest.onNext(ticks, undefined), Rx.ReactiveTest.onCompleted(ticks)]);
  232. }
  233. var idx = self.observers.indexOf(observer);
  234. self.observers.splice(idx, 1);
  235. self.subscriptions[index] = new Subscription(self.subscriptions[index].subscribe, self.scheduler.clock);
  236. },
  237. function (err) {
  238. onRejected(err);
  239. var idx = self.observers.indexOf(observer);
  240. self.observers.splice(idx, 1);
  241. self.subscriptions[index] = new Subscription(self.subscriptions[index].subscribe, self.scheduler.clock);
  242. }
  243. );
  244. this.observers.push(observer);
  245. return newPromise || new MockPromise(this.scheduler, this.messages);
  246. };
  247. var HotObservable = (function (__super__) {
  248. inherits(HotObservable, __super__);
  249. function HotObservable(scheduler, messages) {
  250. __super__.call(this);
  251. var message, notification, observable = this;
  252. this.scheduler = scheduler;
  253. this.messages = messages;
  254. this.subscriptions = [];
  255. this.observers = [];
  256. for (var i = 0, len = this.messages.length; i < len; i++) {
  257. message = this.messages[i];
  258. notification = message.value;
  259. (function (innerNotification) {
  260. scheduler.scheduleAbsolute(null, message.time, function () {
  261. var obs = observable.observers.slice(0);
  262. for (var j = 0, jLen = obs.length; j < jLen; j++) {
  263. innerNotification.accept(obs[j]);
  264. }
  265. return disposableEmpty;
  266. });
  267. })(notification);
  268. }
  269. }
  270. HotObservable.prototype._subscribe = function (o) {
  271. var observable = this;
  272. this.observers.push(o);
  273. this.subscriptions.push(new Subscription(this.scheduler.clock));
  274. var index = this.subscriptions.length - 1;
  275. return disposableCreate(function () {
  276. var idx = observable.observers.indexOf(o);
  277. observable.observers.splice(idx, 1);
  278. observable.subscriptions[index] = new Subscription(observable.subscriptions[index].subscribe, observable.scheduler.clock);
  279. });
  280. };
  281. return HotObservable;
  282. })(Observable);
  283. var ColdObservable = (function (__super__) {
  284. inherits(ColdObservable, __super__);
  285. function ColdObservable(scheduler, messages) {
  286. __super__.call(this);
  287. this.scheduler = scheduler;
  288. this.messages = messages;
  289. this.subscriptions = [];
  290. }
  291. ColdObservable.prototype._subscribe = function (o) {
  292. var message, notification, observable = this;
  293. this.subscriptions.push(new Subscription(this.scheduler.clock));
  294. var index = this.subscriptions.length - 1;
  295. var d = new CompositeDisposable();
  296. for (var i = 0, len = this.messages.length; i < len; i++) {
  297. message = this.messages[i];
  298. notification = message.value;
  299. (function (innerNotification) {
  300. d.add(observable.scheduler.scheduleRelative(null, message.time, function () {
  301. innerNotification.accept(o);
  302. return disposableEmpty;
  303. }));
  304. })(notification);
  305. }
  306. return disposableCreate(function () {
  307. observable.subscriptions[index] = new Subscription(observable.subscriptions[index].subscribe, observable.scheduler.clock);
  308. d.dispose();
  309. });
  310. };
  311. return ColdObservable;
  312. })(Observable);
  313. /** Virtual time scheduler used for testing applications and libraries built using Reactive Extensions. */
  314. Rx.TestScheduler = (function (__super__) {
  315. inherits(TestScheduler, __super__);
  316. function baseComparer(x, y) {
  317. return x > y ? 1 : (x < y ? -1 : 0);
  318. }
  319. function TestScheduler() {
  320. __super__.call(this, 0, baseComparer);
  321. }
  322. /**
  323. * Schedules an action to be executed at the specified virtual time.
  324. *
  325. * @param state State passed to the action to be executed.
  326. * @param dueTime Absolute virtual time at which to execute the action.
  327. * @param action Action to be executed.
  328. * @return Disposable object used to cancel the scheduled action (best effort).
  329. */
  330. TestScheduler.prototype.scheduleAbsolute = function (state, dueTime, action) {
  331. dueTime <= this.clock && (dueTime = this.clock + 1);
  332. return __super__.prototype.scheduleAbsolute.call(this, state, dueTime, action);
  333. };
  334. /**
  335. * Adds a relative virtual time to an absolute virtual time value.
  336. *
  337. * @param absolute Absolute virtual time value.
  338. * @param relative Relative virtual time value to add.
  339. * @return Resulting absolute virtual time sum value.
  340. */
  341. TestScheduler.prototype.add = function (absolute, relative) {
  342. return absolute + relative;
  343. };
  344. /**
  345. * Converts the absolute virtual time value to a DateTimeOffset value.
  346. *
  347. * @param absolute Absolute virtual time value to convert.
  348. * @return Corresponding DateTimeOffset value.
  349. */
  350. TestScheduler.prototype.toAbsoluteTime = function (absolute) {
  351. return new Date(absolute).getTime();
  352. };
  353. /**
  354. * Converts the TimeSpan value to a relative virtual time value.
  355. *
  356. * @param timeSpan TimeSpan value to convert.
  357. * @return Corresponding relative virtual time value.
  358. */
  359. TestScheduler.prototype.toRelativeTime = function (timeSpan) {
  360. return timeSpan;
  361. };
  362. /**
  363. * Starts the test scheduler and uses the specified virtual times to invoke the factory function, subscribe to the resulting sequence, and dispose the subscription.
  364. *
  365. * @param create Factory method to create an observable sequence.
  366. * @param created Virtual time at which to invoke the factory to create an observable sequence.
  367. * @param subscribed Virtual time at which to subscribe to the created observable sequence.
  368. * @param disposed Virtual time at which to dispose the subscription.
  369. * @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.
  370. */
  371. TestScheduler.prototype.startScheduler = function (createFn, settings) {
  372. settings || (settings = {});
  373. settings.created == null && (settings.created = ReactiveTest.created);
  374. settings.subscribed == null && (settings.subscribed = ReactiveTest.subscribed);
  375. settings.disposed == null && (settings.disposed = ReactiveTest.disposed);
  376. var observer = this.createObserver(), source, subscription;
  377. this.scheduleAbsolute(null, settings.created, function () {
  378. source = createFn();
  379. return disposableEmpty;
  380. });
  381. this.scheduleAbsolute(null, settings.subscribed, function () {
  382. subscription = source.subscribe(observer);
  383. return disposableEmpty;
  384. });
  385. this.scheduleAbsolute(null, settings.disposed, function () {
  386. subscription.dispose();
  387. return disposableEmpty;
  388. });
  389. this.start();
  390. return observer;
  391. };
  392. /**
  393. * Creates a hot observable using the specified timestamped notification messages either as an array or arguments.
  394. * @param messages Notifications to surface through the created sequence at their specified absolute virtual times.
  395. * @return Hot observable sequence that can be used to assert the timing of subscriptions and notifications.
  396. */
  397. TestScheduler.prototype.createHotObservable = function () {
  398. var len = arguments.length, args;
  399. if (Array.isArray(arguments[0])) {
  400. args = arguments[0];
  401. } else {
  402. args = new Array(len);
  403. for (var i = 0; i < len; i++) { args[i] = arguments[i]; }
  404. }
  405. return new HotObservable(this, args);
  406. };
  407. /**
  408. * Creates a cold observable using the specified timestamped notification messages either as an array or arguments.
  409. * @param messages Notifications to surface through the created sequence at their specified virtual time offsets from the sequence subscription time.
  410. * @return Cold observable sequence that can be used to assert the timing of subscriptions and notifications.
  411. */
  412. TestScheduler.prototype.createColdObservable = function () {
  413. var len = arguments.length, args;
  414. if (Array.isArray(arguments[0])) {
  415. args = arguments[0];
  416. } else {
  417. args = new Array(len);
  418. for (var i = 0; i < len; i++) { args[i] = arguments[i]; }
  419. }
  420. return new ColdObservable(this, args);
  421. };
  422. /**
  423. * Creates a resolved promise with the given value and ticks
  424. * @param {Number} ticks The absolute time of the resolution.
  425. * @param {Any} value The value to yield at the given tick.
  426. * @returns {MockPromise} A mock Promise which fulfills with the given value.
  427. */
  428. TestScheduler.prototype.createResolvedPromise = function (ticks, value) {
  429. return new MockPromise(this, [Rx.ReactiveTest.onNext(ticks, value), Rx.ReactiveTest.onCompleted(ticks)]);
  430. };
  431. /**
  432. * Creates a rejected promise with the given reason and ticks
  433. * @param {Number} ticks The absolute time of the resolution.
  434. * @param {Any} reason The reason for rejection to yield at the given tick.
  435. * @returns {MockPromise} A mock Promise which rejects with the given reason.
  436. */
  437. TestScheduler.prototype.createRejectedPromise = function (ticks, reason) {
  438. return new MockPromise(this, [Rx.ReactiveTest.onError(ticks, reason)]);
  439. };
  440. /**
  441. * Creates an observer that records received notification messages and timestamps those.
  442. * @return Observer that can be used to assert the timing of received notifications.
  443. */
  444. TestScheduler.prototype.createObserver = function () {
  445. return new MockObserver(this);
  446. };
  447. return TestScheduler;
  448. })(VirtualTimeScheduler);
  449. return Rx;
  450. }));