mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-19 21:32:07 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
|
|
@ -0,0 +1,217 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { TEST_USER_IDS } from '@bus/../tests/helpers/test_constants';
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
import { makeMockServer } from "@web/../tests/helpers/mock_server";
|
||||
import core from 'web.core';
|
||||
|
||||
const modelDefinitionsPromise = new Promise(resolve => {
|
||||
QUnit.begin(() => resolve(getModelDefinitions()));
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch model definitions from the server then insert fields present in the
|
||||
* `bus.model.definitions` registry. Use `addModelNamesToFetch`/`insertModelFields`
|
||||
* helpers in order to add models to be fetched, default values to the fields,
|
||||
* fields to a model definition.
|
||||
*
|
||||
* @return {Map<string, Object>} A map from model names to model fields definitions.
|
||||
* @see model_definitions_setup.js
|
||||
*/
|
||||
async function getModelDefinitions() {
|
||||
const modelDefinitionsRegistry = registry.category('bus.model.definitions');
|
||||
const modelNamesToFetch = modelDefinitionsRegistry.get('modelNamesToFetch');
|
||||
const fieldsToInsertRegistry = modelDefinitionsRegistry.category('fieldsToInsert');
|
||||
|
||||
// fetch the model definitions.
|
||||
const formData = new FormData();
|
||||
formData.append('csrf_token', core.csrf_token);
|
||||
formData.append('model_names_to_fetch', JSON.stringify(modelNamesToFetch));
|
||||
const response = await window.fetch('/bus/get_model_definitions', { body: formData, method: 'POST' });
|
||||
if (response.status !== 200) {
|
||||
throw new Error('Error while fetching required models');
|
||||
}
|
||||
const modelDefinitions = new Map(Object.entries(await response.json()));
|
||||
|
||||
for (const [modelName, fields] of modelDefinitions) {
|
||||
// insert fields present in the fieldsToInsert registry : if the field
|
||||
// exists, update its default value according to the one in the
|
||||
// registry; If it does not exist, add it to the model definition.
|
||||
const fieldNamesToFieldToInsert = fieldsToInsertRegistry.category(modelName).getEntries();
|
||||
for (const [fname, fieldToInsert] of fieldNamesToFieldToInsert) {
|
||||
if (fname in fields) {
|
||||
fields[fname].default = fieldToInsert.default;
|
||||
} else {
|
||||
fields[fname] = fieldToInsert;
|
||||
}
|
||||
}
|
||||
// apply default values for date like fields if none was passed.
|
||||
for (const fname in fields) {
|
||||
const field = fields[fname];
|
||||
if (['date', 'datetime'].includes(field.type) && !field.default) {
|
||||
const defaultFieldValue = field.type === 'date'
|
||||
? () => moment.utc().format('YYYY-MM-DD')
|
||||
: () => moment.utc().format("YYYY-MM-DD HH:mm:ss");
|
||||
field.default = defaultFieldValue;
|
||||
} else if (fname === 'active' && !('default' in field)) {
|
||||
// records are active by default.
|
||||
field.default = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// add models present in the fake models registry to the model definitions.
|
||||
const fakeModels = modelDefinitionsRegistry.category('fakeModels').getEntries();
|
||||
for (const [modelName, fields] of fakeModels) {
|
||||
modelDefinitions.set(modelName, fields);
|
||||
}
|
||||
return modelDefinitions;
|
||||
}
|
||||
|
||||
let pyEnv;
|
||||
/**
|
||||
* Creates an environment that can be used to setup test data as well as
|
||||
* creating data after test start.
|
||||
*
|
||||
* @param {Object} serverData serverData to pass to the mockServer.
|
||||
* @param {Object} [serverData.action] actions to be passed to the mock
|
||||
* server.
|
||||
* @param {Object} [serverData.views] views to be passed to the mock
|
||||
* server.
|
||||
* @returns {Object} An environment that can be used to interact with
|
||||
* the mock server (creation, deletion, update of records...)
|
||||
*/
|
||||
export async function startServer({ actions, views = {} } = {}) {
|
||||
const models = {};
|
||||
const modelDefinitions = await modelDefinitionsPromise;
|
||||
const recordsToInsertRegistry = registry.category('bus.model.definitions').category('recordsToInsert');
|
||||
for (const [modelName, fields] of modelDefinitions) {
|
||||
const records = [];
|
||||
if (recordsToInsertRegistry.contains(modelName)) {
|
||||
// prevent tests from mutating the records.
|
||||
records.push(...JSON.parse(JSON.stringify(recordsToInsertRegistry.get(modelName))));
|
||||
}
|
||||
models[modelName] = { fields: { ...fields }, records };
|
||||
|
||||
// generate default views for this model if none were passed.
|
||||
const viewArchsSubRegistries = registry.category('bus.view.archs').subRegistries;
|
||||
for (const [viewType, archsRegistry] of Object.entries(viewArchsSubRegistries)) {
|
||||
views[`${modelName},false,${viewType}`] =
|
||||
views[`${modelName},false,${viewType}`] ||
|
||||
archsRegistry.get(modelName, archsRegistry.get('default'));
|
||||
}
|
||||
}
|
||||
pyEnv = new Proxy(
|
||||
{
|
||||
get currentPartner() {
|
||||
return this.mockServer.currentPartner;
|
||||
},
|
||||
getData() {
|
||||
return this.mockServer.models;
|
||||
},
|
||||
getViews() {
|
||||
return views;
|
||||
},
|
||||
simulateConnectionLost(closeCode) {
|
||||
this.mockServer._simulateConnectionLost(closeCode);
|
||||
},
|
||||
...TEST_USER_IDS,
|
||||
},
|
||||
{
|
||||
get(target, name) {
|
||||
if (target[name]) {
|
||||
return target[name];
|
||||
}
|
||||
const modelAPI = {
|
||||
/**
|
||||
* Simulate a 'create' operation on a model.
|
||||
*
|
||||
* @param {Object[]|Object} values records to be created.
|
||||
* @returns {integer[]|integer} array of ids if more than one value was passed,
|
||||
* id of created record otherwise.
|
||||
*/
|
||||
create(values) {
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(values)) {
|
||||
values = [values];
|
||||
}
|
||||
const recordIds = values.map(value => target.mockServer.mockCreate(name, value));
|
||||
return recordIds.length === 1 ? recordIds[0] : recordIds;
|
||||
},
|
||||
/**
|
||||
* Simulate a 'search' operation on a model.
|
||||
*
|
||||
* @param {Array} domain
|
||||
* @param {Object} context
|
||||
* @returns {integer[]} array of ids corresponding to the given domain.
|
||||
*/
|
||||
search(domain, context = {}) {
|
||||
return target.mockServer.mockSearch(name, [domain], context);
|
||||
},
|
||||
/**
|
||||
* Simulate a `search_count` operation on a model.
|
||||
*
|
||||
* @param {Array} domain
|
||||
* @return {number} count of records matching the given domain.
|
||||
*/
|
||||
searchCount(domain) {
|
||||
return this.search(domain).length;
|
||||
},
|
||||
/**
|
||||
* Simulate a 'search_read' operation on a model.
|
||||
*
|
||||
* @param {Array} domain
|
||||
* @param {Object} kwargs
|
||||
* @returns {Object[]} array of records corresponding to the given domain.
|
||||
*/
|
||||
searchRead(domain, kwargs = {}) {
|
||||
return target.mockServer.mockSearchRead(name, [domain], kwargs);
|
||||
},
|
||||
/**
|
||||
* Simulate an 'unlink' operation on a model.
|
||||
*
|
||||
* @param {integer[]} ids
|
||||
* @returns {boolean} mockServer 'unlink' method always returns true.
|
||||
*/
|
||||
unlink(ids) {
|
||||
return target.mockServer.mockUnlink(name, [ids]);
|
||||
},
|
||||
/**
|
||||
* Simulate a 'write' operation on a model.
|
||||
*
|
||||
* @param {integer[]} ids ids of records to write on.
|
||||
* @param {Object} values values to write on the records matching given ids.
|
||||
* @returns {boolean} mockServer 'write' method always returns true.
|
||||
*/
|
||||
write(ids, values) {
|
||||
return target.mockServer.mockWrite(name, [ids, values]);
|
||||
},
|
||||
};
|
||||
if (name === 'bus.bus') {
|
||||
modelAPI['_sendone'] = target.mockServer._mockBusBus__sendone.bind(target.mockServer);
|
||||
modelAPI['_sendmany'] = target.mockServer._mockBusBus__sendmany.bind(target.mockServer);
|
||||
}
|
||||
return modelAPI;
|
||||
},
|
||||
set(target, name, value) {
|
||||
return target[name] = value;
|
||||
},
|
||||
},
|
||||
);
|
||||
pyEnv['mockServer'] = await makeMockServer({ actions, models, views });
|
||||
pyEnv['mockServer'].pyEnv = pyEnv;
|
||||
registerCleanup(() => pyEnv = undefined);
|
||||
return pyEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Object} An environment that can be used to interact with the mock
|
||||
* server (creation, deletion, update of records...)
|
||||
*/
|
||||
export function getPyEnv() {
|
||||
return pyEnv || startServer();
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { TEST_USER_IDS } from "@bus/../tests/helpers/test_constants";
|
||||
import { patchWebsocketWorkerWithCleanup } from '@bus/../tests/helpers/mock_websocket';
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { MockServer } from "@web/../tests/helpers/mock_server";
|
||||
|
||||
patch(MockServer.prototype, 'bus', {
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
Object.assign(this, TEST_USER_IDS);
|
||||
const self = this;
|
||||
this.websocketWorker = patchWebsocketWorkerWithCleanup({
|
||||
_sendToServer(message) {
|
||||
self._performWebsocketRequest(message);
|
||||
this._super(message);
|
||||
},
|
||||
});
|
||||
this.pendingLongpollingPromise = null;
|
||||
this.notificationsToBeResolved = [];
|
||||
this.lastBusNotificationId = 0;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @param {Object} message Message sent through the websocket to the
|
||||
* server.
|
||||
* @param {string} [message.event_name]
|
||||
* @param {any} [message.data]
|
||||
*/
|
||||
_performWebsocketRequest({ event_name, data }) {
|
||||
if (event_name === 'update_presence') {
|
||||
const { inactivity_period, im_status_ids_by_model } = data;
|
||||
this._mockIrWebsocket__updatePresence(inactivity_period, im_status_ids_by_model);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Simulates `_sendone` on `bus.bus`.
|
||||
*
|
||||
* @param {string} channel
|
||||
* @param {string} notificationType
|
||||
* @param {any} message
|
||||
*/
|
||||
_mockBusBus__sendone(channel, notificationType, message) {
|
||||
this._mockBusBus__sendmany([[channel, notificationType, message]]);
|
||||
},
|
||||
/**
|
||||
* Simulates `_sendmany` on `bus.bus`.
|
||||
*
|
||||
* @param {Array} notifications
|
||||
*/
|
||||
_mockBusBus__sendmany(notifications) {
|
||||
if (!notifications.length) {
|
||||
return;
|
||||
}
|
||||
const values = [];
|
||||
for (const notification of notifications) {
|
||||
const [type, payload] = notification.slice(1, notification.length);
|
||||
values.push({ id: this.lastBusNotificationId++, message: { payload, type }});
|
||||
if (this.debug) {
|
||||
console.log("%c[bus]", "color: #c6e; font-weight: bold;", type, payload);
|
||||
}
|
||||
}
|
||||
this.websocketWorker.broadcast('notification', values);
|
||||
|
||||
},
|
||||
/**
|
||||
* Simulate the lost of the connection by simulating a closeEvent on
|
||||
* the worker websocket.
|
||||
*
|
||||
* @param {number} clodeCode the code to close the connection with.
|
||||
*/
|
||||
_simulateConnectionLost(closeCode) {
|
||||
this.websocketWorker.websocket.close(closeCode);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { MockServer } from "@web/../tests/helpers/mock_server";
|
||||
|
||||
patch(MockServer.prototype, 'bus/models/ir_websocket', {
|
||||
/**
|
||||
* Simulates `_update_presence` on `ir.websocket`.
|
||||
*
|
||||
* @param inactivityPeriod
|
||||
* @param imStatusIdsByModel
|
||||
*/
|
||||
_mockIrWebsocket__updatePresence(inactivityPeriod, imStatusIdsByModel) {
|
||||
const imStatusNotifications = this._mockIrWebsocket__getImStatus(imStatusIdsByModel);
|
||||
if (Object.keys(imStatusNotifications).length > 0) {
|
||||
this._mockBusBus__sendone(this.currentPartnerId, 'bus/im_status', imStatusNotifications);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Simulates `_get_im_status` on `ir.websocket`.
|
||||
*
|
||||
* @param {Object} imStatusIdsByModel
|
||||
* @param {Number[]|undefined} res.partner ids of res.partners whose im_status
|
||||
* should be monitored.
|
||||
*/
|
||||
_mockIrWebsocket__getImStatus(imStatusIdsByModel) {
|
||||
const imStatus = {};
|
||||
const { 'res.partner': partnerIds } = imStatusIdsByModel;
|
||||
if (partnerIds) {
|
||||
imStatus['partners'] = this.mockSearchRead('res.partner', [[['id', 'in', partnerIds]]], { context: { 'active_test': false }, fields: ['im_status'] })
|
||||
}
|
||||
return imStatus;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { presenceService } from '@bus/services/presence_service';
|
||||
|
||||
export function makeFakePresenceService(params = {}) {
|
||||
return {
|
||||
...presenceService,
|
||||
start(env) {
|
||||
return {
|
||||
...presenceService.start(env),
|
||||
...params,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { WebsocketWorker } from "@bus/workers/websocket_worker";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
|
||||
class WebSocketMock extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this.readyState = 0;
|
||||
|
||||
queueMicrotask(() => {
|
||||
this.readyState = 1;
|
||||
const openEv = new Event('open');
|
||||
this.onopen(openEv);
|
||||
this.dispatchEvent(openEv);
|
||||
});
|
||||
}
|
||||
|
||||
close(code = 1000, reason) {
|
||||
this.readyState = 3;
|
||||
const closeEv = new CloseEvent('close', {
|
||||
code,
|
||||
reason,
|
||||
wasClean: code === 1000,
|
||||
});
|
||||
this.onclose(closeEv);
|
||||
this.dispatchEvent(closeEv);
|
||||
}
|
||||
|
||||
onclose(closeEv) {}
|
||||
onerror(errorEv) {}
|
||||
onopen(openEv) {}
|
||||
|
||||
send(data) {
|
||||
if (this.readyState !== 1) {
|
||||
const errorEv = new Event('error');
|
||||
this.onerror(errorEv);
|
||||
this.dispatchEvent(errorEv);
|
||||
throw new DOMException("Failed to execute 'send' on 'WebSocket': State is not OPEN");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SharedWorkerMock extends EventTarget {
|
||||
constructor(websocketWorker) {
|
||||
super();
|
||||
this._websocketWorker = websocketWorker;
|
||||
this._messageChannel = new MessageChannel();
|
||||
this.port = this._messageChannel.port1;
|
||||
// port 1 should be started by the service itself.
|
||||
this._messageChannel.port2.start();
|
||||
this._websocketWorker.registerClient(this._messageChannel.port2);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkerMock extends SharedWorkerMock {
|
||||
constructor(websocketWorker) {
|
||||
super(websocketWorker);
|
||||
this.port.start();
|
||||
this.postMessage = this.port.postMessage.bind(this.port);
|
||||
}
|
||||
}
|
||||
|
||||
let websocketWorker;
|
||||
/**
|
||||
* @param {*} params Parameters used to patch the websocket worker.
|
||||
* @returns {WebsocketWorker} Instance of the worker which will run during the
|
||||
* test. Usefull to interact with the worker in order to test the
|
||||
* websocket behavior.
|
||||
*/
|
||||
export function patchWebsocketWorkerWithCleanup(params = {}) {
|
||||
patchWithCleanup(window, {
|
||||
WebSocket: function () {
|
||||
return new WebSocketMock();
|
||||
},
|
||||
}, { pure: true });
|
||||
patchWithCleanup(websocketWorker || WebsocketWorker.prototype, params);
|
||||
websocketWorker = websocketWorker || new WebsocketWorker();
|
||||
patchWithCleanup(browser, {
|
||||
SharedWorker: function () {
|
||||
const sharedWorker = new SharedWorkerMock(websocketWorker);
|
||||
registerCleanup(() => {
|
||||
sharedWorker._messageChannel.port1.close();
|
||||
sharedWorker._messageChannel.port2.close();
|
||||
});
|
||||
return sharedWorker;
|
||||
},
|
||||
Worker: function () {
|
||||
const worker = new WorkerMock(websocketWorker);
|
||||
registerCleanup(() => {
|
||||
worker._messageChannel.port1.close();
|
||||
worker._messageChannel.port2.close();
|
||||
});
|
||||
return worker;
|
||||
},
|
||||
}, { pure: true });
|
||||
registerCleanup(() => {
|
||||
if (websocketWorker) {
|
||||
clearTimeout(websocketWorker.connectTimeout);
|
||||
websocketWorker = null;
|
||||
}
|
||||
});
|
||||
return websocketWorker;
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
const modelDefinitionsRegistry = registry.category('bus.model.definitions');
|
||||
const customModelFieldsRegistry = modelDefinitionsRegistry.category('fieldsToInsert');
|
||||
const recordsToInsertRegistry = modelDefinitionsRegistry.category('recordsToInsert');
|
||||
const fakeModelsRegistry = modelDefinitionsRegistry.category('fakeModels');
|
||||
/**
|
||||
* Add models whose definitions need to be fetched on the server.
|
||||
*
|
||||
* @param {string[]} modelName
|
||||
*/
|
||||
export function addModelNamesToFetch(modelNames) {
|
||||
if (!modelDefinitionsRegistry.contains('modelNamesToFetch')) {
|
||||
modelDefinitionsRegistry.add('modelNamesToFetch', []);
|
||||
}
|
||||
modelDefinitionsRegistry.get('modelNamesToFetch').push(...modelNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add models that will be added to the model definitions. We should
|
||||
* avoid to rely on fake models and use real models instead.
|
||||
*
|
||||
* @param {string} modelName
|
||||
* @param {Object} fields
|
||||
*/
|
||||
export function addFakeModel(modelName, fields) {
|
||||
fakeModelsRegistry.add(modelName, fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add model fields that are not present on the server side model's definitions
|
||||
* but are required to ease testing or add default values for existing fields.
|
||||
*
|
||||
* @param {string} modelName
|
||||
* @param {Object} fieldNamesToFields
|
||||
*/
|
||||
export function insertModelFields(modelName, fieldNamesToFields) {
|
||||
const modelCustomFieldsRegistry = customModelFieldsRegistry.category(modelName);
|
||||
for (const fname in fieldNamesToFields) {
|
||||
modelCustomFieldsRegistry.add(fname, fieldNamesToFields[fname]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add records to the initial server data.
|
||||
*
|
||||
* @param {string} modelName
|
||||
* @param {Object[]} records
|
||||
*/
|
||||
export function insertRecords(modelName, records) {
|
||||
if (!recordsToInsertRegistry.contains(modelName)) {
|
||||
recordsToInsertRegistry.add(modelName, []);
|
||||
}
|
||||
recordsToInsertRegistry.get(modelName).push(...records);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { TEST_GROUP_IDS, TEST_USER_IDS } from '@bus/../tests/helpers/test_constants';
|
||||
import {
|
||||
addModelNamesToFetch,
|
||||
insertModelFields,
|
||||
insertRecords
|
||||
} from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Models
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
addModelNamesToFetch([
|
||||
'ir.attachment', 'ir.model', 'ir.model.fields', 'res.company', 'res.country',
|
||||
'res.groups', 'res.partner', 'res.users'
|
||||
]);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Insertion of fields
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
insertModelFields('res.partner', {
|
||||
description: { string: 'description', type: 'text' },
|
||||
});
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Insertion of records
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
insertRecords('res.company', [{ id: 1 }]);
|
||||
insertRecords('res.groups', [
|
||||
{ id: TEST_GROUP_IDS.groupUserId, name: "Internal User" },
|
||||
]);
|
||||
insertRecords('res.users', [
|
||||
{ display_name: "Your Company, Mitchell Admin", id: TEST_USER_IDS.currentUserId, name: "Mitchell Admin", partner_id: TEST_USER_IDS.currentPartnerId, },
|
||||
{ active: false, display_name: "Public user", id: TEST_USER_IDS.publicUserId, name: "Public user", partner_id: TEST_USER_IDS.publicPartnerId, },
|
||||
]);
|
||||
insertRecords('res.partner', [
|
||||
{ active: false, display_name: "Public user", id: TEST_USER_IDS.publicPartnerId, is_public: true },
|
||||
{ display_name: "Your Company, Mitchell Admin", id: TEST_USER_IDS.currentPartnerId, name: "Mitchell Admin", },
|
||||
{ active: false, display_name: "OdooBot", id: TEST_USER_IDS.partnerRootId, name: "OdooBot" },
|
||||
]);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
export const TEST_GROUP_IDS = {
|
||||
groupUserId: 11,
|
||||
};
|
||||
|
||||
export const TEST_USER_IDS = {
|
||||
partnerRootId: 2,
|
||||
currentPartnerId: 3,
|
||||
currentUserId: 2,
|
||||
publicPartnerId: 4,
|
||||
publicUserId: 3,
|
||||
};
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
const viewArchsRegistry = registry.category('bus.view.archs');
|
||||
const activityArchsRegistry = viewArchsRegistry.category('activity');
|
||||
const formArchsRegistry = viewArchsRegistry.category('form');
|
||||
const kanbanArchsRegistry = viewArchsRegistry.category('kanban');
|
||||
const listArchsRegistry = viewArchsRegistry.category('list');
|
||||
const searchArchsRegistry = viewArchsRegistry.category('search');
|
||||
|
||||
activityArchsRegistry.add('default', '<activity><templates></templates></activity>');
|
||||
formArchsRegistry.add('default', '<form/>');
|
||||
kanbanArchsRegistry.add('default', '<kanban><templates></templates>');
|
||||
listArchsRegistry.add('default', '<tree/>');
|
||||
searchArchsRegistry.add('default', '<search/>');
|
||||
|
||||
formArchsRegistry.add(
|
||||
'res.partner',
|
||||
`<form>
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="activity_ids"/>
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>`
|
||||
);
|
||||
formArchsRegistry.add(
|
||||
'res.fake',
|
||||
`<form>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>`
|
||||
);
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/* @odoo-module */
|
||||
|
||||
import { patchWebsocketWorkerWithCleanup } from "@bus/../tests/helpers/mock_websocket";
|
||||
|
||||
import { makeDeferred } from "@web/../tests/helpers/utils";
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
import { patch, unpatch } from "@web/core/utils/patch";
|
||||
|
||||
// Should be enough to decide whether or not notifications/channel
|
||||
// subscriptions... are received.
|
||||
const TIMEOUT = 500;
|
||||
|
||||
/**
|
||||
* 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 uuid = String(Date.now() + Math.random());
|
||||
const missingChannels = new Set(channels);
|
||||
const deferred = makeDeferred();
|
||||
function check({ crashOnFail = false } = {}) {
|
||||
const success = missingChannels.size === 0;
|
||||
if (!success && !crashOnFail) {
|
||||
return;
|
||||
}
|
||||
unpatch(worker, uuid);
|
||||
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();
|
||||
patch(worker, uuid, {
|
||||
async [operation === "add" ? "_addChannel" : "_deleteChannel"](client, channel) {
|
||||
await this._super(client, channel);
|
||||
missingChannels.delete(channel);
|
||||
check();
|
||||
},
|
||||
});
|
||||
return deferred;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue