'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var mockSocket = require('mock-socket'); var jestDiff = require('jest-diff'); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } class Queue { constructor() { _defineProperty(this, "pendingItems", []); _defineProperty(this, "nextItemResolver", void 0); _defineProperty(this, "nextItem", new Promise(done => this.nextItemResolver = done)); } put(item) { this.pendingItems.push(item); this.nextItemResolver(); this.nextItem = new Promise(done => this.nextItemResolver = done); } get() { const item = this.pendingItems.shift(); if (item) { // return the next queued item immediately return Promise.resolve(item); } let resolver; const nextItemPromise = new Promise(done => resolver = done); this.nextItem.then(() => { resolver(this.pendingItems.shift()); }); return nextItemPromise; } } /** * A simple compatibility method for react's "act". * If @testing-library/react is already installed, we just use * their implementation - it's complete and has useful warnings. * If @testing-library/react is *not* installed, then we just assume * that the user is not testing a react application, and use a noop instead. */ let act; try { act = require("@testing-library/react").act; } catch (_) { act = callback => { callback(); }; } var act$1 = act; const identity = x => x; class WS { static clean() { WS.instances.forEach(instance => { instance.close(); instance.messages = []; }); WS.instances = []; } constructor(url, opts = {}) { _defineProperty(this, "server", void 0); _defineProperty(this, "serializer", void 0); _defineProperty(this, "deserializer", void 0); _defineProperty(this, "messages", []); _defineProperty(this, "messagesToConsume", new Queue()); _defineProperty(this, "_isConnected", void 0); _defineProperty(this, "_isClosed", void 0); WS.instances.push(this); const { jsonProtocol = false, ...serverOptions } = opts; this.serializer = jsonProtocol ? JSON.stringify : identity; this.deserializer = jsonProtocol ? JSON.parse : identity; let connectionResolver, closedResolver; this._isConnected = new Promise(done => connectionResolver = done); this._isClosed = new Promise(done => closedResolver = done); this.server = new mockSocket.Server(url, serverOptions); this.server.on("close", closedResolver); this.server.on("connection", socket => { connectionResolver(socket); socket.on("message", message => { const parsedMessage = this.deserializer(message); this.messages.push(parsedMessage); this.messagesToConsume.put(parsedMessage); }); }); } get connected() { let resolve; const connectedPromise = new Promise(done => resolve = done); const waitForConnected = async () => { await act$1(async () => { await this._isConnected; }); resolve(await this._isConnected); // make sure `await act` is really done }; waitForConnected(); return connectedPromise; } get closed() { let resolve; const closedPromise = new Promise(done => resolve = done); const waitForclosed = async () => { await act$1(async () => { await this._isClosed; }); await this._isClosed; // make sure `await act` is really done resolve(); }; waitForclosed(); return closedPromise; } get nextMessage() { return this.messagesToConsume.get(); } on(eventName, callback) { // @ts-ignore https://github.com/romgain/jest-websocket-mock/issues/26#issuecomment-571579567 this.server.on(eventName, callback); } send(message) { act$1(() => { this.server.emit("message", this.serializer(message)); }); } close(options) { act$1(() => { this.server.close(options); }); } error(options) { act$1(() => { this.server.emit("error", null); }); this.server.close(options); } } _defineProperty(WS, "instances", []); const WAIT_DELAY = 1000; const TIMEOUT = Symbol("timoeut"); const makeInvalidWsMessage = function makeInvalidWsMessage(ws, matcher) { return this.utils.matcherHint(this.isNot ? `.not.${matcher}` : `.${matcher}`, "WS", "expected") + "\n\n" + `Expected the websocket object to be a valid WS mock.\n` + `Received: ${typeof ws}\n` + ` ${this.utils.printReceived(ws)}`; }; expect.extend({ async toReceiveMessage(ws, expected, options) { const isWS = ws instanceof WS; if (!isWS) { return { pass: this.isNot, // always fail message: makeInvalidWsMessage.bind(this, ws, "toReceiveMessage") }; } const waitDelay = options?.timeout ?? WAIT_DELAY; let timeoutId; const messageOrTimeout = await Promise.race([ws.nextMessage, new Promise(resolve => { timeoutId = setTimeout(() => resolve(TIMEOUT), waitDelay); })]); clearTimeout(timeoutId); if (messageOrTimeout === TIMEOUT) { return { pass: this.isNot, // always fail message: () => this.utils.matcherHint(this.isNot ? ".not.toReceiveMessage" : ".toReceiveMessage", "WS", "expected") + "\n\n" + `Expected the websocket server to receive a message,\n` + `but it didn't receive anything in ${waitDelay}ms.` }; } const received = messageOrTimeout; const pass = this.equals(received, expected); const message = pass ? () => this.utils.matcherHint(".not.toReceiveMessage", "WS", "expected") + "\n\n" + `Expected the next received message to not equal:\n` + ` ${this.utils.printExpected(expected)}\n` + `Received:\n` + ` ${this.utils.printReceived(received)}` : () => { const diffString = jestDiff.diff(expected, received, { expand: this.expand }); return this.utils.matcherHint(".toReceiveMessage", "WS", "expected") + "\n\n" + `Expected the next received message to equal:\n` + ` ${this.utils.printExpected(expected)}\n` + `Received:\n` + ` ${this.utils.printReceived(received)}\n\n` + `Difference:\n\n${diffString}`; }; return { actual: received, expected, message, name: "toReceiveMessage", pass }; }, toHaveReceivedMessages(ws, messages) { const isWS = ws instanceof WS; if (!isWS) { return { pass: this.isNot, // always fail message: makeInvalidWsMessage.bind(this, ws, "toHaveReceivedMessages") }; } const received = messages.map(expected => // object comparison to handle JSON protocols ws.messages.some(actual => this.equals(actual, expected))); const pass = this.isNot ? received.some(Boolean) : received.every(Boolean); const message = pass ? () => this.utils.matcherHint(".not.toHaveReceivedMessages", "WS", "expected") + "\n\n" + `Expected the WS server to not have received the following messages:\n` + ` ${this.utils.printExpected(messages)}\n` + `But it received:\n` + ` ${this.utils.printReceived(ws.messages)}` : () => { return this.utils.matcherHint(".toHaveReceivedMessages", "WS", "expected") + "\n\n" + `Expected the WS server to have received the following messages:\n` + ` ${this.utils.printExpected(messages)}\n` + `Received:\n` + ` ${this.utils.printReceived(ws.messages)}\n\n`; }; return { actual: ws.messages, expected: messages, message, name: "toHaveReceivedMessages", pass }; } }); exports.WS = WS; exports["default"] = WS;