UNPKG

vitest-marbles

Version:

Marble testing helpers library for RxJs and Jest

382 lines (352 loc) 13.7 kB
/******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports /******/ __webpack_require__.r = (exports) => { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; /*!*******************************!*\ !*** ./index.ts + 12 modules ***! \*******************************/ // ESM COMPAT FLAG __webpack_require__.r(__webpack_exports__); // EXPORTS __webpack_require__.d(__webpack_exports__, { "Scheduler": () => (/* reexport */ Scheduler), "cold": () => (/* binding */ cold), "hot": () => (/* binding */ hot), "time": () => (/* binding */ time) }); ;// CONCATENATED MODULE: external "rxjs" const external_rxjs_namespaceObject = rxjs; ;// CONCATENATED MODULE: external "rxjs/testing" const testing_namespaceObject = rxjs/testing; ;// CONCATENATED MODULE: ./src/marbles-glossary.ts var MarblesGlossary; (function (MarblesGlossary) { MarblesGlossary["Completion"] = "|"; MarblesGlossary["Error"] = "#"; MarblesGlossary["TimeFrame"] = "-"; MarblesGlossary["Subscription"] = "^"; MarblesGlossary["Unsubscription"] = "!"; MarblesGlossary["GroupStart"] = "("; MarblesGlossary["GroupEnd"] = ")"; })(MarblesGlossary || (MarblesGlossary = {})); ;// CONCATENATED MODULE: ./src/notification-event.ts class NotificationEvent { constructor(start) { this.start = start; this.marbles = ''; } get end() { return this.start + this.marbles.length; } } ;// CONCATENATED MODULE: ./src/notification-kind.ts const ValueLiteral = {}; const NotificationKindChars = { N: ValueLiteral, C: MarblesGlossary.Completion, E: MarblesGlossary.Error, }; ;// CONCATENATED MODULE: ./src/marblizer.ts const frameStep = 10; class Marblizer { static marblize(messages) { const emissions = Marblizer.getNotificationEvents(messages); let marbles = ''; for (let i = 0, prevEndFrame = 0; i < emissions.length; prevEndFrame = emissions[i].end, i++) { marbles = `${marbles}${MarblesGlossary.TimeFrame.repeat(emissions[i].start - prevEndFrame) + emissions[i].marbles}`; } return marbles; } static marblizeSubscriptions(logs) { return logs.map(log => this.marblizeLogEntry(log.subscribedFrame / frameStep, MarblesGlossary.Subscription) + this.marblizeLogEntry((log.unsubscribedFrame - log.subscribedFrame) / frameStep - 1, MarblesGlossary.Unsubscription)); } static marblizeLogEntry(logPoint, symbol) { if (logPoint !== Infinity) { return MarblesGlossary.TimeFrame.repeat(logPoint) + symbol; } else { return ''; } } static getNotificationEvents(messages) { const framesToEmissions = messages.reduce((result, message) => { if (!result[message.frame]) { result[message.frame] = new NotificationEvent(message.frame / frameStep); } const event = result[message.frame]; event.marbles += Marblizer.extractMarble(message); return result; }, {}); const events = Object.keys(framesToEmissions).map(frame => framesToEmissions[frame]); Marblizer.encloseGroupEvents(events); return events; } static extractMarble(message) { let marble = NotificationKindChars[message.notification.kind]; if (marble === ValueLiteral) marble = message.notification.value; return marble; } static encloseGroupEvents(events) { events.forEach(event => { if (event.marbles.length > 1) { event.marbles = `${MarblesGlossary.GroupStart}${event.marbles}${MarblesGlossary.GroupEnd}`; } }); } } ;// CONCATENATED MODULE: ./src/vitest/custom-matchers.ts function canMarblize(...messages) { return messages.every(message => message.filter(({ notification: { kind } }) => kind === 'N').every(isCharacter)); } function isCharacter({ notification }) { const value = notification.value; return ((typeof value === 'string' && value.length === 1) || (value !== undefined && JSON.stringify(value).length === 1)); } const customTestMatchers = { toBeNotifications(actual, expected) { let actualMarble = actual; let expectedMarble = expected; if (canMarblize(actual, expected)) { actualMarble = Marblizer.marblize(actual); expectedMarble = Marblizer.marblize(expected); } const pass = this.equals(actualMarble, expectedMarble); const message = pass ? () => this.utils.matcherHint('.not.toBeNotifications') + '\n\n' + `Expected notifications to not be:\n` + ` ${this.utils.printExpected(expectedMarble)}\n` + `But got:\n` + ` ${this.utils.printReceived(actualMarble)}` : () => { const diffString = this.utils.diff(expectedMarble, actualMarble, { expand: true, }); return (this.utils.matcherHint('.toBeNotifications') + '\n\n' + `Expected notifications to be:\n` + ` ${this.utils.printExpected(expectedMarble)}\n` + `But got:\n` + ` ${this.utils.printReceived(actualMarble)}` + (diffString ? `\n\nDifference:\n\n${diffString}` : '')); }; return { message, pass }; }, toBeSubscriptions(actual, expected) { const actualMarbleArray = Marblizer.marblizeSubscriptions(actual); const expectedMarbleArray = Marblizer.marblizeSubscriptions(expected); const pass = subscriptionsPass(actualMarbleArray, expectedMarbleArray); const message = pass ? () => this.utils.matcherHint('.not.toHaveSubscriptions') + '\n\n' + `Expected observable to not have the following subscription points:\n` + ` ${this.utils.printExpected(expectedMarbleArray)}\n` + `But got:\n` + ` ${this.utils.printReceived(actualMarbleArray)}` : () => { const diffString = this.utils.diff(expectedMarbleArray, actualMarbleArray, { expand: true, }); return (this.utils.matcherHint('.toHaveSubscriptions') + '\n\n' + `Expected observable to have the following subscription points:\n` + ` ${this.utils.printExpected(expectedMarbleArray)}\n` + `But got:\n` + ` ${this.utils.printReceived(actualMarbleArray)}` + (diffString ? `\n\nDifference:\n\n${diffString}` : '')); }; return { message, pass }; }, toHaveEmptySubscriptions(actual) { const pass = !(actual && actual.length > 0); let marbles; if (actual && actual.length > 0) { marbles = Marblizer.marblizeSubscriptions(actual); } const message = pass ? () => this.utils.matcherHint('.not.toHaveNoSubscriptions') + '\n\n' + `Expected observable to have at least one subscription point, but got nothing` + this.utils.printReceived('') : () => this.utils.matcherHint('.toHaveNoSubscriptions') + '\n\n' + `Expected observable to have no subscription points\n` + `But got:\n` + ` ${this.utils.printReceived(marbles)}\n\n`; return { message, pass }; }, }; function subscriptionsPass(actualMarbleArray, expectedMarbleArray) { if (actualMarbleArray.length !== expectedMarbleArray.length) { return false; } let pass = true; for (const actualMarble of actualMarbleArray) { if (!expectedMarbleArray.includes(actualMarble)) { pass = false; break; } } return pass; } expect.extend(customTestMatchers); ;// CONCATENATED MODULE: ./src/rxjs/assert-deep-equal.ts function expectedIsSubscriptionLogArray(actual, expected) { return ((actual.length === 0 && expected.length === 0) || (expected.length !== 0 && expected[0].subscribedFrame !== undefined)); } function actualIsSubscriptionsAndExpectedIsEmpty(actual, expected) { return expected.length === 0 && actual.length !== 0 && actual[0].subscribedFrame !== undefined; } function assertDeepEqual(actual, expected) { if (!expected) return; if (actualIsSubscriptionsAndExpectedIsEmpty(actual, expected)) { expect(actual).toHaveEmptySubscriptions(); } else if (expectedIsSubscriptionLogArray(actual, expected)) { expect(actual).toBeSubscriptions(expected); } else { expect(actual).toBeNotifications(expected); } } ;// CONCATENATED MODULE: ./src/rxjs/scheduler.ts class Scheduler { static init() { Scheduler.instance = new testing_namespaceObject.TestScheduler(assertDeepEqual); } static get() { if (Scheduler.instance) { return Scheduler.instance; } throw new Error('Scheduler is not initialized'); } static reset() { Scheduler.instance = null; } static materializeInnerObservable(observable, outerFrame) { const scheduler = Scheduler.get(); // @ts-ignore return scheduler.materializeInnerObservable(observable, outerFrame); } } ;// CONCATENATED MODULE: ./src/rxjs/cold-observable.ts class ColdObservable extends external_rxjs_namespaceObject.Observable { constructor(marbles, values, error) { super(); this.marbles = marbles; this.values = values; this.error = error; this.source = Scheduler.get().createColdObservable(marbles, values, error); } getSubscriptions() { return this.source.subscriptions; } } ;// CONCATENATED MODULE: ./src/rxjs/hot-observable.ts class HotObservable extends external_rxjs_namespaceObject.Observable { constructor(marbles, values, error) { super(); this.marbles = marbles; this.values = values; this.error = error; this.source = Scheduler.get().createHotObservable(marbles, values, error); } getSubscriptions() { return this.source.subscriptions; } } ;// CONCATENATED MODULE: ./src/rxjs/strip-alignment-chars.ts function stripAlignmentChars(marbles) { return marbles.replace(/^[ ]+/, ''); } ;// CONCATENATED MODULE: ./index.ts function hot(marbles, values, error) { return new HotObservable(stripAlignmentChars(marbles), values, error); } function cold(marbles, values, error) { return new ColdObservable(stripAlignmentChars(marbles), values, error); } function time(marbles) { return Scheduler.get().createTime(stripAlignmentChars(marbles)); } const dummyResult = { message: () => '', pass: true }; expect.extend({ toHaveSubscriptions(actual, marbles) { const sanitizedMarbles = Array.isArray(marbles) ? marbles.map(stripAlignmentChars) : stripAlignmentChars(marbles); Scheduler.get().expectSubscriptions(actual.getSubscriptions()).toBe(sanitizedMarbles); return dummyResult; }, toHaveNoSubscriptions(actual) { Scheduler.get().expectSubscriptions(actual.getSubscriptions()).toBe([]); return dummyResult; }, toBeObservable(actual, expected) { Scheduler.get().expectObservable(actual).toBe(expected.marbles, expected.values, expected.error); return dummyResult; }, toBeMarble(actual, marbles) { Scheduler.get().expectObservable(actual).toBe(stripAlignmentChars(marbles)); return dummyResult; }, toSatisfyOnFlush(actual, func) { Scheduler.get().expectObservable(actual); // tslint:disable:no-string-literal const flushTests = Scheduler.get()['flushTests']; flushTests[flushTests.length - 1].ready = true; onFlush.push(func); return dummyResult; } }); let onFlush = []; beforeEach(() => { Scheduler.init(); onFlush = []; }); afterEach(() => { Scheduler.get().flush(); while (onFlush.length > 0) { // @ts-ignore onFlush.shift()(); } Scheduler.reset(); }); /******/ })() ; //# sourceMappingURL=index.js.map