UNPKG

rxjs-zone-less

Version:

A set of wrappers for RxJS to avoid unnecessary change detection and zone interference in Angular.

240 lines (239 loc) 10.9 kB
import { __extends } from "tslib"; import { immediateProvider } from '../internals/immediateProvider'; import { dateTimestampProvider } from '../internals/date-time-stamp.provider'; import { intervalProvider } from '../internals/intervalProvider'; import { animationFrameProvider } from '../internals/animationFrameProvider'; import { TestScheduler } from 'rxjs/testing'; var RxTestScheduler = /** @class */ (function (_super) { __extends(RxTestScheduler, _super); function RxTestScheduler() { return _super !== null && _super.apply(this, arguments) || this; } RxTestScheduler.prototype.run = function (callback) { var _this = this; try { var ret = _super.prototype.run.call(this, function (helpers) { var animator = _this._createAnimator(); var delegates = _this._createDelegates(); animationFrameProvider.delegate = animator.delegate; intervalProvider.delegate = delegates.interval; immediateProvider.delegate = delegates.immediate; dateTimestampProvider.delegate = _this; var origAnimate = helpers.animate; helpers.animate = function (marbles) { animator.animate(marbles); origAnimate(marbles); }; return callback(helpers); }); return ret; } finally { animationFrameProvider.delegate = undefined; intervalProvider.delegate = undefined; immediateProvider.delegate = undefined; dateTimestampProvider.delegate = undefined; } }; RxTestScheduler.prototype._createAnimator = function () { var _this = this; if (!this.runMode) { throw new Error('animate() must only be used in run mode'); } // The TestScheduler assigns a delegate to the provider that's used for // requestAnimationFrame (rAF). The delegate works in conjunction with the // animate run helper to coordinate the invocation of any rAF callbacks, // that are effected within tests, with the animation frames specified by // the test's author - in the marbles that are passed to the animate run // helper. This allows the test's author to write deterministic tests and // gives the author full control over when - or if - animation frames are // 'painted'. var lastHandle = 0; var map; var delegate = { requestAnimationFrame: function (callback) { if (!map) { throw new Error('animate() was not called within run()'); } var handle = ++lastHandle; map.set(handle, callback); return handle; }, cancelAnimationFrame: function (handle) { if (!map) { throw new Error('animate() was not called within run()'); } map.delete(handle); }, }; var animate = function (marbles) { if (map) { throw new Error('animate() must not be called more than once within run()'); } if (/[|#]/.test(marbles)) { throw new Error('animate() must not complete or error'); } map = new Map(); var messages = TestScheduler.parseMarbles(marbles, undefined, undefined, undefined, true); for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { var message = messages_1[_i]; _this.schedule(function () { var now = _this.now(); // Capture the callbacks within the queue and clear the queue // before enumerating the callbacks, as callbacks might // reschedule themselves. (And, yeah, we're using a Map to represent // the queue, but the values are guaranteed to be returned in // insertion order, so it's all good. Trust me, I've read the docs.) var callbacks = Array.from(map.values()); map.clear(); for (var _i = 0, callbacks_1 = callbacks; _i < callbacks_1.length; _i++) { var callback = callbacks_1[_i]; callback(now); } }, message.frame); } }; return { animate: animate, delegate: delegate }; }; RxTestScheduler.prototype._createDelegates = function () { // When in run mode, the TestScheduler provides alternate implementations // of set/clearImmediate and set/clearInterval. These implementations are // consumed by the scheduler implementations via the providers. This is // done to effect deterministic asap and async scheduler behavior so that // all of the schedulers are testable in 'run mode'. Prior to v7, // delegation occurred at the scheduler level. That is, the asap and // animation frame schedulers were identical in behavior to the async // scheduler. Now, when in run mode, asap actions are prioritized over // async actions and animation frame actions are coordinated using the // animate run helper. var _this = this; var lastHandle = 0; var scheduleLookup = new Map(); var run = function () { // Whenever a scheduled run is executed, it must run a single immediate // or interval action - with immediate actions being prioritized over // interval and timeout actions. var now = _this.now(); var scheduledRecords = Array.from(scheduleLookup.values()); var scheduledRecordsDue = scheduledRecords.filter(function (_a) { var due = _a.due; return due <= now; }); var dueImmediates = scheduledRecordsDue.filter(function (_a) { var type = _a.type; return type === 'immediate'; }); if (dueImmediates.length > 0) { var _a = dueImmediates[0], handle = _a.handle, handler = _a.handler; scheduleLookup.delete(handle); handler(); return; } var dueIntervals = scheduledRecordsDue.filter(function (_a) { var type = _a.type; return type === 'interval'; }); if (dueIntervals.length > 0) { var firstDueInterval = dueIntervals[0]; var duration = firstDueInterval.duration, handler = firstDueInterval.handler; firstDueInterval.due = now + duration; // The interval delegate must behave like setInterval, so run needs to // be rescheduled. This will continue until the clearInterval delegate // unsubscribes and deletes the handle from the map. firstDueInterval.subscription = _this.schedule(run, duration); handler(); return; } var dueTimeouts = scheduledRecordsDue.filter(function (_a) { var type = _a.type; return type === 'timeout'; }); if (dueTimeouts.length > 0) { var _b = dueTimeouts[0], handle = _b.handle, handler = _b.handler; scheduleLookup.delete(handle); handler(); return; } throw new Error('Expected a due immediate or interval'); }; // The following objects are the delegates that replace conventional // runtime implementations with TestScheduler implementations. // // The immediate delegate is depended upon by the asapScheduler. // // The interval delegate is depended upon by the asyncScheduler. // // The timeout delegate is not depended upon by any scheduler, but it's // included here because the onUnhandledError and onStoppedNotification // configuration points use setTimeout to avoid producer interference. It's // inclusion allows for the testing of these configuration points. var immediate = { setImmediate: function (handler) { var handle = ++lastHandle; scheduleLookup.set(handle, { due: _this.now(), duration: 0, handle: handle, handler: handler, subscription: _this.schedule(run, 0), type: 'immediate', }); return handle; }, clearImmediate: function (handle) { var value = scheduleLookup.get(handle); if (value) { value.subscription.unsubscribe(); scheduleLookup.delete(handle); } }, }; var interval = { setInterval: function (handler, duration) { if (duration === void 0) { duration = 0; } var handle = ++lastHandle; scheduleLookup.set(handle, { due: _this.now() + duration, duration: duration, handle: handle, handler: handler, subscription: _this.schedule(run, duration), type: 'interval', }); return handle; }, clearInterval: function (handle) { var value = scheduleLookup.get(handle); if (value) { value.subscription.unsubscribe(); scheduleLookup.delete(handle); } }, }; var timeout = { setTimeout: function (handler, duration) { if (duration === void 0) { duration = 0; } var handle = ++lastHandle; scheduleLookup.set(handle, { due: _this.now() + duration, duration: duration, handle: handle, handler: handler, subscription: _this.schedule(run, duration), type: 'timeout', }); return handle; }, clearTimeout: function (handle) { var value = scheduleLookup.get(handle); if (value) { value.subscription.unsubscribe(); scheduleLookup.delete(handle); } }, }; return { immediate: immediate, interval: interval, timeout: timeout }; }; return RxTestScheduler; }(TestScheduler)); export { RxTestScheduler };