@granito/vitest-marbles 1.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +204 -0
  3. package/dist/index.d.ts +20 -0
  4. package/dist/index.js +60 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/marbles-glossary.d.ts +9 -0
  7. package/dist/src/marbles-glossary.js +11 -0
  8. package/dist/src/marbles-glossary.js.map +1 -0
  9. package/dist/src/marblizer.d.ts +9 -0
  10. package/dist/src/marblizer.js +47 -0
  11. package/dist/src/marblizer.js.map +1 -0
  12. package/dist/src/notification-event.d.ts +6 -0
  13. package/dist/src/notification-event.js +11 -0
  14. package/dist/src/notification-event.js.map +1 -0
  15. package/dist/src/notification-kind.d.ts +7 -0
  16. package/dist/src/notification-kind.js +8 -0
  17. package/dist/src/notification-kind.js.map +1 -0
  18. package/dist/src/rxjs/assert-deep-equal.d.ts +4 -0
  19. package/dist/src/rxjs/assert-deep-equal.js +19 -0
  20. package/dist/src/rxjs/assert-deep-equal.js.map +1 -0
  21. package/dist/src/rxjs/cold-observable.d.ts +11 -0
  22. package/dist/src/rxjs/cold-observable.js +19 -0
  23. package/dist/src/rxjs/cold-observable.js.map +1 -0
  24. package/dist/src/rxjs/hot-observable.d.ts +11 -0
  25. package/dist/src/rxjs/hot-observable.js +19 -0
  26. package/dist/src/rxjs/hot-observable.js.map +1 -0
  27. package/dist/src/rxjs/scheduler.d.ts +10 -0
  28. package/dist/src/rxjs/scheduler.js +22 -0
  29. package/dist/src/rxjs/scheduler.js.map +1 -0
  30. package/dist/src/rxjs/strip-alignment-chars.d.ts +1 -0
  31. package/dist/src/rxjs/strip-alignment-chars.js +4 -0
  32. package/dist/src/rxjs/strip-alignment-chars.js.map +1 -0
  33. package/dist/src/rxjs/types.d.ts +9 -0
  34. package/dist/src/rxjs/types.js +2 -0
  35. package/dist/src/rxjs/types.js.map +1 -0
  36. package/dist/src/vitest/custom-matchers.d.ts +13 -0
  37. package/dist/src/vitest/custom-matchers.js +103 -0
  38. package/dist/src/vitest/custom-matchers.js.map +1 -0
  39. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Synapse Wireless Labs
4
+ Copyright (c) 2025 Alexei Yashkov
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ [![Build](https://github.com/granito-source/vitest-marbles/actions/workflows/build.yaml/badge.svg)](https://github.com/granito-source/vitest-marbles/actions/workflows/build.yaml)
2
+ [![Latest Version](https://img.shields.io/npm/v/%40granito%2Fvitest-marbles.svg)](https://npm.im/@granito/vitest-marbles)
3
+
4
+ # Vitest Marbles
5
+
6
+ A set of helper functions and [Vitest](http://vitest.dev/) matchers for
7
+ [RxJs](https://rxjs.dev/)
8
+ [marble testing](https://rxjs.dev/guide/testing/marble-testing).
9
+ This library will help you to test your reactive code in easy and
10
+ clean way.
11
+
12
+ # Features
13
+
14
+ * Typescript;
15
+ * marblized error messages.
16
+
17
+ # Prerequisites
18
+
19
+ - Vitest;
20
+ - RxJS;
21
+ - familiarity with
22
+ [marbles syntax](https://rxjs.dev/guide/testing/marble-testing).
23
+
24
+ # Not supported
25
+
26
+ * time progression syntax.
27
+
28
+ # Usage
29
+
30
+ ```shell
31
+ npm install --save-vev @granito/vitest-marbles
32
+ ```
33
+
34
+ In the test file:
35
+
36
+ ```typescript
37
+ import { cold, hot, time, schedule } from '@granito/vitest-marbles';
38
+ ```
39
+
40
+ Inside the test:
41
+
42
+ ```typescript
43
+ expect(stream).toBeObservable(expected);
44
+ expect(stream).toBeMarble(marbleString);
45
+ expect(stream).toHaveSubscriptions(marbleString);
46
+ expect(stream).toHaveSubscriptions(marbleStringsArray);
47
+ expect(stream).toHaveNoSubscriptions();
48
+ expect(stream).toSatisfyOnFlush(() => {
49
+ expect(someMock).toHaveBeenCalled();
50
+ });
51
+ ```
52
+
53
+ # Examples
54
+
55
+ ## toBeObservable()
56
+
57
+ Verifies that the resulting stream emits certain values at certain time
58
+ frames.
59
+
60
+ ```typescript
61
+ it('merges two hot observables and start emitting from the subscription point', () => {
62
+ const e1 = hot('----a--^--b-------c--|', {a: 0});
63
+ const e2 = hot(' ---d-^--e---------f-----|', {a: 0});
64
+ const expected = cold('---(be)----c-f-----|', {a: 0});
65
+
66
+ expect(e1.pipe(merge(e2))).toBeObservable(expected);
67
+ });
68
+ ```
69
+
70
+ Sample output when the test fails (if change the expected result to
71
+ `'-d--(be)----c-f-----|'`).
72
+
73
+ ```text
74
+ Expected notifications to be:
75
+ "-d--(be)----c-f-----|"
76
+ But got:
77
+ "---(be)----c-f-----|"
78
+ ```
79
+
80
+ ## toBeMarble()
81
+
82
+ Same as `toBeObservable()` but receives marble string instead.
83
+
84
+ ```js
85
+ it('concatenates two cold observables into single cold observable', () => {
86
+ const a = cold('-a-|');
87
+ const b = cold('-b-|');
88
+ const expected = '-a--b-|';
89
+
90
+ expect(a.pipe(concat(b))).toBeMarble(expected);
91
+ });
92
+ ```
93
+
94
+ ## toHaveSubscriptions()
95
+
96
+ Verifies that the observable was subscribed in the provided time frames.
97
+ Useful, for example, when you want to verify that particular
98
+ `switchMap()` worked as expected.
99
+
100
+ ```typescript
101
+ it('Should figure out single subscription points', () => {
102
+ const x = cold(' --a---b---c--|');
103
+ const xsubs = ' ------^-------!';
104
+ const y = cold(' ---d--e---f---|');
105
+ const ysubs = ' --------------^-------------!';
106
+ const e1 = hot(' ------x-------y------|', { x, y });
107
+ const expected = cold('--------a---b----d--e---f---|');
108
+
109
+ expect(e1.pipe(switchAll())).toBeObservable(expected);
110
+ expect(x).toHaveSubscriptions(xsubs);
111
+ expect(y).toHaveSubscriptions(ysubs);
112
+ });
113
+ ```
114
+ The matcher can also accept multiple subscription marbles.
115
+
116
+ ```typescript
117
+ it('figures out multiple subscription points', () => {
118
+ const x = cold(' --a---b---c--|');
119
+ const y = cold(' ----x---x|', {x});
120
+ const ySubscription1 = ' ----^---!';
121
+ // '--a---b---c--|'
122
+ const ySubscription2 = ' --------^------------!';
123
+ const expectedY = cold(' ------a---a---b---c--|');
124
+ const z = cold(' -x|', {x});
125
+ // '--a---b---c--|'
126
+ const zSubscription = ' -^------------!';
127
+ const expectedZ = cold(' ---a---b---c--|');
128
+
129
+ expect(y.pipe(switchAll())).toBeObservable(expectedY);
130
+ expect(z.pipe(switchAll())).toBeObservable(expectedZ);
131
+ expect(x).toHaveSubscriptions([
132
+ ySubscription1,
133
+ ySubscription2,
134
+ zSubscription
135
+ ]);
136
+ });
137
+ ```
138
+ Sample output when the test fails (if change `ySubscription1` to
139
+ `'-----------------^---!'`).
140
+
141
+ ```text
142
+ Expected observable to have the following subscription points:
143
+ ["-----------------^---!", "--------^------------!", "-^------------!"]
144
+ But got:
145
+ ["-^------------!", "----^---!", "--------^------------!"]
146
+ ```
147
+
148
+ ## toHaveNoSubscriptions()
149
+
150
+ Verifies that the observable was not subscribed during the test.
151
+ Especially useful when you want to verify that certain chain was not
152
+ called due to an error:
153
+
154
+ ```typescript
155
+ it('verifies that switchMap() was not performed due to an error', () => {
156
+ const x = cold('--a---b---c--|');
157
+ const y = cold('---#-x--', {x});
158
+ const result = y.pipe(switchAll());
159
+
160
+ expect(result).toBeMarble('---#');
161
+ expect(x).toHaveNoSubscriptions();
162
+ });
163
+ ```
164
+
165
+ Sample output when the test fails (if remove error and change the
166
+ expected marble to `'------a---b---c--|'`).
167
+
168
+ ```
169
+ Expected observable to have no subscription points
170
+ But got:
171
+ ["----^------------!"]
172
+ ```
173
+
174
+ ## toSatisfyOnFlush()
175
+
176
+ Allows you to assert on certain side effects/conditions that should be
177
+ satisfied when the observable has been flushed (finished).
178
+
179
+ ```typescript
180
+ it('verifies mock has been called', () => {
181
+ const mock = vi.fn();
182
+ const stream$ = cold('blah|').pipe(tap(mock));
183
+
184
+ expect(stream$).toSatisfyOnFlush(() => {
185
+ expect(mock).toHaveBeenCalledTimes(4);
186
+ });
187
+ });
188
+ ```
189
+
190
+ ## schedule()
191
+
192
+ Allows you to schedule task on specified frame.
193
+
194
+ ```typescript
195
+ it('verifies subject values', () => {
196
+ const source = new Subject();
197
+ const expected = cold('ab');
198
+
199
+ schedule(() => source.next('a'), 1);
200
+ schedule(() => source.next('b'), 2);
201
+
202
+ expect(source).toBeObservable(expected);
203
+ });
204
+ ```
@@ -0,0 +1,20 @@
1
+ import { ColdObservable } from './src/rxjs/cold-observable';
2
+ import { HotObservable } from './src/rxjs/hot-observable';
3
+ import { Subscription } from 'rxjs';
4
+ export type ObservableWithSubscriptions = ColdObservable | HotObservable;
5
+ export { Scheduler } from './src/rxjs/scheduler';
6
+ interface CustomMatchers<R = unknown> {
7
+ toBeObservable(observable: ObservableWithSubscriptions): R;
8
+ toHaveSubscriptions(marbles: string | string[]): R;
9
+ toHaveNoSubscriptions(): R;
10
+ toBeMarble(marble: string): R;
11
+ toSatisfyOnFlush(func: () => void): R;
12
+ }
13
+ declare module 'vitest' {
14
+ interface Matchers<T = any> extends CustomMatchers<T> {
15
+ }
16
+ }
17
+ export declare function hot(marbles: string, values?: object, error?: object): HotObservable;
18
+ export declare function cold(marbles: string, values?: object, error?: object): ColdObservable;
19
+ export declare function time(marbles: string): number;
20
+ export declare function schedule(work: () => void, delay: number): Subscription;
package/dist/index.js ADDED
@@ -0,0 +1,60 @@
1
+ import { ColdObservable } from './src/rxjs/cold-observable';
2
+ import { HotObservable } from './src/rxjs/hot-observable';
3
+ import { Scheduler } from './src/rxjs/scheduler';
4
+ import { stripAlignmentChars } from './src/rxjs/strip-alignment-chars';
5
+ import { TestScheduler } from 'rxjs/testing';
6
+ export { Scheduler } from './src/rxjs/scheduler';
7
+ export function hot(marbles, values, error) {
8
+ return new HotObservable(stripAlignmentChars(marbles), values, error);
9
+ }
10
+ export function cold(marbles, values, error) {
11
+ return new ColdObservable(stripAlignmentChars(marbles), values, error);
12
+ }
13
+ export function time(marbles) {
14
+ return Scheduler.get().createTime(stripAlignmentChars(marbles));
15
+ }
16
+ export function schedule(work, delay) {
17
+ return Scheduler.get().schedule(work, delay);
18
+ }
19
+ const dummyResult = {
20
+ message: () => '',
21
+ pass: true,
22
+ };
23
+ expect.extend({
24
+ toHaveSubscriptions(actual, marbles) {
25
+ const sanitizedMarbles = Array.isArray(marbles) ? marbles.map(stripAlignmentChars) : stripAlignmentChars(marbles);
26
+ Scheduler.get().expectSubscriptions(actual.getSubscriptions()).toBe(sanitizedMarbles);
27
+ return dummyResult;
28
+ },
29
+ toHaveNoSubscriptions(actual) {
30
+ Scheduler.get().expectSubscriptions(actual.getSubscriptions()).toBe([]);
31
+ return dummyResult;
32
+ },
33
+ toBeObservable(actual, expected) {
34
+ Scheduler.get().expectObservable(actual).toBe(expected.marbles, expected.values, expected.error);
35
+ return dummyResult;
36
+ },
37
+ toBeMarble(actual, marbles) {
38
+ Scheduler.get().expectObservable(actual).toBe(stripAlignmentChars(marbles));
39
+ return dummyResult;
40
+ },
41
+ toSatisfyOnFlush(actual, func) {
42
+ Scheduler.get().expectObservable(actual);
43
+ const flushTests = Scheduler.get()['flushTests'];
44
+ flushTests[flushTests.length - 1].ready = true;
45
+ onFlush.push(func);
46
+ return dummyResult;
47
+ }
48
+ });
49
+ let onFlush = [];
50
+ beforeEach(() => {
51
+ Scheduler.init();
52
+ onFlush = [];
53
+ });
54
+ afterEach(() => {
55
+ Scheduler.get().run(() => TestScheduler.frameTimeFactor = 10);
56
+ while (onFlush.length > 0)
57
+ onFlush.shift()?.();
58
+ Scheduler.reset();
59
+ });
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare enum MarblesGlossary {
2
+ Completion = "|",
3
+ Error = "#",
4
+ TimeFrame = "-",
5
+ Subscription = "^",
6
+ Unsubscription = "!",
7
+ GroupStart = "(",
8
+ GroupEnd = ")"
9
+ }
@@ -0,0 +1,11 @@
1
+ export var MarblesGlossary;
2
+ (function (MarblesGlossary) {
3
+ MarblesGlossary["Completion"] = "|";
4
+ MarblesGlossary["Error"] = "#";
5
+ MarblesGlossary["TimeFrame"] = "-";
6
+ MarblesGlossary["Subscription"] = "^";
7
+ MarblesGlossary["Unsubscription"] = "!";
8
+ MarblesGlossary["GroupStart"] = "(";
9
+ MarblesGlossary["GroupEnd"] = ")";
10
+ })(MarblesGlossary || (MarblesGlossary = {}));
11
+ //# sourceMappingURL=marbles-glossary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marbles-glossary.js","sourceRoot":"","sources":["../../src/marbles-glossary.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,eAQX;AARD,WAAY,eAAe;IACzB,mCAAgB,CAAA;IAChB,8BAAW,CAAA;IACX,kCAAe,CAAA;IACf,qCAAkB,CAAA;IAClB,uCAAoB,CAAA;IACpB,mCAAgB,CAAA;IAChB,iCAAc,CAAA;AAChB,CAAC,EARW,eAAe,KAAf,eAAe,QAQ1B"}
@@ -0,0 +1,9 @@
1
+ import { SubscriptionLog, TestMessages } from './rxjs/types';
2
+ export declare class Marblizer {
3
+ static marblize(messages: TestMessages): string;
4
+ static marblizeSubscriptions(logs: SubscriptionLog[]): string[];
5
+ private static marblizeLogEntry;
6
+ private static getNotificationEvents;
7
+ private static extractMarble;
8
+ private static encloseGroupEvents;
9
+ }
@@ -0,0 +1,47 @@
1
+ import { MarblesGlossary } from './marbles-glossary';
2
+ import { NotificationEvent } from './notification-event';
3
+ import { NotificationKindChars, ValueLiteral } from './notification-kind';
4
+ const frameStep = 10;
5
+ export class Marblizer {
6
+ static marblize(messages) {
7
+ const emissions = Marblizer.getNotificationEvents(messages);
8
+ let marbles = '';
9
+ for (let i = 0, prevEndFrame = 0; i < emissions.length; prevEndFrame = emissions[i].end, i++)
10
+ marbles = `${marbles}${MarblesGlossary.TimeFrame.repeat(emissions[i].start - prevEndFrame) + emissions[i].marbles}`;
11
+ return marbles;
12
+ }
13
+ static marblizeSubscriptions(logs) {
14
+ return logs.map(log => this.marblizeLogEntry(log.subscribedFrame / frameStep, MarblesGlossary.Subscription) +
15
+ this.marblizeLogEntry((log.unsubscribedFrame - log.subscribedFrame) / frameStep - 1, MarblesGlossary.Unsubscription));
16
+ }
17
+ static marblizeLogEntry(logPoint, symbol) {
18
+ return logPoint !== Infinity ? MarblesGlossary.TimeFrame.repeat(logPoint) + symbol : '';
19
+ }
20
+ static getNotificationEvents(messages) {
21
+ const framesToEmissions = messages.reduce((result, message) => {
22
+ if (!result[message.frame])
23
+ result[message.frame] = new NotificationEvent(message.frame / frameStep);
24
+ const event = result[message.frame];
25
+ event.marbles += Marblizer.extractMarble(message);
26
+ return result;
27
+ }, {});
28
+ const events = Object
29
+ .keys(framesToEmissions)
30
+ .map(frame => framesToEmissions[Number(frame)]);
31
+ Marblizer.encloseGroupEvents(events);
32
+ return events;
33
+ }
34
+ static extractMarble(message) {
35
+ let marble = NotificationKindChars[message.notification.kind];
36
+ if (marble === ValueLiteral)
37
+ marble = message.notification.value;
38
+ return marble;
39
+ }
40
+ static encloseGroupEvents(events) {
41
+ events.forEach(event => {
42
+ if (event.marbles.length > 1)
43
+ event.marbles = `${MarblesGlossary.GroupStart}${event.marbles}${MarblesGlossary.GroupEnd}`;
44
+ });
45
+ }
46
+ }
47
+ //# sourceMappingURL=marblizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marblizer.js","sourceRoot":"","sources":["../../src/marblizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare class NotificationEvent {
2
+ start: number;
3
+ marbles: string;
4
+ constructor(start: number);
5
+ get end(): number;
6
+ }
@@ -0,0 +1,11 @@
1
+ export class NotificationEvent {
2
+ start;
3
+ marbles = '';
4
+ constructor(start) {
5
+ this.start = start;
6
+ }
7
+ get end() {
8
+ return this.start + this.marbles.length;
9
+ }
10
+ }
11
+ //# sourceMappingURL=notification-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-event.js","sourceRoot":"","sources":["../../src/notification-event.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,iBAAiB;IAGT;IAFnB,OAAO,GAAG,EAAE,CAAC;IAEb,YAAmB,KAAa;QAAb,UAAK,GAAL,KAAK,CAAQ;IAChC,CAAC;IAED,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC1C,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ import { MarblesGlossary } from './marbles-glossary';
2
+ export declare const ValueLiteral: {};
3
+ export declare const NotificationKindChars: {
4
+ N: {};
5
+ C: MarblesGlossary;
6
+ E: MarblesGlossary;
7
+ };
@@ -0,0 +1,8 @@
1
+ import { MarblesGlossary } from './marbles-glossary';
2
+ export const ValueLiteral = {};
3
+ export const NotificationKindChars = {
4
+ N: ValueLiteral,
5
+ C: MarblesGlossary.Completion,
6
+ E: MarblesGlossary.Error,
7
+ };
8
+ //# sourceMappingURL=notification-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-kind.js","sourceRoot":"","sources":["../../src/notification-kind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAE/B,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,CAAC,EAAE,YAAY;IACf,CAAC,EAAE,eAAe,CAAC,UAAU;IAC7B,CAAC,EAAE,eAAe,CAAC,KAAK;CACzB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { SubscriptionLog, TestMessages } from './types';
2
+ import '../vitest/custom-matchers';
3
+ export type MessageOrSubscription = TestMessages | SubscriptionLog[];
4
+ export declare function assertDeepEqual(actual: MessageOrSubscription, expected: MessageOrSubscription): void;
@@ -0,0 +1,19 @@
1
+ import '../vitest/custom-matchers';
2
+ function expectedIsSubscriptionLogArray(actual, expected) {
3
+ return actual.length === 0 && expected.length === 0 ||
4
+ expected.length !== 0 && expected[0].subscribedFrame !== undefined;
5
+ }
6
+ function actualIsSubscriptionsAndExpectedIsEmpty(actual, expected) {
7
+ return expected.length === 0 && actual.length !== 0 && actual[0].subscribedFrame !== undefined;
8
+ }
9
+ export function assertDeepEqual(actual, expected) {
10
+ if (!expected)
11
+ return;
12
+ if (actualIsSubscriptionsAndExpectedIsEmpty(actual, expected))
13
+ expect(actual).toHaveEmptySubscriptions();
14
+ else if (expectedIsSubscriptionLogArray(actual, expected))
15
+ expect(actual).toBeSubscriptions(expected);
16
+ else
17
+ expect(actual).toBeNotifications(expected);
18
+ }
19
+ //# sourceMappingURL=assert-deep-equal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-deep-equal.js","sourceRoot":"","sources":["../../../src/rxjs/assert-deep-equal.ts"],"names":[],"mappings":"AACA,OAAO,2BAA2B,CAAC;AAInC,SAAS,8BAA8B,CAAC,MAA6B,EACnE,QAA+B;IAC/B,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QACjD,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAK,QAAQ,CAAC,CAAC,CAAS,CAAC,eAAe,KAAK,SAAS,CAAC;AAChF,CAAC;AAED,SAAS,uCAAuC,CAAC,MAA6B,EAC5E,QAA+B;IAC/B,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAK,MAAM,CAAC,CAAC,CAAS,CAAC,eAAe,KAAK,SAAS,CAAC;AAC1G,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAA6B,EAAE,QAA+B;IAC5F,IAAI,CAAC,QAAQ;QACX,OAAO;IAET,IAAI,uCAAuC,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,CAAC,wBAAwB,EAAE,CAAC;SACvC,IAAI,8BAA8B,CAAC,MAAM,EAAE,QAAQ,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;;QAE3C,MAAM,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Observable } from 'rxjs';
2
+ import { TestScheduler } from 'rxjs/testing';
3
+ import { SubscriptionLog } from './types';
4
+ export declare class ColdObservable extends Observable<any> {
5
+ marbles: string;
6
+ values?: Record<string, any> | undefined;
7
+ error?: any | undefined;
8
+ source: ReturnType<TestScheduler['createColdObservable']>;
9
+ constructor(marbles: string, values?: Record<string, any> | undefined, error?: any | undefined);
10
+ getSubscriptions(): SubscriptionLog[];
11
+ }
@@ -0,0 +1,19 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Scheduler } from './scheduler';
3
+ export class ColdObservable extends Observable {
4
+ marbles;
5
+ values;
6
+ error;
7
+ source;
8
+ constructor(marbles, values, error) {
9
+ super();
10
+ this.marbles = marbles;
11
+ this.values = values;
12
+ this.error = error;
13
+ this.source = Scheduler.get().createColdObservable(marbles, values, error);
14
+ }
15
+ getSubscriptions() {
16
+ return this.source.subscriptions;
17
+ }
18
+ }
19
+ //# sourceMappingURL=cold-observable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cold-observable.js","sourceRoot":"","sources":["../../../src/rxjs/cold-observable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAGlC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,OAAO,cAAe,SAAQ,UAAe;IAG9B;IAAwB;IAAqC;IAFvE,MAAM,CAAoD;IAEnE,YAAmB,OAAe,EAAS,MAA4B,EAAS,KAAW;QACzF,KAAK,EAAE,CAAC;QADS,YAAO,GAAP,OAAO,CAAQ;QAAS,WAAM,GAAN,MAAM,CAAsB;QAAS,UAAK,GAAL,KAAK,CAAM;QAGzF,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7E,CAAC;IAED,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ import { Observable } from 'rxjs';
2
+ import { TestScheduler } from 'rxjs/testing';
3
+ import { SubscriptionLog } from './types';
4
+ export declare class HotObservable extends Observable<any> {
5
+ marbles: string;
6
+ values?: Record<string, any> | undefined;
7
+ error?: any | undefined;
8
+ source: ReturnType<TestScheduler['createHotObservable']>;
9
+ constructor(marbles: string, values?: Record<string, any> | undefined, error?: any | undefined);
10
+ getSubscriptions(): SubscriptionLog[];
11
+ }
@@ -0,0 +1,19 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Scheduler } from './scheduler';
3
+ export class HotObservable extends Observable {
4
+ marbles;
5
+ values;
6
+ error;
7
+ source;
8
+ constructor(marbles, values, error) {
9
+ super();
10
+ this.marbles = marbles;
11
+ this.values = values;
12
+ this.error = error;
13
+ this.source = Scheduler.get().createHotObservable(marbles, values, error);
14
+ }
15
+ getSubscriptions() {
16
+ return this.source.subscriptions;
17
+ }
18
+ }
19
+ //# sourceMappingURL=hot-observable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hot-observable.js","sourceRoot":"","sources":["../../../src/rxjs/hot-observable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAGlC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,OAAO,aAAc,SAAQ,UAAe;IAG7B;IAAwB;IAAqC;IAFvE,MAAM,CAAmD;IAElE,YAAmB,OAAe,EAAS,MAA4B,EAAS,KAAW;QACzF,KAAK,EAAE,CAAC;QADS,YAAO,GAAP,OAAO,CAAQ;QAAS,WAAM,GAAN,MAAM,CAAsB;QAAS,UAAK,GAAL,KAAK,CAAM;QAGzF,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5E,CAAC;IAED,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ import { Observable } from 'rxjs';
2
+ import { TestMessages } from './types';
3
+ import { TestScheduler } from 'rxjs/testing';
4
+ export declare class Scheduler {
5
+ static instance: TestScheduler | null;
6
+ static init(): void;
7
+ static get(): TestScheduler;
8
+ static reset(): void;
9
+ static materializeInnerObservable(observable: Observable<any>, outerFrame: number): TestMessages;
10
+ }
@@ -0,0 +1,22 @@
1
+ import { TestScheduler } from 'rxjs/testing';
2
+ import { assertDeepEqual } from './assert-deep-equal';
3
+ export class Scheduler {
4
+ static instance;
5
+ static init() {
6
+ Scheduler.instance = new TestScheduler(assertDeepEqual);
7
+ }
8
+ static get() {
9
+ if (Scheduler.instance)
10
+ return Scheduler.instance;
11
+ throw new Error('Scheduler is not initialized');
12
+ }
13
+ static reset() {
14
+ Scheduler.instance = null;
15
+ }
16
+ static materializeInnerObservable(observable, outerFrame) {
17
+ const scheduler = Scheduler.get();
18
+ // @ts-expect-error to avoid code duplication
19
+ return scheduler.materializeInnerObservable(observable, outerFrame);
20
+ }
21
+ }
22
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../../src/rxjs/scheduler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,OAAO,SAAS;IACb,MAAM,CAAC,QAAQ,CAAuB;IAEtC,MAAM,CAAC,IAAI;QAChB,SAAS,CAAC,QAAQ,GAAG,IAAI,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1D,CAAC;IAEM,MAAM,CAAC,GAAG;QACf,IAAI,SAAS,CAAC,QAAQ;YACpB,OAAO,SAAS,CAAC,QAAQ,CAAC;QAE5B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAEM,MAAM,CAAC,KAAK;QACjB,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC5B,CAAC;IAEM,MAAM,CAAC,0BAA0B,CAAC,UAA2B,EAAE,UAAkB;QACtF,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC;QAElC,6CAA6C;QAC7C,OAAO,SAAS,CAAC,0BAA0B,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACtE,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export declare function stripAlignmentChars(marbles: string): string;
@@ -0,0 +1,4 @@
1
+ export function stripAlignmentChars(marbles) {
2
+ return marbles.replace(/^ +/, '');
3
+ }
4
+ //# sourceMappingURL=strip-alignment-chars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-alignment-chars.js","sourceRoot":"","sources":["../../../src/rxjs/strip-alignment-chars.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { TestScheduler } from 'rxjs/testing';
2
+ /**
3
+ * Exported return type of TestMessage[] to avoid importing internal APIs.
4
+ */
5
+ export type TestMessages = ReturnType<typeof TestScheduler.parseMarbles>;
6
+ /**
7
+ * Exported return type of SubscriptionLog to avoid importing internal APIs.
8
+ */
9
+ export type SubscriptionLog = ReturnType<typeof TestScheduler.parseMarblesAsSubscriptions>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/rxjs/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import { SubscriptionLog, TestMessages } from '../rxjs/types';
2
+ import { MatchersObject } from '@vitest/expect';
3
+ interface CustomMatchers<R = unknown> {
4
+ toBeNotifications: (messages: TestMessages) => R;
5
+ toBeSubscriptions: (subscriptions: SubscriptionLog[]) => R;
6
+ toHaveEmptySubscriptions: () => R;
7
+ }
8
+ export declare const customTestMatchers: MatchersObject;
9
+ declare module 'vitest' {
10
+ interface Matchers<T = any> extends CustomMatchers<T> {
11
+ }
12
+ }
13
+ export {};
@@ -0,0 +1,103 @@
1
+ import { Marblizer } from '../marblizer';
2
+ export const customTestMatchers = {
3
+ toBeNotifications(actual, expected) {
4
+ let actualMarble = actual;
5
+ let expectedMarble = expected;
6
+ if (canMarblize(actual, expected)) {
7
+ actualMarble = Marblizer.marblize(actual);
8
+ expectedMarble = Marblizer.marblize(expected);
9
+ }
10
+ const pass = this.equals(actualMarble, expectedMarble);
11
+ const message = pass ? () => this.utils.matcherHint('.not.toBeNotifications') +
12
+ '\n\n' +
13
+ `Expected notifications to not be:\n` +
14
+ ` ${this.utils.printExpected(expectedMarble)}\n` +
15
+ `But got:\n` +
16
+ ` ${this.utils.printReceived(actualMarble)}` : () => {
17
+ const diffString = this.utils.diff(expectedMarble, actualMarble, {
18
+ expand: true,
19
+ });
20
+ return (this.utils.matcherHint('.toBeNotifications') +
21
+ '\n\n' +
22
+ `Expected notifications to be:\n` +
23
+ ` ${this.utils.printExpected(expectedMarble)}\n` +
24
+ `But got:\n` +
25
+ ` ${this.utils.printReceived(actualMarble)}` +
26
+ (diffString ? `\n\nDifference:\n\n${diffString}` : ''));
27
+ };
28
+ return {
29
+ pass,
30
+ message
31
+ };
32
+ },
33
+ toBeSubscriptions(actual, expected) {
34
+ const actualMarbleArray = Marblizer.marblizeSubscriptions(actual);
35
+ const expectedMarbleArray = Marblizer.marblizeSubscriptions(expected);
36
+ const pass = subscriptionsPass(actualMarbleArray, expectedMarbleArray);
37
+ const message = pass ? () => this.utils.matcherHint('.not.toHaveSubscriptions') +
38
+ '\n\n' +
39
+ `Expected observable to not have the following subscription points:\n` +
40
+ ` ${this.utils.printExpected(expectedMarbleArray)}\n` +
41
+ `But got:\n` +
42
+ ` ${this.utils.printReceived(actualMarbleArray)}` : () => {
43
+ const diffString = this.utils.diff(expectedMarbleArray, actualMarbleArray, {
44
+ expand: true,
45
+ });
46
+ return (this.utils.matcherHint('.toHaveSubscriptions') +
47
+ '\n\n' +
48
+ `Expected observable to have the following subscription points:\n` +
49
+ ` ${this.utils.printExpected(expectedMarbleArray)}\n` +
50
+ `But got:\n` +
51
+ ` ${this.utils.printReceived(actualMarbleArray)}` +
52
+ (diffString ? `\n\nDifference:\n\n${diffString}` : ''));
53
+ };
54
+ return {
55
+ pass,
56
+ message
57
+ };
58
+ },
59
+ toHaveEmptySubscriptions(actual) {
60
+ const pass = !(actual && actual.length > 0);
61
+ let marbles;
62
+ if (actual && actual.length > 0)
63
+ marbles = Marblizer.marblizeSubscriptions(actual);
64
+ const message = pass ? () => this.utils.matcherHint('.not.toHaveNoSubscriptions') +
65
+ '\n\n' +
66
+ `Expected observable to have at least one subscription point, but got nothing` +
67
+ this.utils.printReceived('') : () => this.utils.matcherHint('.toHaveNoSubscriptions') +
68
+ '\n\n' +
69
+ `Expected observable to have no subscription points\n` +
70
+ `But got:\n` +
71
+ ` ${this.utils.printReceived(marbles)}\n\n`;
72
+ return {
73
+ pass,
74
+ message
75
+ };
76
+ }
77
+ };
78
+ expect.extend(customTestMatchers);
79
+ function canMarblize(...messages) {
80
+ return messages.every(isMessagesMarblizable);
81
+ }
82
+ function isMessagesMarblizable(messages) {
83
+ return messages.every(({ notification }) => notification.kind === 'C' ||
84
+ (notification.kind === 'E' && notification.error === 'error') ||
85
+ (notification.kind === 'N' && isCharacter(notification.value)));
86
+ }
87
+ function isCharacter(value) {
88
+ return typeof value === 'string' && value.length === 1 || value !== undefined && JSON.stringify(value).length === 1;
89
+ }
90
+ function subscriptionsPass(actualMarbleArray, expectedMarbleArray) {
91
+ if (actualMarbleArray.length !== expectedMarbleArray.length) {
92
+ return false;
93
+ }
94
+ let pass = true;
95
+ for (const actualMarble of actualMarbleArray) {
96
+ if (!expectedMarbleArray.includes(actualMarble)) {
97
+ pass = false;
98
+ break;
99
+ }
100
+ }
101
+ return pass;
102
+ }
103
+ //# sourceMappingURL=custom-matchers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-matchers.js","sourceRoot":"","sources":["../../../src/vitest/custom-matchers.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@granito/vitest-marbles",
3
+ "version": "1.0.0-dev.1",
4
+ "description": "Marble testing helpers library for RxJs and Vitest",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/granito-source/vitest-marbles.git"
8
+ },
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "vitest",
12
+ "marbles",
13
+ "marble",
14
+ "testing",
15
+ "test",
16
+ "rxjs",
17
+ "observable"
18
+ ],
19
+ "engines": {
20
+ "node": ">=22",
21
+ "npm": ">=10"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "test": "vitest --no-watch"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "tag": "latest"
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "type": "module",
35
+ "exports": {
36
+ "./package.json": {
37
+ "default": "./package.json"
38
+ },
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "default": "./dist/index.js"
42
+ }
43
+ },
44
+ "module": "./dist/index.js",
45
+ "typings": "./dist/index.d.ts",
46
+ "sideEffects": false,
47
+ "devDependencies": {
48
+ "jsdom": "^27.2.0",
49
+ "rxjs": "^7.8.2",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^4.0.13"
52
+ },
53
+ "peerDependencies": {
54
+ "rxjs": "^7.0.0",
55
+ "vitest": "^4.0.0"
56
+ }
57
+ }