Buckets:
| import { Observable } from '../Observable'; | |
| import { ColdObservable } from './ColdObservable'; | |
| import { HotObservable } from './HotObservable'; | |
| import { SubscriptionLog } from './SubscriptionLog'; | |
| import { VirtualTimeScheduler, VirtualAction } from '../scheduler/VirtualTimeScheduler'; | |
| import { COMPLETE_NOTIFICATION, errorNotification, nextNotification } from '../NotificationFactories'; | |
| import { dateTimestampProvider } from '../scheduler/dateTimestampProvider'; | |
| import { performanceTimestampProvider } from '../scheduler/performanceTimestampProvider'; | |
| import { animationFrameProvider } from '../scheduler/animationFrameProvider'; | |
| import { immediateProvider } from '../scheduler/immediateProvider'; | |
| import { intervalProvider } from '../scheduler/intervalProvider'; | |
| import { timeoutProvider } from '../scheduler/timeoutProvider'; | |
| const defaultMaxFrame = 750; | |
| export class TestScheduler extends VirtualTimeScheduler { | |
| constructor(assertDeepEqual) { | |
| super(VirtualAction, defaultMaxFrame); | |
| this.assertDeepEqual = assertDeepEqual; | |
| this.hotObservables = []; | |
| this.coldObservables = []; | |
| this.flushTests = []; | |
| this.runMode = false; | |
| } | |
| createTime(marbles) { | |
| const indexOf = this.runMode ? marbles.trim().indexOf('|') : marbles.indexOf('|'); | |
| if (indexOf === -1) { | |
| throw new Error('marble diagram for time should have a completion marker "|"'); | |
| } | |
| return indexOf * TestScheduler.frameTimeFactor; | |
| } | |
| createColdObservable(marbles, values, error) { | |
| 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 "!"'); | |
| } | |
| const messages = TestScheduler.parseMarbles(marbles, values, error, undefined, this.runMode); | |
| const cold = new ColdObservable(messages, this); | |
| this.coldObservables.push(cold); | |
| return cold; | |
| } | |
| createHotObservable(marbles, values, error) { | |
| if (marbles.indexOf('!') !== -1) { | |
| throw new Error('hot observable cannot have unsubscription marker "!"'); | |
| } | |
| const messages = TestScheduler.parseMarbles(marbles, values, error, undefined, this.runMode); | |
| const subject = new HotObservable(messages, this); | |
| this.hotObservables.push(subject); | |
| return subject; | |
| } | |
| materializeInnerObservable(observable, outerFrame) { | |
| const messages = []; | |
| observable.subscribe({ | |
| next: (value) => { | |
| messages.push({ frame: this.frame - outerFrame, notification: nextNotification(value) }); | |
| }, | |
| error: (error) => { | |
| messages.push({ frame: this.frame - outerFrame, notification: errorNotification(error) }); | |
| }, | |
| complete: () => { | |
| messages.push({ frame: this.frame - outerFrame, notification: COMPLETE_NOTIFICATION }); | |
| }, | |
| }); | |
| return messages; | |
| } | |
| expectObservable(observable, subscriptionMarbles = null) { | |
| const actual = []; | |
| const flushTest = { actual, ready: false }; | |
| const subscriptionParsed = TestScheduler.parseMarblesAsSubscriptions(subscriptionMarbles, this.runMode); | |
| const subscriptionFrame = subscriptionParsed.subscribedFrame === Infinity ? 0 : subscriptionParsed.subscribedFrame; | |
| const unsubscriptionFrame = subscriptionParsed.unsubscribedFrame; | |
| let subscription; | |
| this.schedule(() => { | |
| subscription = observable.subscribe({ | |
| next: (x) => { | |
| const value = x instanceof Observable ? this.materializeInnerObservable(x, this.frame) : x; | |
| actual.push({ frame: this.frame, notification: nextNotification(value) }); | |
| }, | |
| error: (error) => { | |
| actual.push({ frame: this.frame, notification: errorNotification(error) }); | |
| }, | |
| complete: () => { | |
| actual.push({ frame: this.frame, notification: COMPLETE_NOTIFICATION }); | |
| }, | |
| }); | |
| }, subscriptionFrame); | |
| if (unsubscriptionFrame !== Infinity) { | |
| this.schedule(() => subscription.unsubscribe(), unsubscriptionFrame); | |
| } | |
| this.flushTests.push(flushTest); | |
| const { runMode } = this; | |
| return { | |
| toBe(marbles, values, errorValue) { | |
| flushTest.ready = true; | |
| flushTest.expected = TestScheduler.parseMarbles(marbles, values, errorValue, true, runMode); | |
| }, | |
| toEqual: (other) => { | |
| flushTest.ready = true; | |
| flushTest.expected = []; | |
| this.schedule(() => { | |
| subscription = other.subscribe({ | |
| next: (x) => { | |
| const value = x instanceof Observable ? this.materializeInnerObservable(x, this.frame) : x; | |
| flushTest.expected.push({ frame: this.frame, notification: nextNotification(value) }); | |
| }, | |
| error: (error) => { | |
| flushTest.expected.push({ frame: this.frame, notification: errorNotification(error) }); | |
| }, | |
| complete: () => { | |
| flushTest.expected.push({ frame: this.frame, notification: COMPLETE_NOTIFICATION }); | |
| }, | |
| }); | |
| }, subscriptionFrame); | |
| }, | |
| }; | |
| } | |
| expectSubscriptions(actualSubscriptionLogs) { | |
| const flushTest = { actual: actualSubscriptionLogs, ready: false }; | |
| this.flushTests.push(flushTest); | |
| const { runMode } = this; | |
| return { | |
| toBe(marblesOrMarblesArray) { | |
| const marblesArray = typeof marblesOrMarblesArray === 'string' ? [marblesOrMarblesArray] : marblesOrMarblesArray; | |
| flushTest.ready = true; | |
| flushTest.expected = marblesArray | |
| .map((marbles) => TestScheduler.parseMarblesAsSubscriptions(marbles, runMode)) | |
| .filter((marbles) => marbles.subscribedFrame !== Infinity); | |
| }, | |
| }; | |
| } | |
| flush() { | |
| const hotObservables = this.hotObservables; | |
| while (hotObservables.length > 0) { | |
| hotObservables.shift().setup(); | |
| } | |
| super.flush(); | |
| this.flushTests = this.flushTests.filter((test) => { | |
| if (test.ready) { | |
| this.assertDeepEqual(test.actual, test.expected); | |
| return false; | |
| } | |
| return true; | |
| }); | |
| } | |
| static parseMarblesAsSubscriptions(marbles, runMode = false) { | |
| if (typeof marbles !== 'string') { | |
| return new SubscriptionLog(Infinity); | |
| } | |
| const characters = [...marbles]; | |
| const len = characters.length; | |
| let groupStart = -1; | |
| let subscriptionFrame = Infinity; | |
| let unsubscriptionFrame = Infinity; | |
| let frame = 0; | |
| for (let i = 0; i < len; i++) { | |
| let nextFrame = frame; | |
| const advanceFrameBy = (count) => { | |
| nextFrame += count * this.frameTimeFactor; | |
| }; | |
| const c = characters[i]; | |
| switch (c) { | |
| case ' ': | |
| if (!runMode) { | |
| advanceFrameBy(1); | |
| } | |
| break; | |
| case '-': | |
| advanceFrameBy(1); | |
| break; | |
| case '(': | |
| groupStart = frame; | |
| advanceFrameBy(1); | |
| break; | |
| case ')': | |
| groupStart = -1; | |
| advanceFrameBy(1); | |
| break; | |
| case '^': | |
| if (subscriptionFrame !== Infinity) { | |
| throw new Error("found a second subscription point '^' in a " + 'subscription marble diagram. There can only be one.'); | |
| } | |
| subscriptionFrame = groupStart > -1 ? groupStart : frame; | |
| advanceFrameBy(1); | |
| break; | |
| case '!': | |
| if (unsubscriptionFrame !== Infinity) { | |
| throw new Error("found a second unsubscription point '!' in a " + 'subscription marble diagram. There can only be one.'); | |
| } | |
| unsubscriptionFrame = groupStart > -1 ? groupStart : frame; | |
| break; | |
| default: | |
| if (runMode && c.match(/^[0-9]$/)) { | |
| if (i === 0 || characters[i - 1] === ' ') { | |
| const buffer = characters.slice(i).join(''); | |
| const match = buffer.match(/^([0-9]+(?:\.[0-9]+)?)(ms|s|m) /); | |
| if (match) { | |
| i += match[0].length - 1; | |
| const duration = parseFloat(match[1]); | |
| const unit = match[2]; | |
| let durationInMs; | |
| switch (unit) { | |
| case 'ms': | |
| durationInMs = duration; | |
| break; | |
| case 's': | |
| durationInMs = duration * 1000; | |
| break; | |
| case 'm': | |
| durationInMs = duration * 1000 * 60; | |
| break; | |
| default: | |
| break; | |
| } | |
| advanceFrameBy(durationInMs / this.frameTimeFactor); | |
| break; | |
| } | |
| } | |
| } | |
| throw new Error("there can only be '^' and '!' markers in a " + "subscription marble diagram. Found instead '" + c + "'."); | |
| } | |
| frame = nextFrame; | |
| } | |
| if (unsubscriptionFrame < 0) { | |
| return new SubscriptionLog(subscriptionFrame); | |
| } | |
| else { | |
| return new SubscriptionLog(subscriptionFrame, unsubscriptionFrame); | |
| } | |
| } | |
| static parseMarbles(marbles, values, errorValue, materializeInnerObservables = false, runMode = false) { | |
| if (marbles.indexOf('!') !== -1) { | |
| throw new Error('conventional marble diagrams cannot have the ' + 'unsubscription marker "!"'); | |
| } | |
| const characters = [...marbles]; | |
| const len = characters.length; | |
| const testMessages = []; | |
| const subIndex = runMode ? marbles.replace(/^[ ]+/, '').indexOf('^') : marbles.indexOf('^'); | |
| let frame = subIndex === -1 ? 0 : subIndex * -this.frameTimeFactor; | |
| const getValue = typeof values !== 'object' | |
| ? (x) => x | |
| : (x) => { | |
| if (materializeInnerObservables && values[x] instanceof ColdObservable) { | |
| return values[x].messages; | |
| } | |
| return values[x]; | |
| }; | |
| let groupStart = -1; | |
| for (let i = 0; i < len; i++) { | |
| let nextFrame = frame; | |
| const advanceFrameBy = (count) => { | |
| nextFrame += count * this.frameTimeFactor; | |
| }; | |
| let notification; | |
| const c = characters[i]; | |
| switch (c) { | |
| case ' ': | |
| if (!runMode) { | |
| advanceFrameBy(1); | |
| } | |
| break; | |
| case '-': | |
| advanceFrameBy(1); | |
| break; | |
| case '(': | |
| groupStart = frame; | |
| advanceFrameBy(1); | |
| break; | |
| case ')': | |
| groupStart = -1; | |
| advanceFrameBy(1); | |
| break; | |
| case '|': | |
| notification = COMPLETE_NOTIFICATION; | |
| advanceFrameBy(1); | |
| break; | |
| case '^': | |
| advanceFrameBy(1); | |
| break; | |
| case '#': | |
| notification = errorNotification(errorValue || 'error'); | |
| advanceFrameBy(1); | |
| break; | |
| default: | |
| if (runMode && c.match(/^[0-9]$/)) { | |
| if (i === 0 || characters[i - 1] === ' ') { | |
| const buffer = characters.slice(i).join(''); | |
| const match = buffer.match(/^([0-9]+(?:\.[0-9]+)?)(ms|s|m) /); | |
| if (match) { | |
| i += match[0].length - 1; | |
| const duration = parseFloat(match[1]); | |
| const unit = match[2]; | |
| let durationInMs; | |
| switch (unit) { | |
| case 'ms': | |
| durationInMs = duration; | |
| break; | |
| case 's': | |
| durationInMs = duration * 1000; | |
| break; | |
| case 'm': | |
| durationInMs = duration * 1000 * 60; | |
| break; | |
| default: | |
| break; | |
| } | |
| advanceFrameBy(durationInMs / this.frameTimeFactor); | |
| break; | |
| } | |
| } | |
| } | |
| notification = nextNotification(getValue(c)); | |
| advanceFrameBy(1); | |
| break; | |
| } | |
| if (notification) { | |
| testMessages.push({ frame: groupStart > -1 ? groupStart : frame, notification }); | |
| } | |
| frame = nextFrame; | |
| } | |
| return testMessages; | |
| } | |
| createAnimator() { | |
| if (!this.runMode) { | |
| throw new Error('animate() must only be used in run mode'); | |
| } | |
| let lastHandle = 0; | |
| let map; | |
| const delegate = { | |
| requestAnimationFrame(callback) { | |
| if (!map) { | |
| throw new Error('animate() was not called within run()'); | |
| } | |
| const handle = ++lastHandle; | |
| map.set(handle, callback); | |
| return handle; | |
| }, | |
| cancelAnimationFrame(handle) { | |
| if (!map) { | |
| throw new Error('animate() was not called within run()'); | |
| } | |
| map.delete(handle); | |
| }, | |
| }; | |
| const animate = (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(); | |
| const messages = TestScheduler.parseMarbles(marbles, undefined, undefined, undefined, true); | |
| for (const message of messages) { | |
| this.schedule(() => { | |
| const now = this.now(); | |
| const callbacks = Array.from(map.values()); | |
| map.clear(); | |
| for (const callback of callbacks) { | |
| callback(now); | |
| } | |
| }, message.frame); | |
| } | |
| }; | |
| return { animate, delegate }; | |
| } | |
| createDelegates() { | |
| let lastHandle = 0; | |
| const scheduleLookup = new Map(); | |
| const run = () => { | |
| const now = this.now(); | |
| const scheduledRecords = Array.from(scheduleLookup.values()); | |
| const scheduledRecordsDue = scheduledRecords.filter(({ due }) => due <= now); | |
| const dueImmediates = scheduledRecordsDue.filter(({ type }) => type === 'immediate'); | |
| if (dueImmediates.length > 0) { | |
| const { handle, handler } = dueImmediates[0]; | |
| scheduleLookup.delete(handle); | |
| handler(); | |
| return; | |
| } | |
| const dueIntervals = scheduledRecordsDue.filter(({ type }) => type === 'interval'); | |
| if (dueIntervals.length > 0) { | |
| const firstDueInterval = dueIntervals[0]; | |
| const { duration, handler } = firstDueInterval; | |
| firstDueInterval.due = now + duration; | |
| firstDueInterval.subscription = this.schedule(run, duration); | |
| handler(); | |
| return; | |
| } | |
| const dueTimeouts = scheduledRecordsDue.filter(({ type }) => type === 'timeout'); | |
| if (dueTimeouts.length > 0) { | |
| const { handle, handler } = dueTimeouts[0]; | |
| scheduleLookup.delete(handle); | |
| handler(); | |
| return; | |
| } | |
| throw new Error('Expected a due immediate or interval'); | |
| }; | |
| const immediate = { | |
| setImmediate: (handler) => { | |
| const handle = ++lastHandle; | |
| scheduleLookup.set(handle, { | |
| due: this.now(), | |
| duration: 0, | |
| handle, | |
| handler, | |
| subscription: this.schedule(run, 0), | |
| type: 'immediate', | |
| }); | |
| return handle; | |
| }, | |
| clearImmediate: (handle) => { | |
| const value = scheduleLookup.get(handle); | |
| if (value) { | |
| value.subscription.unsubscribe(); | |
| scheduleLookup.delete(handle); | |
| } | |
| }, | |
| }; | |
| const interval = { | |
| setInterval: (handler, duration = 0) => { | |
| const handle = ++lastHandle; | |
| scheduleLookup.set(handle, { | |
| due: this.now() + duration, | |
| duration, | |
| handle, | |
| handler, | |
| subscription: this.schedule(run, duration), | |
| type: 'interval', | |
| }); | |
| return handle; | |
| }, | |
| clearInterval: (handle) => { | |
| const value = scheduleLookup.get(handle); | |
| if (value) { | |
| value.subscription.unsubscribe(); | |
| scheduleLookup.delete(handle); | |
| } | |
| }, | |
| }; | |
| const timeout = { | |
| setTimeout: (handler, duration = 0) => { | |
| const handle = ++lastHandle; | |
| scheduleLookup.set(handle, { | |
| due: this.now() + duration, | |
| duration, | |
| handle, | |
| handler, | |
| subscription: this.schedule(run, duration), | |
| type: 'timeout', | |
| }); | |
| return handle; | |
| }, | |
| clearTimeout: (handle) => { | |
| const value = scheduleLookup.get(handle); | |
| if (value) { | |
| value.subscription.unsubscribe(); | |
| scheduleLookup.delete(handle); | |
| } | |
| }, | |
| }; | |
| return { immediate, interval, timeout }; | |
| } | |
| run(callback) { | |
| const prevFrameTimeFactor = TestScheduler.frameTimeFactor; | |
| const prevMaxFrames = this.maxFrames; | |
| TestScheduler.frameTimeFactor = 1; | |
| this.maxFrames = Infinity; | |
| this.runMode = true; | |
| const animator = this.createAnimator(); | |
| const delegates = this.createDelegates(); | |
| animationFrameProvider.delegate = animator.delegate; | |
| dateTimestampProvider.delegate = this; | |
| immediateProvider.delegate = delegates.immediate; | |
| intervalProvider.delegate = delegates.interval; | |
| timeoutProvider.delegate = delegates.timeout; | |
| performanceTimestampProvider.delegate = this; | |
| const helpers = { | |
| cold: this.createColdObservable.bind(this), | |
| hot: this.createHotObservable.bind(this), | |
| flush: this.flush.bind(this), | |
| time: this.createTime.bind(this), | |
| expectObservable: this.expectObservable.bind(this), | |
| expectSubscriptions: this.expectSubscriptions.bind(this), | |
| animate: animator.animate, | |
| }; | |
| try { | |
| const ret = callback(helpers); | |
| this.flush(); | |
| return ret; | |
| } | |
| finally { | |
| TestScheduler.frameTimeFactor = prevFrameTimeFactor; | |
| this.maxFrames = prevMaxFrames; | |
| this.runMode = false; | |
| animationFrameProvider.delegate = undefined; | |
| dateTimestampProvider.delegate = undefined; | |
| immediateProvider.delegate = undefined; | |
| intervalProvider.delegate = undefined; | |
| timeoutProvider.delegate = undefined; | |
| performanceTimestampProvider.delegate = undefined; | |
| } | |
| } | |
| } | |
| TestScheduler.frameTimeFactor = 10; | |
| //# sourceMappingURL=TestScheduler.js.map |
Xet Storage Details
- Size:
- 21.6 kB
- Xet hash:
- 742fe52865937243ffe2f39dbfa62c9228deb3410e701b6764f6c9d882b9169e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.