vitest-websocket-mock 0.5.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 +32 -6
- package/dist/index.d.ts +46 -67
- package/dist/index.js +231 -258
- package/package.json +17 -21
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
|
|
|
@@ -97,11 +102,17 @@ A `WS` instance has the following attributes:
|
|
|
97
102
|
on received messages easier:
|
|
98
103
|
|
|
99
104
|
- `.toReceiveMessage`: async matcher that waits for the next message received
|
|
100
|
-
by the
|
|
105
|
+
by the mock websocket server, and asserts its content. It will time out
|
|
101
106
|
with a helpful message after 1000ms.
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
declare module 'vitest' {
|
|
82
|
-
interface Assertion<T = any> extends CustomMatchers<T> {
|
|
83
|
-
}
|
|
84
|
-
interface AsymmetricMatchersContaining extends CustomMatchers {
|
|
85
|
-
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/extend-expect.d.ts
|
|
62
|
+
declare module '@vitest/expect' {
|
|
63
|
+
interface Assertion<T = any> extends CustomMatchers<T> {}
|
|
64
|
+
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
|
86
65
|
}
|
|
87
|
-
|
|
88
|
-
export { 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,281 +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")
|
|
6
|
-
return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// src/extend-expect.ts
|
|
1
|
+
import { createRequire } from "node:module";
|
|
15
2
|
import { expect } from "vitest";
|
|
16
|
-
|
|
17
|
-
// src/matchers/index.ts
|
|
18
|
-
var matchers_exports = {};
|
|
19
|
-
__export(matchers_exports, {
|
|
20
|
-
toHaveReceivedMessages: () => toHaveReceivedMessages_default,
|
|
21
|
-
toReceiveMessage: () => toReceiveMessage_default
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// src/websocket.ts
|
|
25
3
|
import { Server } from "mock-socket";
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
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;
|
|
29
19
|
try {
|
|
30
|
-
|
|
20
|
+
act = __require("@testing-library/react").act;
|
|
31
21
|
} catch (_) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
act = (callback) => {
|
|
23
|
+
callback();
|
|
24
|
+
};
|
|
35
25
|
}
|
|
36
26
|
var act_compat_default = act;
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/queue.ts
|
|
29
|
+
/**
|
|
30
|
+
* @copyright Romain Bertrand 2018
|
|
31
|
+
*/
|
|
39
32
|
var Queue = class {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return nextItemPromise;
|
|
59
|
-
}
|
|
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
|
+
}
|
|
60
51
|
};
|
|
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
|
-
|
|
146
|
-
|
|
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
|
+
}
|
|
147
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 + `
|
|
148
164
|
|
|
149
|
-
|
|
165
|
+
${expectedLabel}\n ${printValue.call(this, expected)}\n${receivedLabel}\n ${printValue.call(this, received)}`;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/derivers/makeInvalidWsMessage.ts
|
|
150
169
|
function makeInvalidWsMessage(ws, matcher) {
|
|
151
|
-
|
|
170
|
+
return matcherHint(matcher, this.isNot) + `
|
|
152
171
|
|
|
153
172
|
Expected the websocket object to be a valid WS mock.
|
|
154
|
-
Received: ${typeof ws}
|
|
155
|
-
${this.utils.printReceived(ws)}`;
|
|
173
|
+
Received: ${typeof ws}\n ${printValue.call(this, ws)}`;
|
|
156
174
|
}
|
|
157
|
-
|
|
158
|
-
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/derivers/deriveToHaveReceivedMessage.ts
|
|
159
177
|
function deriveToHaveReceivedMessage(name, fn) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
return fn.call(this, ws.messages, expected, options);
|
|
170
|
-
};
|
|
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
|
+
};
|
|
171
185
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/derivers/deriveToReceiveMessage.ts
|
|
188
|
+
const WAIT_DELAY = 1e3;
|
|
189
|
+
const TIMEOUT = Symbol("timeout");
|
|
176
190
|
function deriveToReceiveMessage(name, fn) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const messageOrTimeout = await Promise.race([
|
|
188
|
-
ws.nextMessage,
|
|
189
|
-
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), waitDelay))
|
|
190
|
-
]);
|
|
191
|
-
if (messageOrTimeout === TIMEOUT) {
|
|
192
|
-
return {
|
|
193
|
-
pass: this.isNot,
|
|
194
|
-
// always fail
|
|
195
|
-
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) + `
|
|
196
201
|
|
|
197
202
|
Expected the websocket server to receive a message,
|
|
198
203
|
but it didn't receive anything in ${waitDelay}ms.`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
};
|
|
205
|
+
else {
|
|
206
|
+
const received = messageOrTimeout;
|
|
207
|
+
return Promise.resolve(fn.call(this, received, expected, options));
|
|
208
|
+
}
|
|
209
|
+
};
|
|
205
210
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Expected the WS server to not have received the following messages:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
${this.utils.printReceived(received)}` : () => {
|
|
224
|
-
return this.utils.matcherHint(".toHaveReceivedMessages", "WS", "expected") + `
|
|
225
|
-
|
|
226
|
-
Expected the WS server to have received the following messages:
|
|
227
|
-
${this.utils.printExpected(expected)}
|
|
228
|
-
Received:
|
|
229
|
-
${this.utils.printReceived(received)}
|
|
230
|
-
|
|
231
|
-
`;
|
|
232
|
-
};
|
|
233
|
-
return {
|
|
234
|
-
actual: received,
|
|
235
|
-
expected,
|
|
236
|
-
message,
|
|
237
|
-
pass
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
);
|
|
241
|
-
var toHaveReceivedMessages_default = toHaveReceivedMessages;
|
|
242
|
-
|
|
243
|
-
// src/matchers/toReceiveMessage.ts
|
|
244
|
-
import { diff } from "@vitest/utils/diff";
|
|
245
|
-
var toReceiveMessage = deriveToReceiveMessage("toReceiveMessage", function(received, expected) {
|
|
246
|
-
const pass = this.equals(received, expected);
|
|
247
|
-
const message = pass ? () => this.utils.matcherHint(".not.toReceiveMessage", "WS", "expected") + `
|
|
248
|
-
|
|
249
|
-
Expected the next received message to not equal:
|
|
250
|
-
${this.utils.printExpected(expected)}
|
|
251
|
-
Received:
|
|
252
|
-
${this.utils.printReceived(received)}` : () => {
|
|
253
|
-
const diffString = diff(expected, received, { expand: this.expand });
|
|
254
|
-
return this.utils.matcherHint(".toReceiveMessage", "WS", "expected") + `
|
|
255
|
-
|
|
256
|
-
Expected the next received message to equal:
|
|
257
|
-
${this.utils.printExpected(expected)}
|
|
258
|
-
Received:
|
|
259
|
-
${this.utils.printReceived(received)}
|
|
260
|
-
|
|
261
|
-
Difference:
|
|
262
|
-
|
|
263
|
-
${diffString}`;
|
|
264
|
-
};
|
|
265
|
-
return {
|
|
266
|
-
actual: received,
|
|
267
|
-
expected,
|
|
268
|
-
message,
|
|
269
|
-
pass
|
|
270
|
-
};
|
|
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
|
+
};
|
|
271
228
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
*/
|
|
275
252
|
expect.extend(matchers_exports);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
WS as default,
|
|
279
|
-
deriveToHaveReceivedMessage,
|
|
280
|
-
deriveToReceiveMessage
|
|
281
|
-
};
|
|
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,31 +35,26 @@
|
|
|
34
35
|
"author": "Akiomi Kamakura",
|
|
35
36
|
"license": "MIT",
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"@vitest/coverage-v8": "^
|
|
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": "^
|
|
50
|
-
"vite": "^
|
|
51
|
-
"vitest": "^
|
|
42
|
+
"tsdown": "^0.22.5",
|
|
43
|
+
"typescript": "^7.0.2",
|
|
44
|
+
"vite": "^8.1.4",
|
|
45
|
+
"vitest": "^4.1.10"
|
|
52
46
|
},
|
|
53
47
|
"peerDependencies": {
|
|
54
|
-
"vitest": ">=
|
|
48
|
+
"vitest": ">=4"
|
|
55
49
|
},
|
|
56
50
|
"dependencies": {
|
|
57
|
-
"@vitest/utils": "^3.0.0",
|
|
58
51
|
"mock-socket": "^9.2.1"
|
|
59
52
|
},
|
|
60
53
|
"files": [
|
|
61
54
|
"dist",
|
|
62
55
|
"LICENSE"
|
|
63
|
-
]
|
|
56
|
+
],
|
|
57
|
+
"overrides": {
|
|
58
|
+
"esbuild": "^0.28.1"
|
|
59
|
+
}
|
|
64
60
|
}
|