vitest-marbles
Version:
Marble testing helpers library for RxJs and Jest
382 lines (352 loc) • 13.7 kB
JavaScript
/******/ (() => { // 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