UNPKG

jest-marbles

Version:

Marble testing helpers library for RxJs and Jest

124 lines (105 loc) 3.7 kB
import { Observable } from 'rxjs'; import { TestMessages } from './types'; import { TestScheduler } from 'rxjs/testing'; import { assertDeepEqual } from './assert-deep-equal'; const NEGATED = Symbol('jest-marbles-negated'); export class Scheduler { public static instance: TestScheduler | null; private static onFlush: (() => void)[] = []; private static currentAnimate: ((marbles: string) => void) | null = null; private static prevFrameTimeFactor = 0; public static init(): void { const scheduler = new TestScheduler(assertDeepEqual); (scheduler as any).runMode = true; scheduler.maxFrames = Infinity; Scheduler.instance = scheduler; Scheduler.onFlush = []; Scheduler.currentAnimate = null; Scheduler.prevFrameTimeFactor = TestScheduler.frameTimeFactor; TestScheduler.frameTimeFactor = 1; } public static get(): TestScheduler { if (Scheduler.instance) { return Scheduler.instance; } throw new Error('Scheduler is not initialized'); } public static reset(): void { Scheduler.instance = null; Scheduler.currentAnimate = null; } private static flushTests(): any[] { return (Scheduler.get() as any)['flushTests']; } public static captureAnimate(animate: (marbles: string) => void): void { Scheduler.currentAnimate = animate; } public static animate(marbles: string): void { if (!Scheduler.currentAnimate) { throw new Error('animate() can only be used inside marbleTest()'); } Scheduler.currentAnimate(marbles); } public static markLastNegated(): void { const tests = Scheduler.flushTests(); tests[tests.length - 1][NEGATED] = true; } public static markLastReady(): void { const tests = Scheduler.flushTests(); tests[tests.length - 1].ready = true; } public static queueOnFlush(fn: () => void): void { Scheduler.onFlush.push(fn); } public static clearFlushTests(): void { Scheduler.flushTests().length = 0; } public static installNegationAwareAssert(): void { const scheduler = Scheduler.get(); const flushTests = Scheduler.flushTests(); const hasNegated = flushTests.some((t) => t[NEGATED]); if (!hasNegated) { return; } const negatedSet = new WeakSet<object>(flushTests.filter((t) => t[NEGATED])); const readyTests = flushTests.filter((t) => t.ready); let callIndex = 0; (scheduler as any).assertDeepEqual = (actual: any, expected: any) => { const test = readyTests[callIndex++]; if (test && negatedSet.has(test)) { let threw = false; try { assertDeepEqual(actual, expected); } catch { threw = true; } if (!threw) { throw new Error( 'Expected observables to differ, but they matched.\n' + ` Received: ${JSON.stringify(actual)}\n` + ` Not expected: ${JSON.stringify(expected)}` ); } } else { assertDeepEqual(actual, expected); } }; } public static teardown(): void { const scheduler = Scheduler.get(); Scheduler.installNegationAwareAssert(); scheduler.run(() => { /* run() forces frameTimeFactor = 1 internally; no override needed */ }); while (Scheduler.onFlush.length > 0) { Scheduler.onFlush.shift()?.(); } TestScheduler.frameTimeFactor = Scheduler.prevFrameTimeFactor; Scheduler.reset(); } public static materializeInnerObservable(observable: Observable<any>, outerFrame: number): TestMessages { const scheduler = Scheduler.get(); // @ts-expect-error to avoid code duplication return scheduler.materializeInnerObservable(observable, outerFrame); } }