UNPKG

@acutmore/rxjs

Version:

Reactive Extensions for modern JavaScript

308 lines 12.3 kB
import { Observable } from '../Observable'; import { Observer } from '../Observer'; import { Notification } from '../Notification'; import { ColdObservable } from './ColdObservable'; import { HotObservable } from './HotObservable'; import { SubscriptionLog } from './SubscriptionLog'; import { VirtualTimeScheduler, VirtualAction } from '../scheduler/VirtualTimeScheduler'; import { MockPromise } from './MockPromise'; // v4-backwards-compatibility import { ReactiveTest } from './ReactiveTest'; // v4-backwards-compatibility // 750 => 100,001 const defaultMaxFrame = (100 * 1000) + 1; export class TestScheduler extends VirtualTimeScheduler { constructor(assertDeepEqual) { super(VirtualAction, defaultMaxFrame); this.assertDeepEqual = assertDeepEqual; this.hotObservables = []; this.coldObservables = []; this.flushTests = []; } createTime(marbles) { const indexOf = marbles.indexOf('|'); if (indexOf === -1) { throw new Error('marble diagram for time should have a completion marker "|"'); } return indexOf * TestScheduler.frameTimeFactor; } createColdObservable(...args) { if (args.length === 0) { // v4-backwards-compatibility return this.createColdObservable(''); } let messages; if (typeof args[0] === 'string') { const [marbles, values, error] = args; if (marbles.indexOf('^') !== -1) { throw new Error('cold observable cannot have subscription offset "^"'); } if (marbles.indexOf('!') !== -1) { throw new Error('cold observable cannot have unsubscription marker "!"'); } messages = TestScheduler.parseMarbles(marbles, values, error); } else { // v4-backwards-compatibility messages = args; } const cold = new ColdObservable(messages, this); this.coldObservables.push(cold); return cold; } createHotObservable(...args) { if (args.length === 0) { // v4-backwards-compatibility return this.createHotObservable(''); } let messages; if (typeof args[0] === 'string') { const [marbles, values, error] = args; if (marbles.indexOf('!') !== -1) { throw new Error('hot observable cannot have unsubscription marker "!"'); } messages = TestScheduler.parseMarbles(marbles, values, error); } else { // v4-backwards-compatibility messages = args; } const subject = new HotObservable(messages, this); this.hotObservables.push(subject); return subject; } materializeInnerObservable(observable, outerFrame) { const messages = []; observable.subscribe((value) => { messages.push({ frame: this.frame - outerFrame, notification: Notification.createNext(value) }); }, (err) => { messages.push({ frame: this.frame - outerFrame, notification: Notification.createError(err) }); }, () => { messages.push({ frame: this.frame - outerFrame, notification: Notification.createComplete() }); }); return messages; } expectObservable(observable, unsubscriptionMarbles = null) { const actual = []; const flushTest = { actual, ready: false }; const unsubscriptionFrame = TestScheduler .parseMarblesAsSubscriptions(unsubscriptionMarbles).unsubscribedFrame; let subscription; this.schedule(() => { subscription = observable.subscribe(x => { let value = x; // Support Observable-of-Observables if (x instanceof Observable) { value = this.materializeInnerObservable(value, this.frame); } actual.push({ frame: this.frame, notification: Notification.createNext(value) }); }, (err) => { actual.push({ frame: this.frame, notification: Notification.createError(err) }); }, () => { actual.push({ frame: this.frame, notification: Notification.createComplete() }); }); }, 0); if (unsubscriptionFrame !== Number.POSITIVE_INFINITY) { this.schedule(() => subscription.unsubscribe(), unsubscriptionFrame); } this.flushTests.push(flushTest); return { toBe(marbles, values, errorValue) { flushTest.ready = true; flushTest.expected = TestScheduler.parseMarbles(marbles, values, errorValue, true); } }; } expectSubscriptions(actualSubscriptionLogs) { const flushTest = { actual: actualSubscriptionLogs, ready: false }; this.flushTests.push(flushTest); return { toBe(marbles) { const marblesArray = (typeof marbles === 'string') ? [marbles] : marbles; flushTest.ready = true; flushTest.expected = marblesArray.map(marbles => TestScheduler.parseMarblesAsSubscriptions(marbles)); } }; } flush(limit = Number.POSITIVE_INFINITY) { const hotObservables = this.hotObservables; while (hotObservables.length > 0) { hotObservables.shift().setup(); } if (limit === Number.POSITIVE_INFINITY) { super.flush(); const readyFlushTests = this.flushTests.filter(test => test.ready); while (readyFlushTests.length > 0) { const test = readyFlushTests.shift(); this.assertDeepEqual(test.actual, test.expected); } } else { super.limitedFlush(limit); } } static parseMarblesAsSubscriptions(marbles) { if (typeof marbles !== 'string') { return new SubscriptionLog(Number.POSITIVE_INFINITY); } const len = marbles.length; let groupStart = -1; let subscriptionFrame = Number.POSITIVE_INFINITY; let unsubscriptionFrame = Number.POSITIVE_INFINITY; for (let i = 0; i < len; i++) { const frame = i * this.frameTimeFactor; const c = marbles[i]; switch (c) { case '-': case ' ': break; case '(': groupStart = frame; break; case ')': groupStart = -1; break; case '^': if (subscriptionFrame !== Number.POSITIVE_INFINITY) { throw new Error('found a second subscription point \'^\' in a ' + 'subscription marble diagram. There can only be one.'); } subscriptionFrame = groupStart > -1 ? groupStart : frame; break; case '!': if (unsubscriptionFrame !== Number.POSITIVE_INFINITY) { throw new Error('found a second subscription point \'^\' in a ' + 'subscription marble diagram. There can only be one.'); } unsubscriptionFrame = groupStart > -1 ? groupStart : frame; break; default: throw new Error('there can only be \'^\' and \'!\' markers in a ' + 'subscription marble diagram. Found instead \'' + c + '\'.'); } } if (unsubscriptionFrame < 0) { return new SubscriptionLog(subscriptionFrame); } else { return new SubscriptionLog(subscriptionFrame, unsubscriptionFrame); } } static parseMarbles(marbles, values, errorValue, materializeInnerObservables = false) { if (marbles.indexOf('!') !== -1) { throw new Error('conventional marble diagrams cannot have the ' + 'unsubscription marker "!"'); } const len = marbles.length; const testMessages = []; const subIndex = marbles.indexOf('^'); const frameOffset = subIndex === -1 ? 0 : (subIndex * -this.frameTimeFactor); const getValue = typeof values !== 'object' ? (x) => x : (x) => { // Support Observable-of-Observables if (materializeInnerObservables && values[x] instanceof ColdObservable) { return values[x].messages; } return values[x]; }; let groupStart = -1; for (let i = 0; i < len; i++) { const frame = i * this.frameTimeFactor + frameOffset; let notification; const c = marbles[i]; switch (c) { case '-': case ' ': break; case '(': groupStart = frame; break; case ')': groupStart = -1; break; case '|': notification = Notification.createComplete(); break; case '^': break; case '#': notification = Notification.createError(errorValue || 'error'); break; default: notification = Notification.createNext(getValue(c)); break; } if (notification) { testMessages.push({ frame: groupStart > -1 ? groupStart : frame, notification }); } } return testMessages; } // v4-backwards-compatibility createObserver() { const scheduler = this; const messages = []; const obs = Observer.create((v) => { messages.push({ frame: scheduler.now(), notification: Notification.createNext(v) }); }, (err) => { messages.push({ frame: scheduler.now(), notification: Notification.createError(err) }); }, () => { messages.push({ frame: scheduler.now(), notification: Notification.createComplete() }); }); obs.messages = messages; return obs; } // v4-backwards-compatibility startScheduler(factory, settings = {}) { const created = settings.created == null ? ReactiveTest.created : settings.created; const subscribed = settings.subscribed == null ? ReactiveTest.subscribed : settings.subscribed; const disposed = settings.disposed == null ? ReactiveTest.disposed : settings.disposed; const testObserver = this.createObserver(); let subscription; let source; this.scheduleAbsolute(null, created, function () { source = factory(); }); this.scheduleAbsolute(null, subscribed, function () { subscription = source.subscribe(testObserver); }); this.scheduleAbsolute(null, disposed, function () { subscription.dispose(); }); this.start(); return testObserver; } // v4-backwards-compatibility scheduleAbsolute(state, dueTime, action) { const delay = dueTime - this.clock; return super.scheduleAbsolute(state, Math.max(0, delay), action); } // v4-backwards-compatibility advanceBy(time) { this.flush(this.now() + time); } // v4-backwards-compatibility advanceTo(time) { this.flush(time); } // v4-backwards-compatibility createResolvedPromise(time, value) { return new MockPromise(this, time, value, false); } // v4-backwards-compatibility createRejectedPromise(time, rejection) { return new MockPromise(this, time, rejection, true); } // v4-backwards-compatibility scheduleRelative(state, delay, action) { this.schedule(action, delay, state); } // v4-backwards-compatibility stop() { /* noop */ } } // v4-backwards-compatibility TestScheduler.prototype.start = function () { this.flush(); }; //# sourceMappingURL=TestScheduler.js.map