Skip to content

Commit 0a71a93

Browse files
feature/more-tests (#18)
* test: added more tests * chore: fix eslint * chore: add license * chore: license formatting
1 parent fc9d4a6 commit 0a71a93

File tree

5 files changed

+192
-15
lines changed

5 files changed

+192
-15
lines changed

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Pasecinic Nichita
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

src/core/web-socket-connector.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -183,28 +183,29 @@ export class WebSocketConnector {
183183
const requests$ = new BehaviorSubject<
184184
undefined | SendRequestParams<TEvent, TRes, TReqIn, TReqOut>
185185
>(undefined);
186+
186187
const uninitializedValue: StreamResponse<TRes, TReqOut, TErr> = {
187188
status: STREAM_STATUS.uninitialized,
188189
response: defaultResponse,
189190
};
190191
const $ = new BehaviorSubject<StreamResponse<TRes, TReqOut, TErr>>(uninitializedValue);
191-
const userRequests$ = requests$.pipe(
192+
193+
const transformedRequests$ = requests$.pipe(
192194
filterNullAndUndefined(),
193195
transformRequests,
194196
shareReplay(1),
195197
);
196198

197-
userRequests$
199+
transformedRequests$
198200
.pipe(
199-
concatMap((currentProcessingRequest) => {
201+
concatMap((params) => {
200202
const defaultTransformResponse = (() => identity) as TransformResponse<
201203
TEvent,
202204
TRes,
203205
TReqOut
204206
>;
205207

206-
const { request, transformResponse = defaultTransformResponse } =
207-
currentProcessingRequest;
208+
const { request, transformResponse = defaultTransformResponse } = params;
208209

209210
const ready$ = this.messages<TEvent>().pipe(
210211
transformResponse(request),
@@ -233,8 +234,10 @@ export class WebSocketConnector {
233234
);
234235

235236
const newRequest$ = defer(() => {
236-
const nextRequest$ = userRequests$.pipe(
237-
filter((x) => x !== currentProcessingRequest),
237+
const nextRequest$ = transformedRequests$.pipe(
238+
// ignore requests that are currently in processing
239+
// transformedRequests$ is multicasted - so it will always emit the last client request
240+
filter((x) => x !== params),
238241
take(1),
239242
);
240243
return nextRequest$.pipe(
@@ -272,8 +275,8 @@ export class WebSocketConnector {
272275
);
273276

274277
const concat$ = concat(loading$, ready$.pipe(takeUntil(takeUntil$))).pipe(
275-
tap((value) => {
276-
$.next({ ...$.value, ...value });
278+
tap((streamResponse) => {
279+
$.next({ ...$.value, ...streamResponse });
277280
}),
278281
);
279282

@@ -291,9 +294,6 @@ export class WebSocketConnector {
291294
requests$.next({ ...params });
292295
};
293296

294-
return {
295-
send,
296-
$,
297-
};
297+
return { send, $ };
298298
};
299299
}

src/test/get-stream-handler.spec.ts

+122-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { beforeEach, describe, expect, it } from 'vitest';
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
22
import { TestScheduler } from 'rxjs/testing';
33
import { getMockWebsocketConnector } from './get-mock-websocket.connector';
44
import { STREAM_STATUS } from '../core/constants';
5-
import { delay, filter, scan, tap } from 'rxjs';
5+
import { delay, filter, Observable, scan, tap } from 'rxjs';
6+
import { SendRequestParams } from '../core/types';
67

78
interface TestEvent {
89
from: string;
@@ -490,4 +491,123 @@ describe('[getStreamHandler] rxjs marbles tests', () => {
490491
expect(socket.send).toHaveBeenNthCalledWith(4, JSON.stringify({ from: 'b2' }));
491492
/* eslint-enable @typescript-eslint/unbound-method */
492493
});
494+
495+
it('does not reset response on next request `resetResponseOnNextRequest: false`', () => {
496+
const { wsConnector, socket } = getMockWebsocketConnector();
497+
498+
const handler = wsConnector.getStreamHandler<TestEvent, TestEvent, TestEvent, unknown>({
499+
resetResponseOnNextRequest: false,
500+
});
501+
const expectedMarbles = 'a(bc)de';
502+
const expectedValues = {
503+
a: {
504+
status: STREAM_STATUS.uninitialized,
505+
response: undefined,
506+
},
507+
b: {
508+
status: STREAM_STATUS.loading,
509+
request: { from: 'b' },
510+
response: undefined,
511+
error: undefined,
512+
},
513+
c: {
514+
status: STREAM_STATUS.ready,
515+
request: { from: 'b' },
516+
response: { from: 'b' },
517+
error: undefined,
518+
},
519+
d: {
520+
status: STREAM_STATUS.loading,
521+
request: { from: 'c' },
522+
response: { from: 'b' },
523+
error: undefined,
524+
},
525+
e: {
526+
status: STREAM_STATUS.ready,
527+
request: { from: 'c' },
528+
response: { from: 'd' },
529+
error: undefined,
530+
},
531+
};
532+
533+
const triggerMarbles = 'ab 3ms cd';
534+
const triggerValues = {
535+
a: () => {
536+
wsConnector.connect();
537+
socket.onopen!({} as Event);
538+
socket.send(JSON.stringify({ from: 'a' }));
539+
},
540+
b: () => {
541+
handler.send({ request: { from: 'b' } });
542+
socket.send(JSON.stringify({ from: 'b' }));
543+
},
544+
c: () => {
545+
handler.send({ request: { from: 'c' } });
546+
},
547+
d: () => {
548+
socket.send(JSON.stringify({ from: 'd' }));
549+
},
550+
};
551+
552+
testScheduler.run(({ expectObservable, cold }) => {
553+
expectObservable(handler.$).toBe(expectedMarbles, expectedValues);
554+
expectObservable(cold(triggerMarbles, triggerValues).pipe(tap((fn) => fn())));
555+
});
556+
});
557+
558+
it('applies `transformRequest` operator', () => {
559+
const { wsConnector, socket } = getMockWebsocketConnector();
560+
561+
const tapFn = vi.fn();
562+
const transformRequests = vi.fn(
563+
(source$: Observable<SendRequestParams<TestEvent, TestEvent, TestEvent>>) =>
564+
source$.pipe(delay(10), tap(tapFn)),
565+
);
566+
567+
const handler = wsConnector.getStreamHandler<TestEvent, TestEvent, TestEvent>({
568+
transformRequests,
569+
});
570+
const expectedMarbles = 'a 9ms bc';
571+
const expectedValues = {
572+
a: {
573+
status: STREAM_STATUS.uninitialized,
574+
response: undefined,
575+
},
576+
b: {
577+
status: STREAM_STATUS.loading,
578+
request: { from: 'a' },
579+
response: undefined,
580+
error: undefined,
581+
},
582+
c: {
583+
status: STREAM_STATUS.ready,
584+
request: { from: 'a' },
585+
response: { from: 'b' },
586+
error: undefined,
587+
},
588+
};
589+
590+
const request: SendRequestParams<TestEvent, TestEvent, TestEvent> = { request: { from: 'a' } };
591+
592+
const triggerMarbles = 'a 10ms b';
593+
const triggerValues = {
594+
a: () => {
595+
wsConnector.connect();
596+
socket.onopen!({} as Event);
597+
handler.send(request);
598+
socket.send(JSON.stringify({ from: 'a' })); // <- ignored
599+
},
600+
b: () => {
601+
socket.send(JSON.stringify({ from: 'b' }));
602+
},
603+
};
604+
605+
testScheduler.run(({ expectObservable, cold }) => {
606+
expectObservable(handler.$).toBe(expectedMarbles, expectedValues);
607+
expectObservable(cold(triggerMarbles, triggerValues).pipe(tap((fn) => fn())));
608+
});
609+
610+
expect(transformRequests).toHaveBeenCalledOnce();
611+
expect(tapFn).toHaveBeenNthCalledWith(1, request);
612+
});
493613
});

src/test/types.test-d.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { describe, it, expectTypeOf } from 'vitest';
2+
import { getMockWebsocketConnector } from './get-mock-websocket.connector';
3+
import { SendRequestParams, StreamResponse } from '../core/types';
4+
import { BehaviorSubject } from 'rxjs';
5+
6+
describe('[types]', () => {
7+
it('[getStreamHandler]', () => {
8+
const { wsConnector } = getMockWebsocketConnector();
9+
10+
interface WsEvent {
11+
id: number;
12+
data: string;
13+
}
14+
15+
interface Request {
16+
from: string;
17+
timestamp: number;
18+
}
19+
20+
const handler = wsConnector.getStreamHandler<WsEvent, WsEvent, Request>();
21+
22+
/* eslint-disable @typescript-eslint/unbound-method */
23+
expectTypeOf(handler.send)
24+
.parameter(0)
25+
.toMatchTypeOf<SendRequestParams<WsEvent, WsEvent, Request>>();
26+
27+
expectTypeOf(handler.$).toMatchTypeOf<
28+
BehaviorSubject<StreamResponse<WsEvent, Request, unknown>>
29+
>();
30+
/* eslint-enable @typescript-eslint/unbound-method */
31+
});
32+
});

vitest.config.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@ import { defineConfig } from 'vitest/config';
33
export default defineConfig({
44
test: {
55
environment: 'jsdom',
6+
typecheck: {
7+
enabled: true,
8+
include: ['src/**/*.{test,spec}-d.ts'],
9+
},
610
},
711
});

0 commit comments

Comments
 (0)