vitest-websocket-mock 0.6.0 → 0.7.0
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.
- package/README.md +31 -5
- package/dist/index.d.ts +45 -66
- package/dist/index.js +231 -257
- package/package.json +11 -18
package/README.md
CHANGED
|
@@ -80,6 +80,11 @@ A `WS` instance has the following attributes:
|
|
|
80
80
|
new message. The resolved value is the received message (deserialized as a
|
|
81
81
|
JavaScript Object if the `WS` was instantiated with the `{ jsonProtocol: true }`
|
|
82
82
|
option).
|
|
83
|
+
- `messages`: an array that synchronously and cumulatively records every
|
|
84
|
+
message received by the `WS` instance, in the order they were received.
|
|
85
|
+
Since it's updated synchronously, it can be used to assert that no message
|
|
86
|
+
has been received without waiting on a timeout (see
|
|
87
|
+
[Run assertions on received messages](#run-assertions-on-received-messages)).
|
|
83
88
|
|
|
84
89
|
### Methods on a `WS` instance
|
|
85
90
|
|
|
@@ -102,6 +107,12 @@ on received messages easier:
|
|
|
102
107
|
- `.toHaveReceivedMessages`: synchronous matcher that checks that all the
|
|
103
108
|
expected messages have been received by the mock websocket server.
|
|
104
109
|
|
|
110
|
+
**Note**: `.toHaveReceivedMessages([])` always passes, since it only checks
|
|
111
|
+
that every expected message is included in the received messages, and an
|
|
112
|
+
empty list of expected messages is trivially satisfied. To assert that _no_
|
|
113
|
+
message has been received, check `server.messages` directly instead (see
|
|
114
|
+
below).
|
|
115
|
+
|
|
105
116
|
### Run assertions on messages as they are received by the mock server
|
|
106
117
|
|
|
107
118
|
```js
|
|
@@ -116,6 +127,21 @@ test('the server keeps track of received messages, and yields them as they come
|
|
|
116
127
|
});
|
|
117
128
|
```
|
|
118
129
|
|
|
130
|
+
### Assert that a message has not been received
|
|
131
|
+
|
|
132
|
+
`server.messages` is updated synchronously, so it can be checked immediately
|
|
133
|
+
without waiting for a timeout:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
test('asserts that no message has been received', async () => {
|
|
137
|
+
const server = new WS('ws://localhost:1234');
|
|
138
|
+
const client = new WebSocket('ws://localhost:1234');
|
|
139
|
+
|
|
140
|
+
await server.connected;
|
|
141
|
+
expect(server.messages).toEqual([]);
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
119
145
|
### Send messages to the connected clients
|
|
120
146
|
|
|
121
147
|
```js
|
|
@@ -177,7 +203,7 @@ This can be used to test behaviour for a client that connects to a WebSocket ser
|
|
|
177
203
|
```js
|
|
178
204
|
test('rejects connections that fail the verifyClient option', async () => {
|
|
179
205
|
new WS('ws://localhost:1234', { verifyClient: () => false });
|
|
180
|
-
const errorCallback =
|
|
206
|
+
const errorCallback = vi.fn();
|
|
181
207
|
|
|
182
208
|
await expect(
|
|
183
209
|
new Promise((resolve, reject) => {
|
|
@@ -200,7 +226,7 @@ This can be used to test behaviour for a client that connects to a WebSocket ser
|
|
|
200
226
|
test('rejects connections that fail the selectProtocol option', async () => {
|
|
201
227
|
const selectProtocol = () => null;
|
|
202
228
|
new WS('ws://localhost:1234', { selectProtocol });
|
|
203
|
-
const errorCallback =
|
|
229
|
+
const errorCallback = vi.fn();
|
|
204
230
|
|
|
205
231
|
await expect(
|
|
206
232
|
new Promise((resolve, reject) => {
|
|
@@ -256,7 +282,7 @@ it('the server can refuse connections', async () => {
|
|
|
256
282
|
});
|
|
257
283
|
|
|
258
284
|
const client = new WebSocket('ws://localhost:1234');
|
|
259
|
-
client.onclose = (event
|
|
285
|
+
client.onclose = (event) => {
|
|
260
286
|
expect(event.code).toBe(1003);
|
|
261
287
|
expect(event.wasClean).toBe(false);
|
|
262
288
|
expect(event.reason).toBe('NOPE');
|
|
@@ -292,7 +318,7 @@ afterEach(() => {
|
|
|
292
318
|
|
|
293
319
|
`mock-socket` has a strong usage of delays (`setTimeout` to be more specific). This means using `vi.useFakeTimers();` will cause issues such as the client appearing to never connect to the server.
|
|
294
320
|
|
|
295
|
-
While running the websocket server from tests within the
|
|
321
|
+
While running the websocket server from tests within the jsdom environment (as opposed to node)
|
|
296
322
|
you may see errors of the nature:
|
|
297
323
|
|
|
298
324
|
```bash
|
|
@@ -306,7 +332,7 @@ adding `require('setimmediate');` to your `setupTests.js`.
|
|
|
306
332
|
## Testing React applications
|
|
307
333
|
|
|
308
334
|
When testing React applications, `vitest-websocket-mock` will look for
|
|
309
|
-
`@testing-library/react`'s implementation of [`act`](https://
|
|
335
|
+
`@testing-library/react`'s implementation of [`act`](https://react.dev/reference/react/act).
|
|
310
336
|
If it is available, it will wrap all the necessary calls in `act`, so you don't have to.
|
|
311
337
|
|
|
312
338
|
If `@testing-library/react` is not available, we will assume that you're not testing a React application,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,88 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { RawMatcherFn } from
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @copyright Romain Bertrand 2018
|
|
6
|
-
* @copyright Akiomi Kamakura 2023
|
|
7
|
-
*/
|
|
8
|
-
|
|
1
|
+
import { Client, CloseOptions, Server, ServerOptions } from "mock-socket";
|
|
2
|
+
import { RawMatcherFn } from "@vitest/expect";
|
|
3
|
+
//#region src/derivers/deriveToHaveReceivedMessage.d.ts
|
|
9
4
|
declare function deriveToHaveReceivedMessage(name: string, fn: RawMatcherFn): RawMatcherFn;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* @copyright Romain Bertrand 2018
|
|
13
|
-
* @copyright Akiomi Kamakura 2023
|
|
14
|
-
*/
|
|
15
|
-
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/derivers/deriveToReceiveMessage.d.ts
|
|
16
7
|
declare function deriveToReceiveMessage(name: string, fn: RawMatcherFn): RawMatcherFn;
|
|
17
|
-
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/derivers/types.d.ts
|
|
18
10
|
/**
|
|
19
11
|
* @copyright Romain Bertrand 2018
|
|
20
12
|
* @copyright Akiomi Kamakura 2023
|
|
21
13
|
*/
|
|
22
14
|
interface ReceiveMessageOptions {
|
|
23
|
-
|
|
15
|
+
timeout?: number;
|
|
24
16
|
}
|
|
25
|
-
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/queue.d.ts
|
|
26
19
|
/**
|
|
27
20
|
* @copyright Romain Bertrand 2018
|
|
28
21
|
*/
|
|
29
22
|
declare class Queue<ItemT> {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
pendingItems: Array<ItemT>;
|
|
24
|
+
nextItemResolver: () => void;
|
|
25
|
+
nextItem: Promise<void>;
|
|
26
|
+
put(item: ItemT): void;
|
|
27
|
+
get(): Promise<ItemT>;
|
|
35
28
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* @copyright Romain Bertrand 2018
|
|
39
|
-
* @copyright Akiomi Kamakura 2023
|
|
40
|
-
*/
|
|
41
|
-
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/websocket.d.ts
|
|
42
31
|
interface WSOptions extends ServerOptions {
|
|
43
|
-
|
|
32
|
+
jsonProtocol?: boolean;
|
|
44
33
|
}
|
|
45
34
|
type DeserializedMessage<TMessage = object> = string | TMessage;
|
|
46
35
|
declare class WS {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
36
|
+
server: Server;
|
|
37
|
+
serializer: (deserializedMessage: DeserializedMessage) => string;
|
|
38
|
+
deserializer: (message: string) => DeserializedMessage;
|
|
39
|
+
static instances: Array<WS>;
|
|
40
|
+
messages: Array<DeserializedMessage>;
|
|
41
|
+
messagesToConsume: Queue<unknown>;
|
|
42
|
+
private _isConnected;
|
|
43
|
+
private _isClosed;
|
|
44
|
+
static clean(): void;
|
|
45
|
+
constructor(url: string, opts?: WSOptions);
|
|
46
|
+
get connected(): Promise<Client>;
|
|
47
|
+
get closed(): Promise<void>;
|
|
48
|
+
get nextMessage(): Promise<unknown>;
|
|
49
|
+
on(eventName: 'connection' | 'message' | 'close', callback: (socket: Client) => void): void;
|
|
50
|
+
send(message: DeserializedMessage): void;
|
|
51
|
+
close(options?: CloseOptions): void;
|
|
52
|
+
error(options?: CloseOptions): void;
|
|
64
53
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
* @copyright Romain Bertrand 2018
|
|
68
|
-
* @copyright Akiomi Kamakura 2023
|
|
69
|
-
*/
|
|
70
|
-
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/matchers/index.d.ts
|
|
71
56
|
interface CustomMatchers<R = unknown> {
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
toReceiveMessage<TMessage = object>(message: DeserializedMessage<TMessage>, options?: ReceiveMessageOptions): Promise<R>;
|
|
58
|
+
toHaveReceivedMessages<TMessage = object>(messages: Array<DeserializedMessage<TMessage>>): R;
|
|
74
59
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* @copyright Romain Bertrand 2018
|
|
78
|
-
* @copyright Akiomi Kamakura 2023
|
|
79
|
-
*/
|
|
80
|
-
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/extend-expect.d.ts
|
|
81
62
|
declare module '@vitest/expect' {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
interface AsymmetricMatchersContaining extends CustomMatchers {
|
|
85
|
-
}
|
|
63
|
+
interface Assertion<T = any> extends CustomMatchers<T> {}
|
|
64
|
+
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
|
86
65
|
}
|
|
87
|
-
|
|
88
|
-
export { type ReceiveMessageOptions, WS, WS as default, deriveToHaveReceivedMessage, deriveToReceiveMessage };
|
|
66
|
+
//#endregion
|
|
67
|
+
export { type ReceiveMessageOptions, WS, WS as default, deriveToHaveReceivedMessage, deriveToReceiveMessage };
|
package/dist/index.js
CHANGED
|
@@ -1,280 +1,254 @@
|
|
|
1
|
-
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// src/extend-expect.ts
|
|
1
|
+
import { createRequire } from "node:module";
|
|
14
2
|
import { expect } from "vitest";
|
|
15
|
-
|
|
16
|
-
// src/matchers/index.ts
|
|
17
|
-
var matchers_exports = {};
|
|
18
|
-
__export(matchers_exports, {
|
|
19
|
-
toHaveReceivedMessages: () => toHaveReceivedMessages_default,
|
|
20
|
-
toReceiveMessage: () => toReceiveMessage_default
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// src/websocket.ts
|
|
24
3
|
import { Server } from "mock-socket";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
var
|
|
4
|
+
//#region \0rolldown/runtime.js
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __exportAll = (all, no_symbols) => {
|
|
7
|
+
let target = {};
|
|
8
|
+
for (var name in all) __defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true
|
|
11
|
+
});
|
|
12
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
13
|
+
return target;
|
|
14
|
+
};
|
|
15
|
+
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/act-compat.ts
|
|
18
|
+
let act;
|
|
28
19
|
try {
|
|
29
|
-
|
|
20
|
+
act = __require("@testing-library/react").act;
|
|
30
21
|
} catch (_) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
act = (callback) => {
|
|
23
|
+
callback();
|
|
24
|
+
};
|
|
34
25
|
}
|
|
35
26
|
var act_compat_default = act;
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/queue.ts
|
|
29
|
+
/**
|
|
30
|
+
* @copyright Romain Bertrand 2018
|
|
31
|
+
*/
|
|
38
32
|
var Queue = class {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return nextItemPromise;
|
|
58
|
-
}
|
|
33
|
+
pendingItems = [];
|
|
34
|
+
nextItemResolver;
|
|
35
|
+
nextItem = new Promise((done) => this.nextItemResolver = done);
|
|
36
|
+
put(item) {
|
|
37
|
+
this.pendingItems.push(item);
|
|
38
|
+
this.nextItemResolver();
|
|
39
|
+
this.nextItem = new Promise((done) => this.nextItemResolver = done);
|
|
40
|
+
}
|
|
41
|
+
get() {
|
|
42
|
+
const item = this.pendingItems.shift();
|
|
43
|
+
if (item) return Promise.resolve(item);
|
|
44
|
+
let resolver;
|
|
45
|
+
const nextItemPromise = new Promise((done) => resolver = done);
|
|
46
|
+
this.nextItem.then(() => {
|
|
47
|
+
resolver(this.pendingItems.shift());
|
|
48
|
+
});
|
|
49
|
+
return nextItemPromise;
|
|
50
|
+
}
|
|
59
51
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/websocket.ts
|
|
54
|
+
/**
|
|
55
|
+
* @copyright Romain Bertrand 2018
|
|
56
|
+
* @copyright Akiomi Kamakura 2023
|
|
57
|
+
*/
|
|
58
|
+
const identity = (x) => x;
|
|
59
|
+
var WS = class WS {
|
|
60
|
+
server;
|
|
61
|
+
serializer;
|
|
62
|
+
deserializer;
|
|
63
|
+
static instances = [];
|
|
64
|
+
messages = [];
|
|
65
|
+
messagesToConsume = new Queue();
|
|
66
|
+
_isConnected;
|
|
67
|
+
_isClosed;
|
|
68
|
+
static clean() {
|
|
69
|
+
WS.instances.forEach((instance) => {
|
|
70
|
+
instance.close();
|
|
71
|
+
instance.messages = [];
|
|
72
|
+
});
|
|
73
|
+
WS.instances = [];
|
|
74
|
+
}
|
|
75
|
+
constructor(url, opts = {}) {
|
|
76
|
+
WS.instances.push(this);
|
|
77
|
+
const { jsonProtocol = false, ...serverOptions } = opts;
|
|
78
|
+
this.serializer = jsonProtocol ? JSON.stringify : identity;
|
|
79
|
+
this.deserializer = jsonProtocol ? JSON.parse : identity;
|
|
80
|
+
let connectionResolver;
|
|
81
|
+
let closedResolver;
|
|
82
|
+
this._isConnected = new Promise((done) => connectionResolver = done);
|
|
83
|
+
this._isClosed = new Promise((done) => closedResolver = done);
|
|
84
|
+
this.server = new Server(url, serverOptions);
|
|
85
|
+
this.server.on("close", closedResolver);
|
|
86
|
+
this.server.on("connection", (socket) => {
|
|
87
|
+
connectionResolver(socket);
|
|
88
|
+
socket.on("message", (message) => {
|
|
89
|
+
const parsedMessage = this.deserializer(message);
|
|
90
|
+
this.messages.push(parsedMessage);
|
|
91
|
+
this.messagesToConsume.put(parsedMessage);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
get connected() {
|
|
96
|
+
let resolve;
|
|
97
|
+
const connectedPromise = new Promise((done) => resolve = done);
|
|
98
|
+
const waitForConnected = async () => {
|
|
99
|
+
await act_compat_default(async () => {
|
|
100
|
+
await this._isConnected;
|
|
101
|
+
});
|
|
102
|
+
resolve(await this._isConnected);
|
|
103
|
+
};
|
|
104
|
+
waitForConnected();
|
|
105
|
+
return connectedPromise;
|
|
106
|
+
}
|
|
107
|
+
get closed() {
|
|
108
|
+
let resolve;
|
|
109
|
+
const closedPromise = new Promise((done) => resolve = done);
|
|
110
|
+
const waitForclosed = async () => {
|
|
111
|
+
await act_compat_default(async () => {
|
|
112
|
+
await this._isClosed;
|
|
113
|
+
});
|
|
114
|
+
await this._isClosed;
|
|
115
|
+
resolve();
|
|
116
|
+
};
|
|
117
|
+
waitForclosed();
|
|
118
|
+
return closedPromise;
|
|
119
|
+
}
|
|
120
|
+
get nextMessage() {
|
|
121
|
+
return this.messagesToConsume.get();
|
|
122
|
+
}
|
|
123
|
+
on(eventName, callback) {
|
|
124
|
+
this.server.on(eventName, callback);
|
|
125
|
+
}
|
|
126
|
+
send(message) {
|
|
127
|
+
act_compat_default(() => {
|
|
128
|
+
this.server.emit("message", this.serializer(message));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
close(options) {
|
|
132
|
+
act_compat_default(() => {
|
|
133
|
+
this.server.close(options);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
error(options) {
|
|
137
|
+
act_compat_default(() => {
|
|
138
|
+
this.server.emit("error", null);
|
|
139
|
+
});
|
|
140
|
+
this.server.close(options);
|
|
141
|
+
}
|
|
146
142
|
};
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/matcherUtils.ts
|
|
145
|
+
/**
|
|
146
|
+
* Plain-text replacements for `this.utils.matcherHint` / `printExpected` /
|
|
147
|
+
* `printReceived`. Matcher messages must not contain ANSI color codes, so
|
|
148
|
+
* that color detection (e.g. AI-agent environments) cannot make them differ
|
|
149
|
+
* between environments; the reporter renders its own colored diff from the
|
|
150
|
+
* `actual` / `expected` returned by matchers.
|
|
151
|
+
*/
|
|
152
|
+
function matcherHint(matcher, isNot = false) {
|
|
153
|
+
return `expect(WS).${isNot ? "not." : ""}${matcher}(expected)`;
|
|
154
|
+
}
|
|
155
|
+
const SPACE_SYMBOL = "·";
|
|
156
|
+
function replaceTrailingSpaces(text) {
|
|
157
|
+
return text.replace(/\s+$/gm, (spaces) => SPACE_SYMBOL.repeat(spaces.length));
|
|
158
|
+
}
|
|
159
|
+
function printValue(value) {
|
|
160
|
+
return replaceTrailingSpaces(this.utils.stringify(value));
|
|
161
|
+
}
|
|
162
|
+
function formatComparison(hint, expectedLabel, expected, receivedLabel, received) {
|
|
163
|
+
return hint + `
|
|
147
164
|
|
|
148
|
-
|
|
165
|
+
${expectedLabel}\n ${printValue.call(this, expected)}\n${receivedLabel}\n ${printValue.call(this, received)}`;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/derivers/makeInvalidWsMessage.ts
|
|
149
169
|
function makeInvalidWsMessage(ws, matcher) {
|
|
150
|
-
|
|
170
|
+
return matcherHint(matcher, this.isNot) + `
|
|
151
171
|
|
|
152
172
|
Expected the websocket object to be a valid WS mock.
|
|
153
|
-
Received: ${typeof ws}
|
|
154
|
-
${this.utils.printReceived(ws)}`;
|
|
173
|
+
Received: ${typeof ws}\n ${printValue.call(this, ws)}`;
|
|
155
174
|
}
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/derivers/deriveToHaveReceivedMessage.ts
|
|
158
177
|
function deriveToHaveReceivedMessage(name, fn) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
return fn.call(this, ws.messages, expected, options);
|
|
169
|
-
};
|
|
178
|
+
return function(ws, expected, options) {
|
|
179
|
+
if (!(ws instanceof WS)) return {
|
|
180
|
+
pass: this.isNot,
|
|
181
|
+
message: makeInvalidWsMessage.bind(this, ws, name)
|
|
182
|
+
};
|
|
183
|
+
return fn.call(this, ws.messages, expected, options);
|
|
184
|
+
};
|
|
170
185
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/derivers/deriveToReceiveMessage.ts
|
|
188
|
+
const WAIT_DELAY = 1e3;
|
|
189
|
+
const TIMEOUT = Symbol("timeout");
|
|
175
190
|
function deriveToReceiveMessage(name, fn) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const messageOrTimeout = await Promise.race([
|
|
187
|
-
ws.nextMessage,
|
|
188
|
-
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), waitDelay))
|
|
189
|
-
]);
|
|
190
|
-
if (messageOrTimeout === TIMEOUT) {
|
|
191
|
-
return {
|
|
192
|
-
pass: this.isNot,
|
|
193
|
-
// always fail
|
|
194
|
-
message: () => this.utils.matcherHint(`${this.isNot ? ".not" : ""}.${name}`, "WS", "expected") + `
|
|
191
|
+
return async function(ws, expected, options) {
|
|
192
|
+
if (!(ws instanceof WS)) return {
|
|
193
|
+
pass: this.isNot,
|
|
194
|
+
message: makeInvalidWsMessage.bind(this, ws, name)
|
|
195
|
+
};
|
|
196
|
+
const waitDelay = options?.timeout ?? WAIT_DELAY;
|
|
197
|
+
const messageOrTimeout = await Promise.race([ws.nextMessage, new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), waitDelay))]);
|
|
198
|
+
if (messageOrTimeout === TIMEOUT) return {
|
|
199
|
+
pass: this.isNot,
|
|
200
|
+
message: () => matcherHint(name, this.isNot) + `
|
|
195
201
|
|
|
196
202
|
Expected the websocket server to receive a message,
|
|
197
203
|
but it didn't receive anything in ${waitDelay}ms.`
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
};
|
|
205
|
+
else {
|
|
206
|
+
const received = messageOrTimeout;
|
|
207
|
+
return Promise.resolve(fn.call(this, received, expected, options));
|
|
208
|
+
}
|
|
209
|
+
};
|
|
204
210
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Expected the WS server to not have received the following messages:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
${this.utils.printReceived(received)}` : () => {
|
|
223
|
-
return this.utils.matcherHint(".toHaveReceivedMessages", "WS", "expected") + `
|
|
224
|
-
|
|
225
|
-
Expected the WS server to have received the following messages:
|
|
226
|
-
${this.utils.printExpected(expected)}
|
|
227
|
-
Received:
|
|
228
|
-
${this.utils.printReceived(received)}
|
|
229
|
-
|
|
230
|
-
`;
|
|
231
|
-
};
|
|
232
|
-
return {
|
|
233
|
-
actual: received,
|
|
234
|
-
expected,
|
|
235
|
-
message,
|
|
236
|
-
pass
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
var toHaveReceivedMessages_default = toHaveReceivedMessages;
|
|
241
|
-
|
|
242
|
-
// src/matchers/toReceiveMessage.ts
|
|
243
|
-
import { diff } from "@vitest/utils/diff";
|
|
244
|
-
var toReceiveMessage = deriveToReceiveMessage("toReceiveMessage", function(received, expected) {
|
|
245
|
-
const pass = this.equals(received, expected);
|
|
246
|
-
const message = pass ? () => this.utils.matcherHint(".not.toReceiveMessage", "WS", "expected") + `
|
|
247
|
-
|
|
248
|
-
Expected the next received message to not equal:
|
|
249
|
-
${this.utils.printExpected(expected)}
|
|
250
|
-
Received:
|
|
251
|
-
${this.utils.printReceived(received)}` : () => {
|
|
252
|
-
const diffString = diff(expected, received, { expand: this.expand });
|
|
253
|
-
return this.utils.matcherHint(".toReceiveMessage", "WS", "expected") + `
|
|
254
|
-
|
|
255
|
-
Expected the next received message to equal:
|
|
256
|
-
${this.utils.printExpected(expected)}
|
|
257
|
-
Received:
|
|
258
|
-
${this.utils.printReceived(received)}
|
|
259
|
-
|
|
260
|
-
Difference:
|
|
261
|
-
|
|
262
|
-
${diffString}`;
|
|
263
|
-
};
|
|
264
|
-
return {
|
|
265
|
-
actual: received,
|
|
266
|
-
expected,
|
|
267
|
-
message,
|
|
268
|
-
pass
|
|
269
|
-
};
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/derivers/index.ts
|
|
213
|
+
/**
|
|
214
|
+
* @copyright Romain Bertrand 2018
|
|
215
|
+
* @copyright Akiomi Kamakura 2023
|
|
216
|
+
*/
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/matchers/toHaveReceivedMessages.ts
|
|
219
|
+
const toHaveReceivedMessages = deriveToHaveReceivedMessage("toHaveReceivedMessages", function(received, expected) {
|
|
220
|
+
const equalities = expected.map((expectedMsg) => received.some((receivedMsg) => this.equals(receivedMsg, expectedMsg)));
|
|
221
|
+
const pass = this.isNot ? equalities.some(Boolean) : equalities.every(Boolean);
|
|
222
|
+
return {
|
|
223
|
+
actual: received,
|
|
224
|
+
expected,
|
|
225
|
+
message: pass ? () => formatComparison.call(this, matcherHint("toHaveReceivedMessages", true), "Expected the WS server to not have received the following messages:", expected, "But it received:", received) : () => formatComparison.call(this, matcherHint("toHaveReceivedMessages"), "Expected the WS server to have received the following messages:", expected, "Received:", received),
|
|
226
|
+
pass
|
|
227
|
+
};
|
|
270
228
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/matchers/toReceiveMessage.ts
|
|
231
|
+
const toReceiveMessage = deriveToReceiveMessage("toReceiveMessage", function(received, expected) {
|
|
232
|
+
const pass = this.equals(received, expected);
|
|
233
|
+
return {
|
|
234
|
+
actual: received,
|
|
235
|
+
expected,
|
|
236
|
+
message: pass ? () => formatComparison.call(this, matcherHint("toReceiveMessage", true), "Expected the next received message to not equal:", expected, "Received:", received) : () => formatComparison.call(this, matcherHint("toReceiveMessage"), "Expected the next received message to equal:", expected, "Received:", received),
|
|
237
|
+
pass
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/matchers/index.ts
|
|
242
|
+
var matchers_exports = /* @__PURE__ */ __exportAll({
|
|
243
|
+
toHaveReceivedMessages: () => toHaveReceivedMessages,
|
|
244
|
+
toReceiveMessage: () => toReceiveMessage
|
|
245
|
+
});
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/extend-expect.ts
|
|
248
|
+
/**
|
|
249
|
+
* @copyright Romain Bertrand 2018
|
|
250
|
+
* @copyright Akiomi Kamakura 2023
|
|
251
|
+
*/
|
|
274
252
|
expect.extend(matchers_exports);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
WS as default,
|
|
278
|
-
deriveToHaveReceivedMessage,
|
|
279
|
-
deriveToReceiveMessage
|
|
280
|
-
};
|
|
253
|
+
//#endregion
|
|
254
|
+
export { WS, WS as default, deriveToHaveReceivedMessage, deriveToReceiveMessage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-websocket-mock",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Mock websockets and assert complex websocket interactions with Vitest",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"clean": "rimraf dist",
|
|
20
20
|
"prebuild": "npm run clean",
|
|
21
|
-
"build": "
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"build": "tsdown",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"lint": "biome check .",
|
|
24
|
+
"format": "biome check --write .",
|
|
24
25
|
"prepublishOnly": "npm run build",
|
|
25
26
|
"test": "vitest --run",
|
|
26
27
|
"test:watch": "vitest"
|
|
@@ -34,27 +35,19 @@
|
|
|
34
35
|
"author": "Akiomi Kamakura",
|
|
35
36
|
"license": "MIT",
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"@vitest/coverage-v8": "4.1.
|
|
40
|
-
"eslint": "^8.38.0",
|
|
41
|
-
"eslint-config-prettier": "^8.5.0",
|
|
42
|
-
"eslint-config-react-app": "^7.0.1",
|
|
43
|
-
"eslint-plugin-react-hooks": "^4.6.0",
|
|
44
|
-
"eslint-plugin-react-refresh": "^0.3.4",
|
|
45
|
-
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
46
|
-
"prettier": "^2.0.2",
|
|
38
|
+
"@biomejs/biome": "^2.5.3",
|
|
39
|
+
"@types/node": "^26.1.1",
|
|
40
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
47
41
|
"rimraf": "^4.1.2",
|
|
48
|
-
"
|
|
49
|
-
"typescript": "^
|
|
42
|
+
"tsdown": "^0.22.5",
|
|
43
|
+
"typescript": "^7.0.2",
|
|
50
44
|
"vite": "^8.1.4",
|
|
51
|
-
"vitest": "4.1.
|
|
45
|
+
"vitest": "^4.1.10"
|
|
52
46
|
},
|
|
53
47
|
"peerDependencies": {
|
|
54
48
|
"vitest": ">=4"
|
|
55
49
|
},
|
|
56
50
|
"dependencies": {
|
|
57
|
-
"@vitest/utils": "4.1.1",
|
|
58
51
|
"mock-socket": "^9.2.1"
|
|
59
52
|
},
|
|
60
53
|
"files": [
|