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
JavaScript
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 };