vitest-websocket-mock 0.1.0 → 0.1.2
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/dist/index.d.ts +62 -4
- package/dist/index.js +243 -0
- package/package.json +7 -7
- package/dist/act-compat.d.ts +0 -16
- package/dist/act-compat.d.ts.map +0 -1
- package/dist/index-da7887ea.js +0 -21171
- package/dist/index.d.ts.map +0 -1
- package/dist/index.es.js +0 -5
- package/dist/index.umd.js +0 -597
- package/dist/magic-string.es-a5f6be7e.js +0 -692
- package/dist/matchers.d.ts +0 -22
- package/dist/matchers.d.ts.map +0 -1
- package/dist/queue.d.ts +0 -11
- package/dist/queue.d.ts.map +0 -1
- package/dist/websocket.d.ts +0 -34
- package/dist/websocket.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,65 @@
|
|
|
1
|
+
import { Server, Client, CloseOptions, ServerOptions } from 'mock-socket';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @copyright Romain Bertrand 2018
|
|
3
5
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
declare class Queue<ItemT> {
|
|
7
|
+
pendingItems: Array<ItemT>;
|
|
8
|
+
nextItemResolver: () => void;
|
|
9
|
+
nextItem: Promise<void>;
|
|
10
|
+
put(item: ItemT): void;
|
|
11
|
+
get(): Promise<ItemT>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @copyright Romain Bertrand 2018
|
|
16
|
+
* @copyright Akiomi Kamakura 2023
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface WSOptions extends ServerOptions {
|
|
20
|
+
jsonProtocol?: boolean;
|
|
21
|
+
}
|
|
22
|
+
type DeserializedMessage<TMessage = object> = string | TMessage;
|
|
23
|
+
interface MockWebSocket extends Omit<Client, 'close'> {
|
|
24
|
+
close(options?: CloseOptions): void;
|
|
25
|
+
}
|
|
26
|
+
declare class WS {
|
|
27
|
+
server: Server;
|
|
28
|
+
serializer: (deserializedMessage: DeserializedMessage) => string;
|
|
29
|
+
deserializer: (message: string) => DeserializedMessage;
|
|
30
|
+
static instances: Array<WS>;
|
|
31
|
+
messages: Array<DeserializedMessage>;
|
|
32
|
+
messagesToConsume: Queue<unknown>;
|
|
33
|
+
private _isConnected;
|
|
34
|
+
private _isClosed;
|
|
35
|
+
static clean(): void;
|
|
36
|
+
constructor(url: string, opts?: WSOptions);
|
|
37
|
+
get connected(): Promise<Client>;
|
|
38
|
+
get closed(): Promise<void>;
|
|
39
|
+
get nextMessage(): Promise<unknown>;
|
|
40
|
+
on(eventName: 'connection' | 'message' | 'close', callback: (socket: MockWebSocket) => void): void;
|
|
41
|
+
send(message: DeserializedMessage): void;
|
|
42
|
+
close(options?: CloseOptions): void;
|
|
43
|
+
error(options?: CloseOptions): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @copyright Romain Bertrand 2018
|
|
48
|
+
* @copyright Akiomi Kamakura 2023
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
type ReceiveMessageOptions = {
|
|
52
|
+
timeout?: number;
|
|
53
|
+
};
|
|
54
|
+
interface CustomMatchers<R = unknown> {
|
|
55
|
+
toReceiveMessage<TMessage = object>(message: DeserializedMessage<TMessage>, options?: ReceiveMessageOptions): Promise<R>;
|
|
56
|
+
toHaveReceivedMessages<TMessage = object>(messages: Array<DeserializedMessage<TMessage>>): R;
|
|
57
|
+
}
|
|
58
|
+
declare module 'vitest' {
|
|
59
|
+
interface Assertion<T = any> extends CustomMatchers<T> {
|
|
60
|
+
}
|
|
61
|
+
interface AsymmetricMatchersContaining extends CustomMatchers {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { WS, WS as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw new Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/matchers.ts
|
|
10
|
+
import { diff } from "jest-diff";
|
|
11
|
+
import { expect } from "vitest";
|
|
12
|
+
|
|
13
|
+
// src/websocket.ts
|
|
14
|
+
import { Server } from "mock-socket";
|
|
15
|
+
|
|
16
|
+
// src/act-compat.ts
|
|
17
|
+
var act;
|
|
18
|
+
try {
|
|
19
|
+
act = __require("@testing-library/react").act;
|
|
20
|
+
} catch (_) {
|
|
21
|
+
act = (callback) => {
|
|
22
|
+
callback();
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
var act_compat_default = act;
|
|
26
|
+
|
|
27
|
+
// src/queue.ts
|
|
28
|
+
var Queue = class {
|
|
29
|
+
pendingItems = [];
|
|
30
|
+
nextItemResolver;
|
|
31
|
+
nextItem = new Promise((done) => this.nextItemResolver = done);
|
|
32
|
+
put(item) {
|
|
33
|
+
this.pendingItems.push(item);
|
|
34
|
+
this.nextItemResolver();
|
|
35
|
+
this.nextItem = new Promise((done) => this.nextItemResolver = done);
|
|
36
|
+
}
|
|
37
|
+
get() {
|
|
38
|
+
const item = this.pendingItems.shift();
|
|
39
|
+
if (item) {
|
|
40
|
+
return Promise.resolve(item);
|
|
41
|
+
}
|
|
42
|
+
let resolver;
|
|
43
|
+
const nextItemPromise = new Promise((done) => resolver = done);
|
|
44
|
+
this.nextItem.then(() => {
|
|
45
|
+
resolver(this.pendingItems.shift());
|
|
46
|
+
});
|
|
47
|
+
return nextItemPromise;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/websocket.ts
|
|
52
|
+
var identity = (x) => x;
|
|
53
|
+
var WS = class _WS {
|
|
54
|
+
server;
|
|
55
|
+
serializer;
|
|
56
|
+
deserializer;
|
|
57
|
+
static instances = [];
|
|
58
|
+
messages = [];
|
|
59
|
+
messagesToConsume = new Queue();
|
|
60
|
+
_isConnected;
|
|
61
|
+
_isClosed;
|
|
62
|
+
static clean() {
|
|
63
|
+
_WS.instances.forEach((instance) => {
|
|
64
|
+
instance.close();
|
|
65
|
+
instance.messages = [];
|
|
66
|
+
});
|
|
67
|
+
_WS.instances = [];
|
|
68
|
+
}
|
|
69
|
+
constructor(url, opts = {}) {
|
|
70
|
+
_WS.instances.push(this);
|
|
71
|
+
const { jsonProtocol = false, ...serverOptions } = opts;
|
|
72
|
+
this.serializer = jsonProtocol ? JSON.stringify : identity;
|
|
73
|
+
this.deserializer = jsonProtocol ? JSON.parse : identity;
|
|
74
|
+
let connectionResolver, closedResolver;
|
|
75
|
+
this._isConnected = new Promise((done) => connectionResolver = done);
|
|
76
|
+
this._isClosed = new Promise((done) => closedResolver = done);
|
|
77
|
+
this.server = new Server(url, serverOptions);
|
|
78
|
+
this.server.on("close", closedResolver);
|
|
79
|
+
this.server.on("connection", (socket) => {
|
|
80
|
+
connectionResolver(socket);
|
|
81
|
+
socket.on("message", (message) => {
|
|
82
|
+
const parsedMessage = this.deserializer(message);
|
|
83
|
+
this.messages.push(parsedMessage);
|
|
84
|
+
this.messagesToConsume.put(parsedMessage);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
get connected() {
|
|
89
|
+
let resolve;
|
|
90
|
+
const connectedPromise = new Promise((done) => resolve = done);
|
|
91
|
+
const waitForConnected = async () => {
|
|
92
|
+
await act_compat_default(async () => {
|
|
93
|
+
await this._isConnected;
|
|
94
|
+
});
|
|
95
|
+
resolve(await this._isConnected);
|
|
96
|
+
};
|
|
97
|
+
waitForConnected();
|
|
98
|
+
return connectedPromise;
|
|
99
|
+
}
|
|
100
|
+
get closed() {
|
|
101
|
+
let resolve;
|
|
102
|
+
const closedPromise = new Promise((done) => resolve = done);
|
|
103
|
+
const waitForclosed = async () => {
|
|
104
|
+
await act_compat_default(async () => {
|
|
105
|
+
await this._isClosed;
|
|
106
|
+
});
|
|
107
|
+
await this._isClosed;
|
|
108
|
+
resolve();
|
|
109
|
+
};
|
|
110
|
+
waitForclosed();
|
|
111
|
+
return closedPromise;
|
|
112
|
+
}
|
|
113
|
+
get nextMessage() {
|
|
114
|
+
return this.messagesToConsume.get();
|
|
115
|
+
}
|
|
116
|
+
on(eventName, callback) {
|
|
117
|
+
this.server.on(eventName, callback);
|
|
118
|
+
}
|
|
119
|
+
send(message) {
|
|
120
|
+
act_compat_default(() => {
|
|
121
|
+
this.server.emit("message", this.serializer(message));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
close(options) {
|
|
125
|
+
act_compat_default(() => {
|
|
126
|
+
this.server.close(options);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
error(options) {
|
|
130
|
+
act_compat_default(() => {
|
|
131
|
+
this.server.emit("error", null);
|
|
132
|
+
});
|
|
133
|
+
this.server.close(options);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/matchers.ts
|
|
138
|
+
var WAIT_DELAY = 1e3;
|
|
139
|
+
var TIMEOUT = Symbol("timoeut");
|
|
140
|
+
var makeInvalidWsMessage = function makeInvalidWsMessage2(ws, matcher) {
|
|
141
|
+
return this.utils.matcherHint(this.isNot ? `.not.${matcher}` : `.${matcher}`, "WS", "expected") + `
|
|
142
|
+
|
|
143
|
+
Expected the websocket object to be a valid WS mock.
|
|
144
|
+
Received: ${typeof ws}
|
|
145
|
+
${this.utils.printReceived(ws)}`;
|
|
146
|
+
};
|
|
147
|
+
expect.extend({
|
|
148
|
+
async toReceiveMessage(ws, expected, options) {
|
|
149
|
+
const isWS = ws instanceof WS;
|
|
150
|
+
if (!isWS) {
|
|
151
|
+
return {
|
|
152
|
+
pass: this.isNot,
|
|
153
|
+
// always fail
|
|
154
|
+
message: makeInvalidWsMessage.bind(this, ws, "toReceiveMessage")
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const waitDelay = options?.timeout ?? WAIT_DELAY;
|
|
158
|
+
const messageOrTimeout = await Promise.race([
|
|
159
|
+
ws.nextMessage,
|
|
160
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), waitDelay))
|
|
161
|
+
]);
|
|
162
|
+
if (messageOrTimeout === TIMEOUT) {
|
|
163
|
+
return {
|
|
164
|
+
pass: this.isNot,
|
|
165
|
+
// always fail
|
|
166
|
+
message: () => this.utils.matcherHint(this.isNot ? ".not.toReceiveMessage" : ".toReceiveMessage", "WS", "expected") + `
|
|
167
|
+
|
|
168
|
+
Expected the websocket server to receive a message,
|
|
169
|
+
but it didn't receive anything in ${waitDelay}ms.`
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const received = messageOrTimeout;
|
|
173
|
+
const pass = this.equals(received, expected);
|
|
174
|
+
const message = pass ? () => this.utils.matcherHint(".not.toReceiveMessage", "WS", "expected") + `
|
|
175
|
+
|
|
176
|
+
Expected the next received message to not equal:
|
|
177
|
+
${this.utils.printExpected(expected)}
|
|
178
|
+
Received:
|
|
179
|
+
${this.utils.printReceived(received)}` : () => {
|
|
180
|
+
const diffString = diff(expected, received, { expand: this.expand });
|
|
181
|
+
return this.utils.matcherHint(".toReceiveMessage", "WS", "expected") + `
|
|
182
|
+
|
|
183
|
+
Expected the next received message to equal:
|
|
184
|
+
${this.utils.printExpected(expected)}
|
|
185
|
+
Received:
|
|
186
|
+
${this.utils.printReceived(received)}
|
|
187
|
+
|
|
188
|
+
Difference:
|
|
189
|
+
|
|
190
|
+
${diffString}`;
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
actual: received,
|
|
194
|
+
expected,
|
|
195
|
+
message,
|
|
196
|
+
name: "toReceiveMessage",
|
|
197
|
+
pass
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
toHaveReceivedMessages(ws, messages) {
|
|
201
|
+
const isWS = ws instanceof WS;
|
|
202
|
+
if (!isWS) {
|
|
203
|
+
return {
|
|
204
|
+
pass: this.isNot,
|
|
205
|
+
// always fail
|
|
206
|
+
message: makeInvalidWsMessage.bind(this, ws, "toHaveReceivedMessages")
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const received = messages.map(
|
|
210
|
+
(expected) => (
|
|
211
|
+
// object comparison to handle JSON protocols
|
|
212
|
+
ws.messages.some((actual) => this.equals(actual, expected))
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
const pass = this.isNot ? received.some(Boolean) : received.every(Boolean);
|
|
216
|
+
const message = pass ? () => this.utils.matcherHint(".not.toHaveReceivedMessages", "WS", "expected") + `
|
|
217
|
+
|
|
218
|
+
Expected the WS server to not have received the following messages:
|
|
219
|
+
${this.utils.printExpected(messages)}
|
|
220
|
+
But it received:
|
|
221
|
+
${this.utils.printReceived(ws.messages)}` : () => {
|
|
222
|
+
return this.utils.matcherHint(".toHaveReceivedMessages", "WS", "expected") + `
|
|
223
|
+
|
|
224
|
+
Expected the WS server to have received the following messages:
|
|
225
|
+
${this.utils.printExpected(messages)}
|
|
226
|
+
Received:
|
|
227
|
+
${this.utils.printReceived(ws.messages)}
|
|
228
|
+
|
|
229
|
+
`;
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
actual: ws.messages,
|
|
233
|
+
expected: messages,
|
|
234
|
+
message,
|
|
235
|
+
name: "toHaveReceivedMessages",
|
|
236
|
+
pass
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
export {
|
|
241
|
+
WS,
|
|
242
|
+
WS as default
|
|
243
|
+
};
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-websocket-mock",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Mock websockets and assert complex websocket interactions with
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Mock websockets and assert complex websocket interactions with Vitest",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/akiomik/vitest-websocket-mock.git"
|
|
9
9
|
},
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
|
-
"module": "dist/index.
|
|
11
|
+
"module": "dist/index.js",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"import": "dist/index.
|
|
15
|
-
"types": "dist/index.d.ts"
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"clean": "rimraf dist",
|
|
20
20
|
"prebuild": "npm run clean",
|
|
21
|
-
"build": "
|
|
21
|
+
"build": "tsup",
|
|
22
22
|
"lint": "prettier --check . && eslint .",
|
|
23
23
|
"format": "prettier --write .",
|
|
24
24
|
"prepublishOnly": "npm run build",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
44
44
|
"prettier": "^2.0.2",
|
|
45
45
|
"rimraf": "^4.1.2",
|
|
46
|
+
"tsup": "^7.0.0",
|
|
46
47
|
"typescript": "^4.0.2",
|
|
47
48
|
"vite": "^4.3.9",
|
|
48
|
-
"vite-plugin-node-stdlib-browser": "^0.2.1",
|
|
49
49
|
"vitest": "^0.31.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
package/dist/act-compat.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright Romain Bertrand 2018
|
|
3
|
-
* @copyright Akiomi Kamakura 2023
|
|
4
|
-
*
|
|
5
|
-
* A simple compatibility method for react's "act".
|
|
6
|
-
* If @testing-library/react is already installed, we just use
|
|
7
|
-
* their implementation - it's complete and has useful warnings.
|
|
8
|
-
* If @testing-library/react is *not* installed, then we just assume
|
|
9
|
-
* that the user is not testing a react application, and use a noop instead.
|
|
10
|
-
*/
|
|
11
|
-
type Callback = () => Promise<void | undefined> | void | undefined;
|
|
12
|
-
type AsyncAct = (callback: Callback) => Promise<undefined>;
|
|
13
|
-
type SyncAct = (callback: Callback) => void;
|
|
14
|
-
declare let act: AsyncAct | SyncAct;
|
|
15
|
-
export default act;
|
|
16
|
-
//# sourceMappingURL=act-compat.d.ts.map
|
package/dist/act-compat.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"act-compat.d.ts","sourceRoot":"","sources":["../src/act-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,KAAK,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AACnE,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;AAC3D,KAAK,OAAO,GAAG,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;AAE5C,QAAA,IAAI,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC;AAW5B,eAAe,GAAG,CAAC"}
|