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 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
- import './matchers';
5
- export { default } from './websocket';
6
- export { default as WS } from './websocket';
7
- //# sourceMappingURL=index.d.ts.map
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.0",
4
- "description": "Mock websockets and assert complex websocket interactions with Jest",
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.es.js",
11
+ "module": "dist/index.js",
12
12
  "exports": {
13
13
  ".": {
14
- "import": "dist/index.es.js",
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": "vite build && tsc -p tsconfig.build.json",
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": {
@@ -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
@@ -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"}