bus/static/tests/helpers/websocket_event_deferred.js
Данил Воробьев 9ae41c29a2 initial commit
2024-05-03 12:03:07 +00:00

227 lines
8.2 KiB
JavaScript

/* @odoo-module */
import { patchWebsocketWorkerWithCleanup } from "@bus/../tests/helpers/mock_websocket";
import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
import { registerCleanup } from "@web/../tests/helpers/cleanup";
import { makeDeferred } from "@web/../tests/helpers/utils";
// should be enough to decide whether or not notifications/channel
// subscriptions... are received.
const TIMEOUT = 500;
const callbackRegistry = registry.category("mock_server_websocket_callbacks");
/**
* Returns a deferred that resolves when a websocket subscription is
* done. If channels are provided, the deferred will only resolve when
* we subscribe to all of them.
*
* @param {...string} [requiredChannels]
* @returns {import("@web/core/utils/concurrency").Deferred}
*/
export function waitUntilSubscribe(...requiredChannels) {
const subscribeDeferred = makeDeferred();
const failTimeout = setTimeout(() => {
const errMsg = `Subscription to ${JSON.stringify(requiredChannels)} not received.`;
subscribeDeferred.reject(new Error(errMsg));
QUnit.assert.ok(false, errMsg);
}, TIMEOUT);
const lastCallback = callbackRegistry.get("subscribe", () => {});
callbackRegistry.add(
"subscribe",
(data) => {
const { channels } = data;
lastCallback(data);
const allChannelsSubscribed = requiredChannels.every((channel) =>
channels.includes(channel)
);
if (allChannelsSubscribed) {
subscribeDeferred.resolve();
QUnit.assert.ok(
true,
`Subscription to ${JSON.stringify(requiredChannels)} received.`
);
clearTimeout(failTimeout);
}
},
{ force: true }
);
return subscribeDeferred;
}
/**
* Returns a deferred that resolves when the given channel(s) addition/deletion
* is notified to the websocket worker.
*
* @param {string[]} channels
* @param {object} [options={}]
* @param {"add"|"delete"} [options.operation="add"]
*
* @returns {import("@web/core/utils/concurrency").Deferred} */
export function waitForChannels(channels, { operation = "add" } = {}) {
const missingChannels = new Set(channels);
const deferred = makeDeferred();
function check({ crashOnFail = false } = {}) {
const success = missingChannels.size === 0;
if (!success && !crashOnFail) {
return;
}
unpatch();
clearTimeout(failTimeout);
const msg = success
? `Channel(s) [${channels.join(", ")}] ${operation === "add" ? "added" : "deleted"}.`
: `Waited ${TIMEOUT}ms for [${channels.join(", ")}] to be ${
operation === "add" ? "added" : "deleted"
}`;
QUnit.assert.ok(success, msg);
if (success) {
deferred.resolve();
} else {
deferred.reject(new Error(msg));
}
}
const failTimeout = setTimeout(() => check({ crashOnFail: true }), TIMEOUT);
registerCleanup(() => {
if (missingChannels.length > 0) {
check({ crashOnFail: true });
}
});
const worker = patchWebsocketWorkerWithCleanup();
const workerMethod = operation === "add" ? "_addChannel" : "_deleteChannel";
const unpatch = patch(worker, {
async [workerMethod](client, channel) {
await super[workerMethod](client, channel);
missingChannels.delete(channel);
check();
},
});
return deferred;
}
/**
* @typedef {Object} ExpectedNotificationOptions
* @property {boolean} [received=true]
* @typedef {[env: import("@web/env").OdooEnv, notificationType: string, notificationPayload: any, options: ExpectedNotificationOptions]} ExpectedNotification
*/
/**
* Wait for a notification to be received/not received. Returns
* a deferred that resolves when the assertion is done.
*
* @param {ExpectedNotification} notification
* @returns {import("@web/core/utils/concurrency").Deferred}
*/
function _waitNotification(notification) {
const [env, type, payload, { received = true } = {}] = notification;
const notificationDeferred = makeDeferred();
const failTimeout = setTimeout(() => {
QUnit.assert.ok(
!received,
`Notification of type "${type}" with payload ${payload} not received.`
);
env.services["bus_service"].removeEventListener("notification", callback);
notificationDeferred.resolve();
}, TIMEOUT);
const callback = ({ detail: notifications }) => {
for (const notification of notifications) {
if (notification.type !== type) {
continue;
}
if (
payload === undefined ||
JSON.stringify(notification.payload) === JSON.stringify(payload)
) {
QUnit.assert.ok(
received,
`Notification of type "${type}" with payload ${JSON.stringify(
notification.payload
)} receveived.`
);
notificationDeferred.resolve();
clearTimeout(failTimeout);
env.services["bus_service"].removeEventListener("notification", callback);
}
}
};
env.services["bus_service"].addEventListener("notification", callback);
return notificationDeferred;
}
/**
* Wait for the expected notifications to be received/not received. Returns
* a deferred that resolves when the assertion is done.
*
* @param {ExpectedNotification[]} expectedNotifications
* @returns {import("@web/core/utils/concurrency").Deferred}
*/
export function waitNotifications(...expectedNotifications) {
return Promise.all(
expectedNotifications.map((expectedNotification) => _waitNotification(expectedNotification))
);
}
/**
* Returns a deferred that resolves when an event matching the given type is
* received from the bus service.
*
* @typedef {"connect"|"disconnect"|"reconnect"|"reconnecting"|"notification"} EventType
* @param {import("@web/env").OdooEnv} env
* @param {EventType} eventType
* @param {object} [options={}]
* @param {boolean} [options.received=true]
*/
export function waitForBusEvent(env, eventType, { received = true } = {}) {
const eventReceivedDeferred = makeDeferred();
const failTimeout = setTimeout(() => {
env.services["bus_service"].removeEventListener(eventType, callback);
QUnit.assert.ok(
!received,
received
? `Waited ${TIMEOUT}ms for ${eventType} event.`
: `Event of type "${eventType}" not received.`
);
eventReceivedDeferred.resolve();
}, TIMEOUT);
const callback = () => {
env.services["bus_service"].removeEventListener(eventType, callback);
QUnit.assert.ok(received, `Event of type "${eventType}" received.`);
eventReceivedDeferred.resolve();
clearTimeout(failTimeout);
};
env.services["bus_service"].addEventListener(eventType, callback);
return eventReceivedDeferred;
}
/**
* Returns a deferred that resolves when an event matching the given type is
* received by the websocket worker.
*
* @param {import("@web/env").OdooEnv} env
* @param {import("@bus/workers/websocket_worker").WorkerAction} targetAction
* @param {object} [options={}]
* @param {boolean} [options.received=true]
*/
export function waitForWorkerEvent(targetAction) {
const eventReiceivedDeferred = makeDeferred();
const failTimeout = setTimeout(() => {
unpatch();
QUnit.assert.ok(false, `Waited ${TIMEOUT}ms for ${targetAction} to be received.`);
eventReiceivedDeferred.resolve();
}, TIMEOUT);
const worker = patchWebsocketWorkerWithCleanup();
const unpatch = patch(worker, {
_onClientMessage(_, { action }) {
super._onClientMessage(...arguments);
if (targetAction === action) {
unpatch();
QUnit.assert.ok(true, `Action "${action}" received.`);
eventReiceivedDeferred.resolve();
clearTimeout(failTimeout);
}
},
});
registerCleanup(unpatch);
return eventReiceivedDeferred;
}