diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-06-28 17:26:46 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-06-28 17:43:56 -0700 |
| commit | e4fa1e69e7ebfb627c7198fd1a9881e9327ec4d4 (patch) | |
| tree | 06284a538a6008eca75051399e47db4e5d50301c /node_modules/rxjs/dist/esm/internal/testing/TestScheduler.js | |
initial commit: scaffolding
Diffstat (limited to 'node_modules/rxjs/dist/esm/internal/testing/TestScheduler.js')
| -rw-r--r-- | node_modules/rxjs/dist/esm/internal/testing/TestScheduler.js | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/node_modules/rxjs/dist/esm/internal/testing/TestScheduler.js b/node_modules/rxjs/dist/esm/internal/testing/TestScheduler.js new file mode 100644 index 0000000..90419db --- /dev/null +++ b/node_modules/rxjs/dist/esm/internal/testing/TestScheduler.js @@ -0,0 +1,505 @@ +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
\ No newline at end of file |
