[REF] web,mail: remove legacy MockServer and related utils

This commit removes the legacy implementation of the MockServer
and test utils making it available in the test environment (e.g.
addMockEnvironment).

Part of task~3439226

closes odoo/odoo#130671

Related: odoo/enterprise#45141
Signed-off-by: Francois Georis (fge) <fge@odoo.com>
This commit is contained in:
Aaron Bohy
2023-08-04 09:43:08 +02:00
parent 41750141c2
commit 850427e4ba
16 changed files with 32 additions and 3548 deletions
@@ -15,13 +15,7 @@ const ROUTES_TO_IGNORE = [
"/web/dataset/call_kw/res.users/load_views",
"/web/dataset/call_kw/res.users/systray_get_activities",
];
const WEBCLIENT_PARAMETER_NAMES = new Set([
"legacyParams",
"mockRPC",
"serverData",
"target",
"webClientClass",
]);
const WEBCLIENT_PARAMETER_NAMES = new Set(["mockRPC", "serverData", "target", "webClientClass"]);
const SERVICES_PARAMETER_NAMES = new Set([
"legacyServices",
"loadingBaseDelayDuration",
@@ -129,17 +123,14 @@ export const setupManager = {
*/
setupServiceRegistries({ loadingBaseDelayDuration = 0, messagingBus, services = {} } = {}) {
const OriginalAudio = window.Audio;
patchWithCleanup(
window,
{
Audio: function () {
const audio = new OriginalAudio();
audio.preload = "none";
audio.play = () => {};
return audio;
},
}
);
patchWithCleanup(window, {
Audio: function () {
const audio = new OriginalAudio();
audio.preload = "none";
audio.play = () => {};
return audio;
},
});
patchWithCleanup(session, { show_effect: true });
services["messagingValues"] = services["messagingValues"] ?? {
start() {
@@ -7,12 +7,7 @@ import Widget from "@web/legacy/js/core/widget";
var ESCAPE_KEY = $.Event("keyup", { which: 27 });
async function createEmptyParent(debug) {
var widget = new Widget();
await testUtils.mock.addMockEnvironment(widget, {
debug: debug || false,
});
return widget;
return new Widget();
}
QUnit.module('core', {}, function () {
@@ -5,6 +5,8 @@ import core from "@web/legacy/js/services/core";
import Dialog from "@web/legacy/js/core/dialog";
import QWeb from "@web/legacy/js/core/qweb";
import Widget from "@web/legacy/js/core/widget";
import session from "web.session";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import testUtils from "@web/../tests/legacy/helpers/test_utils";
QUnit.module('core', {}, function () {
@@ -401,18 +403,19 @@ QUnit.module('core', {}, function () {
var def;
var parent = new Widget();
await testUtils.mock.addMockEnvironment(parent, {
session: {
rpc: function () {
def = testUtils.makeTestPromise();
def.abort = def.reject;
return def;
},
},
services: {
ajax: AjaxService
patchWithCleanup(session, {
rpc: () => {
def = testUtils.makeTestPromise();
def.abort = def.reject;
return def;
},
});
var ajaxService = new AjaxService();
testUtils.mock.intercept(parent, 'call_service', function ({ data }) {
if (data.service === "ajax" && data.method === "rpc") {
data.callback(ajaxService.rpc(...data.args));
}
});
var widget = new Widget(parent);
widget._rpc({route: '/a/route'}).then(function () {
@@ -1,7 +1,6 @@
/** @odoo-module **/
import KeyboardNavigationMixin from "@web/legacy/frontend/keyboard_navigation_mixin";
import testUtils from "@web/../tests/legacy/helpers/test_utils";
import Widget from "@web/legacy/js/core/widget";
QUnit.module('KeyboardNavigationMixin', function () {
@@ -25,7 +24,7 @@ QUnit.module('KeyboardNavigationMixin', function () {
return this._super(...arguments);
},
});
var parent = await testUtils.createParent({});
var parent = new Widget();
var w = new KeyboardWidget(parent);
await w.appendTo($target);
@@ -66,7 +65,7 @@ QUnit.module('KeyboardNavigationMixin', function () {
return this._super(...arguments);
},
});
var parent = await testUtils.createParent({});
var parent = new Widget();
var w = new KeyboardWidget(parent);
await w.appendTo($target);
File diff suppressed because it is too large Load Diff
@@ -11,7 +11,6 @@
import testUtilsFields from "@web/../tests/legacy/helpers/test_utils_fields";
import testUtilsFile from "@web/../tests/legacy/helpers/test_utils_file";
import testUtilsMock from "@web/../tests/legacy/helpers/test_utils_mock";
import Widget from "@web/legacy/js/core/widget";
function deprecated(fn, type) {
return function () {
@@ -22,19 +21,6 @@
};
}
/**
* Create a widget parent from given parameters.
*
* @param {Object} params This object will be given to addMockEnvironment, so
* any parameters from that method applies
* @returns {Promise<Widget>}
*/
async function createParent(params) {
const widget = new Widget();
await testUtilsMock.addMockEnvironment(widget, params);
return widget;
}
/**
* Helper function, make a promise with a public resolve function. Note that
* this is not standard and should not be used outside of tests...
@@ -99,8 +85,6 @@
}
export const mock = {
addMockEnvironment: testUtilsMock.addMockEnvironment,
addMockEnvironmentOwl: testUtilsMock.addMockEnvironmentOwl,
intercept: testUtilsMock.intercept,
patch: testUtilsMock.patch,
patchDate: testUtilsMock.patchDate,
@@ -137,14 +121,12 @@
fields,
file,
createParent,
makeTestPromise: makeTestPromise,
makeTestPromiseWithAssert: makeTestPromiseWithAssert,
nextMicrotaskTick: nextMicrotaskTick,
nextTick: nextTick,
// backward-compatibility
addMockEnvironment: deprecated(testUtilsMock.addMockEnvironment, 'mock'),
dragAndDrop: deprecated(testUtilsDom.dragAndDrop, 'dom'),
getView: deprecated(testUtilsMock.getView, 'mock'),
intercept: deprecated(testUtilsMock.intercept, 'mock'),
@@ -9,199 +9,10 @@
* testUtils file.
*/
import AbstractStorageService from "@web/legacy/js/core/abstract_storage_service";
import AjaxService from "@web/legacy/js/services/ajax_service";
import Bus from "@web/legacy/js/core/bus";
import config from "@web/legacy/js/services/config";
import core from "@web/legacy/js/services/core";
import dom from "@web/legacy/js/core/dom";
import makeTestEnvironment from "@web/../tests/legacy/helpers/test_env";
import MockServer from "@web/../tests/legacy/helpers/mock_server";
import RamStorage from "@web/legacy/js/core/ram_storage";
import session from "web.session";
import { patchWithCleanup, patchDate } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { assets } from "@web/core/assets";
import { patchDate } from "@web/../tests/helpers/utils";
import { Component } from "@odoo/owl";
import { uniqueId } from "@web/core/utils/functions";
//------------------------------------------------------------------------------
// Private functions
//------------------------------------------------------------------------------
/**
* Returns a mocked environment to be used by OWL components in tests, with
* requested services (+ ajax, local_storage and session_storage) deployed.
*
* @private
* @param {Object} params
* @param {Bus} [params.bus]
* @param {Object} [params.env]
* @param {Bus} [params.env.bus]
* @param {Object} [params.env.services]
* @param {Object} [params.services]
* @param {Object} [params.session]
* @param {MockServer} [mockServer]
* @returns {Promise<Object>} env
*/
async function _getMockedOwlEnv(params, mockServer) {
params.env = params.env || {};
const database = {parameters: params.translateParameters || {}};
// build the env
const services = {};
const env = Object.assign({}, params.env, {
_t: params.env && params.env._t || Object.assign((s => s), { database }),
browser: Object.assign({
fetch: (resource, init) => mockServer.performFetch(resource, init),
}, params.env.browser),
bus: params.bus || params.env.bus || new Bus(),
services: Object.assign(services, params.env.services),
session: params.env.session || params.session || {},
});
// deploy services into the env
// determine services to instantiate (classes), and already register function services
const servicesToDeploy = {};
for (const name in params.services || {}) {
const Service = params.services[name];
if (Service.constructor.name === 'Class') {
servicesToDeploy[name] = Service;
} else {
services[name] = Service;
}
}
// always deploy ajax, local storage and session storage
if (!servicesToDeploy.ajax) {
const MockedAjaxService = AjaxService.extend({
rpc: mockServer.performRpc.bind(mockServer),
});
services.ajax = new MockedAjaxService(env);
}
const RamStorageService = AbstractStorageService.extend({
storage: new RamStorage(),
});
if (!servicesToDeploy.local_storage) {
services.local_storage = new RamStorageService(env);
}
if (!servicesToDeploy.session_storage) {
services.session_storage = new RamStorageService(env);
}
// deploy other requested services
let done = false;
while (!done) {
const serviceName = Object.keys(servicesToDeploy).find(serviceName => {
const Service = servicesToDeploy[serviceName];
return Service.prototype.dependencies.every(depName => {
return env.services[depName];
});
});
if (serviceName) {
const Service = servicesToDeploy[serviceName];
services[serviceName] = new Service(env);
delete servicesToDeploy[serviceName];
services[serviceName].start();
} else {
const serviceNames = Object.keys(servicesToDeploy);
if (serviceNames.length) {
console.warn("Non loaded services:", serviceNames);
}
done = true;
}
}
// wait for asynchronous services to properly start
await new Promise(setTimeout);
return env;
}
/**
* This function is used to mock global objects (session, config...) in tests.
* It is necessary for legacy widgets. It returns a cleanUp function to call at
* the end of the test.
*
* The function could be removed as soon as we do not support legacy widgets
* anymore.
*
* @private
* @param {Object} params
* @param {Object} [params.config] if given, it is used to extend the global
* config,
* @param {Object} [params.session] if given, it is used to extend the current,
* real session.
* @param {Object} [params.translateParameters] if given, it will be used to
* extend the core._t.database.parameters object.
* @returns {function} a cleanUp function to restore everything, to call at the
* end of the test
*/
function _mockGlobalObjects(params) {
// store initial session state (for restoration)
const initialSession = Object.assign({}, session);
const sessionPatch = Object.assign({
getTZOffset() { return 0; },
async user_has_group() { return false; },
}, params.session);
// patch session
Object.assign(session, sessionPatch);
// patch config
let initialConfig;
if ('config' in params) {
initialConfig = Object.assign({}, config);
initialConfig.device = Object.assign({}, config.device);
if ('device' in params.config) {
Object.assign(config.device, params.config.device);
}
if ('debug' in params.config) {
odoo.debug = params.config.debug;
}
}
// patch translate params
let initialParameters;
if ('translateParameters' in params) {
initialParameters = Object.assign({}, core._t.database.parameters);
Object.assign(core._t.database.parameters, params.translateParameters);
}
// build the cleanUp function to restore everything at the end of the test
function cleanUp() {
let key;
for (key in sessionPatch) {
delete session[key];
}
Object.assign(session, initialSession);
if ('config' in params) {
for (key in config) {
delete config[key];
}
Object.assign(config, initialConfig);
}
if ('translateParameters' in params) {
for (key in core._t.database.parameters) {
delete core._t.database.parameters[key];
}
Object.assign(core._t.database.parameters, initialParameters);
}
}
return cleanUp;
}
/**
* logs all event going through the target widget.
*
* @param {Widget} widget
*/
function _observe(widget) {
var _trigger_up = widget._trigger_up.bind(widget);
widget._trigger_up = function (event) {
console.log('%c[event] ' + event.name, 'color: blue; font-weight: bold;', event);
_trigger_up(event);
};
}
//------------------------------------------------------------------------------
// Public functions
//------------------------------------------------------------------------------
@@ -230,241 +41,6 @@ function intercept(widget, eventName, fn, propagate) {
};
}
/**
* Add a mock environment to test Owl Components. This function generates a test
* env and sets it on the given Component. It also has several side effects,
* like patching the global session or config objects. It returns a cleanup
* function to call at the end of the test.
*
* @param {Component} Component
* @param {Object} [params]
* @param {Object} [params.actions]
* @param {Object} [params.archs]
* @param {string} [params.currentDate]
* @param {Object} [params.data]
* @param {boolean} [params.debug]
* @param {function} [params.mockFetch]
* @param {function} [params.mockRPC]
* @param {number} [params.fieldDebounce=0] the value of the DEBOUNCE attribute
* of fields
* @param {boolean} [params.mockSRC=false] if true, redirect src GET requests to
* the mockServer
* @param {MockServer} [mockServer]
* @returns {Promise<function>} the cleanup function
*/
async function addMockEnvironmentOwl(Component, params, mockServer) {
params = params || {};
// instantiate a mockServer if not provided
if (!mockServer) {
let Server = MockServer;
if (params.mockFetch) {
Server = Server.extend({ _performFetch: params.mockFetch });
}
if (params.mockRPC) {
Server = Server.extend({ _performRpc: params.mockRPC });
}
mockServer = new Server(params.data, {
actions: params.actions,
archs: params.archs,
currentDate: params.currentDate,
debug: params.debug,
});
}
patchWithCleanup(browser, {
fetch: async (url, args) => {
const result = await mockServer.performFetch(url, args || {});
return {
json: () => result,
text: () => result,
};
},
});
if (params.mockFetch) {
const { loadJS, loadCSS } = assets;
patchWithCleanup(assets, {
loadJS: async function (ressource) {
let res = await params.mockFetch(ressource, {});
if (res === undefined) {
res = await loadJS(ressource);
} else {
console.log("%c[assets] fetch (mock) JS ressource " + ressource, "color: #66e; font-weight: bold;");
}
return res;
},
loadCSS: async function (ressource) {
let res = await params.mockFetch(ressource, {});
if (res === undefined) {
res = await loadCSS(ressource);
} else {
console.log("%c[assets] fetch (mock) CSS ressource " + ressource, "color: #66e; font-weight: bold;");
}
return res;
},
});
}
const initialDOMDebounceValue = dom.DEBOUNCE;
dom.DEBOUNCE = 0;
// mock global objects for legacy widgets (session, config...)
const restoreMockedGlobalObjects = _mockGlobalObjects(params);
// set the test env on owl Component
const env = await _getMockedOwlEnv(params, mockServer);
const originalEnv = Component.env;
const __env = makeTestEnvironment(env, mockServer.performRpc.bind(mockServer));
owl.Component.env = __env;
// while we have a mix between Owl and legacy stuff, some of them triggering
// events on the env.bus (a new Bus instance especially created for the current
// test), the others using core.bus, we have to ensure that events triggered
// on env.bus are also triggered on core.bus (note that outside the testing
// environment, both are the exact same instance of Bus)
const envBusTrigger = env.bus.trigger;
env.bus.trigger = function () {
core.bus.trigger(...arguments);
envBusTrigger.call(env.bus, ...arguments);
};
// build the clean up function to call at the end of the test
function cleanUp() {
env.bus.destroy();
Object.keys(env.services).forEach(function (s) {
var service = env.services[s] || {};
if (service.destroy && !service.isDestroyed()) {
service.destroy();
}
});
dom.DEBOUNCE = initialDOMDebounceValue;
// clear the caches (e.g. data_manager, ModelFieldSelector) at the end
// of each test to avoid collisions
core.bus.trigger('clear_cache');
$('body').off('DOMNodeInserted.removeSRC');
$('.blockUI').remove(); // fixme: move to qunit_config in OdooAfterTestHook?
restoreMockedGlobalObjects();
Component.env = originalEnv;
}
return cleanUp;
}
/**
* Add a mock environment to a widget. This helper function can simulate
* various kind of side effects, such as mocking RPCs, changing the session,
* or the translation settings.
*
* @param {Widget} widget
* @param {Object} params
* @param {Object} [params.archs] a map of string [model,view_id,view_type] to
* a arch object. It is used to mock answers to 'load_views' custom events.
* This is useful when the widget instantiate a formview dialog that needs
* to load a particular arch.
* @param {string} [params.currentDate] a string representation of the current
* date. It is given to the mock server.
* @param {Object} params.data the data given to the created mock server. It is
* used to generate mock answers for every kind of routes supported by odoo
* @param {number} [params.debug] if set to true, logs RPCs and uncaught Odoo
* events.
* @param {Object} [params.bus] the instance of Bus that will be used (in the env)
* @param {function} [params.mockFetch] a function that will be used to override
* the _performFetch method from the mock server. It is really useful to add
* some custom fetch mocks, or to check some assertions.
* @param {function} [params.mockRPC] a function that will be used to override
* the _performRpc method from the mock server. It is really useful to add
* some custom rpc mocks, or to check some assertions.
* @param {Object} [params.session] if it is given, it will be used as answer
* for all calls to this.getSession() by the widget, of its children. Also,
* it will be used to extend the current, real session. This side effect is
* undone when the widget is destroyed.
* @param {Object} [params.translateParameters] if given, it will be used to
* extend the core._t.database.parameters object. After the widget
* destruction, the original parameters will be restored.
* @param {Object} [params.intercepts] an object with event names as key, and
* callback as value. Each key,value will be used to intercept the event.
* Note that this is particularly useful if you want to intercept events going
* up in the init process of the view, because there are no other way to do it
* after this method returns. Some events ('call_service', "load_views",
* "get_session", "load_filters") have a special treatment beforehand.
* @param {Object} [params.services={}] list of services to load in
* addition to the ajax service. For instance, if a test needs the local
* storage service in order to work, it can provide a mock version of it.
* @param {boolean} [debounce=true] set to false to completely remove the
* debouncing, forcing the handler to be called directly (not on the next
* execution stack, like it does with delay=0).
* @param {boolean} [throttle=false] set to true to keep the throttling, which
* is completely removed by default.
*
* @returns {Promise<MockServer>} the instance of the mock server, created by this
* function. It is necessary for createView so that method can call some
* other methods on it.
*/
async function addMockEnvironment(widget, params) {
// log events triggered up if debug flag is true
if (params.debug) {
_observe(widget);
var separator = window.location.href.indexOf('?') !== -1 ? "&" : "?";
var url = window.location.href + separator + 'testId=' + QUnit.config.current.testId;
console.log('%c[debug] debug mode activated', 'color: blue; font-weight: bold;', url);
}
// instantiate mock server
var Server = MockServer;
if (params.mockFetch) {
Server = MockServer.extend({ _performFetch: params.mockFetch });
}
if (params.mockRPC) {
Server = Server.extend({ _performRpc: params.mockRPC });
}
var mockServer = new Server(params.data, {
actions: params.actions,
archs: params.archs,
currentDate: params.currentDate,
debug: params.debug,
widget: widget,
});
// build and set the Owl env on Component
if (!('mockSRC' in params)) { // redirect src rpcs to the mock server
params.mockSRC = true;
}
const cleanUp = await addMockEnvironmentOwl(Component, params, mockServer);
const env = Component.env;
// ensure to clean up everything when the widget will be destroyed
const destroy = widget.destroy;
widget.destroy = function () {
cleanUp();
destroy.call(this, ...arguments);
};
// intercept service/data manager calls and redirect them to the env
intercept(widget, 'call_service', function (ev) {
if (env.services[ev.data.service]) {
var service = env.services[ev.data.service];
const result = service[ev.data.method].apply(service, ev.data.args || []);
ev.data.callback(result);
}
});
intercept(widget, "get_session", ev => {
ev.data.callback(session);
});
// make sure all other Odoo events bubbling up are intercepted
Object.keys(params.intercepts || {}).forEach(function (name) {
intercept(widget, name, params.intercepts[name]);
});
return mockServer;
}
/**
* Patch window.Date so that the time starts its flow from the provided Date.
*
@@ -580,8 +156,6 @@ function unpatch(target) {
}
export default {
addMockEnvironment: addMockEnvironment,
addMockEnvironmentOwl: addMockEnvironmentOwl,
intercept: intercept,
patchDate: legacyPatchDate,
patch: patch,
@@ -1,137 +0,0 @@
/** @odoo-module **/
import MockServer from '@web/../tests/legacy/helpers/mock_server';
QUnit.module('web', {}, function () {
QUnit.module('legacy', {}, function () {
QUnit.module('mock_relational_fields_tests.js', {
beforeEach() {
this.data = {
foo: {
fields: {
one2many_field: { type: 'one2many', relation: 'bar', inverse_fname_by_model_name: { bar: 'many2one_field' } },
many2one_field: { type: 'many2one', relation: 'bar', inverse_fname_by_model_name: { bar: 'one2many_field' } },
many2many_field: { type: 'many2many', relation: 'bar', inverse_fname_by_model_name: { bar: 'many2many_field' } },
many2one_reference: { type: 'many2one_reference', model_name_ref_fname: 'res_model', inverse_fname_by_model_name: { bar: 'one2many_field' } },
res_model: { type: 'char' },
},
records: [],
},
bar: {
fields: {
many2one_field: { type: 'many2one', relation: 'foo' },
one2many_field: { type: 'one2many', relation: 'foo', inverse_fname_by_model_name: { foo: 'many2one_field' } },
many2many_field: { type: 'many2many', relation: 'foo', inverse_fname_by_model_name: { foo: 'many2many_field' } },
},
records: [],
},
};
}
});
QUnit.test('many2one_ref should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
res_model: 'bar',
many2one_reference: 1,
});
const mockServer = new MockServer(this.data, {});
assert.deepEqual([2], mockServer.data['bar'].records[0].one2many_field);
mockServer._mockUnlink('foo', [2]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
QUnit.test('many2one should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2one_field: 1,
});
const mockServer = new MockServer(this.data, {});
assert.deepEqual([2], mockServer.data['bar'].records[0].one2many_field);
mockServer._mockUnlink('foo', [2]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
QUnit.test('one2many should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['bar'].records.push({ id: 2 });
this.data['foo'].records.push({
id: 3,
one2many_field: [1, 2],
});
const mockServer = new MockServer(this.data, {});
assert.strictEqual(3, mockServer.data['bar'].records[0].many2one_field);
assert.strictEqual(3, mockServer.data['bar'].records[1].many2one_field);
mockServer._mockUnlink('foo', [3]);
assert.strictEqual(false, mockServer.data['bar'].records[0].many2one_field);
assert.strictEqual(false, mockServer.data['bar'].records[1].many2one_field);
});
QUnit.test('many2many should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2many_field: [1],
});
const mockServer = new MockServer(this.data, {});
assert.deepEqual([2], mockServer.data['bar'].records[0].many2many_field);
mockServer._mockUnlink('foo', [2]);
assert.deepEqual([], mockServer.data['bar'].records[0].many2many_field);
});
QUnit.test('one2many update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['bar'].records.push({ id: 2 });
this.data['foo'].records.push({
id: 3,
one2many_field: [1, 2],
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[3], { one2many_field: [1] }]);
assert.strictEqual(3, mockServer.data['bar'].records[0].many2one_field);
assert.strictEqual(false, mockServer.data['bar'].records[1].many2one_field);
});
QUnit.test('many2many update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2many_field: [1],
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[2], { many2many_field: [] }]);
assert.deepEqual([], mockServer.data['bar'].records[0].many2many_field);
});
QUnit.test('many2one update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2one_field: 1,
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[2], { many2one_field: false }]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
QUnit.test('many2one_ref update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
res_model: 'bar',
many2one_reference: 1,
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[2], { many2one_reference: false }]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
});
});
@@ -1,465 +0,0 @@
/** @odoo-module **/
import MockServer from "@web/../tests/legacy/helpers/mock_server";
QUnit.module("Legacy MockServer", {
beforeEach() {
this.data = {
"res.partner": {
fields: {
name: {
string: "Name",
type: "string",
},
email: {
string: "Email",
type: "string",
},
active: {
string: "Active",
type: "bool",
default: true,
},
},
records: [
{ id: 1, name: "Jean-Michel", email: "jean.michel@example.com" },
{ id: 2, name: "Raoul", email: "raoul@example.com", active: false },
],
},
bar: {
fields: {
foo: {
string: "Foo",
type: "integer",
searchable: true,
group_operator: "sum",
},
date: { string: "Date", type: "date", store: true, sortable: true },
datetime: {
string: "DateTime",
type: "datetime",
store: true,
sortable: true,
},
partners: { string: "Buddies", type: "many2many", relation: "res.partner" },
},
records: [
{
id: 1,
foo: 12,
date: "2016-12-14",
datetime: "2016-12-14 12:34:56",
partners: [1, 2],
},
{
id: 2,
foo: 1,
date: "2016-10-26",
datetime: "2016-10-26 12:34:56",
partners: [1],
},
{
id: 3,
foo: 17,
date: "2016-12-15",
datetime: "2016-12-15 12:34:56",
partners: [2],
},
{ id: 4, foo: 2, date: "2016-04-11", datetime: "2016-04-11 12:34:56" },
{ id: 5, foo: 0, date: "2016-12-15", datetime: "2016-12-15 12:34:56" },
{ id: 6, foo: 42, date: "2019-12-30", datetime: "2019-12-30 12:34:56" },
],
},
};
},
}, function () {
QUnit.test("performRpc: search_read with an empty array of fields", async function (assert) {
assert.expect(1);
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "res.partner",
method: "search_read",
args: [],
kwargs: {
fields: [],
},
});
const expectedFields = ["id", "email", "name", "display_name"];
assert.strictEqual(
expectedFields.filter((x) => Object.keys(result[0]).indexOf(x) < 0).length,
0,
"should contains all the fields"
);
});
QUnit.test("performRpc: search_read without fields", async function (assert) {
assert.expect(1);
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "res.partner",
method: "search_read",
args: [],
kwargs: {},
});
const expectedFields = ["id", "email", "name", "display_name"];
assert.strictEqual(
expectedFields.filter((x) => Object.keys(result[0]).indexOf(x) < 0).length,
0,
"should contains all the fields"
);
});
QUnit.test("performRpc: search with active_test=false", async function (assert) {
assert.expect(1);
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "res.partner",
method: "search",
args: [[]],
kwargs: {
context: { active_test: false },
},
});
assert.deepEqual(result, [1, 2]);
});
QUnit.test("performRpc: search with active_test=true", async function (assert) {
assert.expect(1);
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "res.partner",
method: "search",
args: [[]],
kwargs: {
context: { active_test: true },
},
});
assert.deepEqual(result, [1]);
});
QUnit.test("performRpc: search_read with active_test=false", async function (assert) {
assert.expect(1);
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "res.partner",
method: "search_read",
args: [[]],
kwargs: {
fields: ["name"],
context: { active_test: false },
},
});
assert.deepEqual(result, [{id: 1, name: "Jean-Michel"}, {id: 2, name: "Raoul"}]);
});
QUnit.test("performRpc: search_read with active_test=true", async function (assert) {
assert.expect(1);
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "res.partner",
method: "search_read",
args: [[]],
kwargs: {
fields: ["name"],
context: { active_test: true },
},
});
assert.deepEqual(result, [{id: 1, name: "Jean-Michel"}]);
});
QUnit.test("performRpc: read_group, group by date", async function (assert) {
assert.expect(10);
const server = new MockServer(this.data, {});
let result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["date"], //Month by default
},
});
assert.deepEqual(
result.map((x) => x.date),
["December 2016", "October 2016", "April 2016", "December 2019"]
);
assert.deepEqual(
result.map((x) => x.date_count),
[3, 1, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["date:day"],
},
});
assert.deepEqual(
result.map((x) => x["date:day"]),
["2016-12-14", "2016-10-26", "2016-12-15", "2016-04-11", "2019-12-30"]
);
assert.deepEqual(
result.map((x) => x.date_count),
[1, 1, 2, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["date:week"],
},
});
assert.deepEqual(
result.map((x) => x["date:week"]),
["W50 2016", "W43 2016", "W15 2016", "W01 2020"]
);
assert.deepEqual(
result.map((x) => x.date_count),
[3, 1, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["date:quarter"],
},
});
assert.deepEqual(
result.map((x) => x["date:quarter"]),
["Q4 2016", "Q2 2016", "Q4 2019"]
);
assert.deepEqual(
result.map((x) => x.date_count),
[4, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["date:year"],
},
});
assert.deepEqual(
result.map((x) => x["date:year"]),
["2016", "2019"]
);
assert.deepEqual(
result.map((x) => x.date_count),
[5, 1]
);
});
QUnit.test("performRpc: read_group, group by datetime", async function (assert) {
const server = new MockServer(this.data, {});
let result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["datetime"], //Month by default
},
});
assert.deepEqual(
result.map((x) => x.datetime),
["December 2016", "October 2016", "April 2016", "December 2019"]
);
assert.deepEqual(
result.map((x) => x.datetime_count),
[3, 1, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["datetime:hour"],
},
});
assert.deepEqual(
result.map((x) => x["datetime:hour"]),
["13:00 14 Dec", "13:00 26 Oct", "13:00 15 Dec", "13:00 11 Apr", "13:00 30 Dec"]
);
assert.deepEqual(
result.map((x) => x.datetime_count),
[1, 1, 2, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["datetime:day"],
},
});
assert.deepEqual(
result.map((x) => x["datetime:day"]),
["2016-12-14", "2016-10-26", "2016-12-15", "2016-04-11", "2019-12-30"]
);
assert.deepEqual(
result.map((x) => x.datetime_count),
[1, 1, 2, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["datetime:week"],
},
});
assert.deepEqual(
result.map((x) => x["datetime:week"]),
["W50 2016", "W43 2016", "W15 2016", "W01 2020"]
);
assert.deepEqual(
result.map((x) => x.datetime_count),
[3, 1, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["datetime:quarter"],
},
});
assert.deepEqual(
result.map((x) => x["datetime:quarter"]),
["Q4 2016", "Q2 2016", "Q4 2019"]
);
assert.deepEqual(
result.map((x) => x.datetime_count),
[4, 1, 1]
);
result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["datetime:year"],
},
});
assert.deepEqual(
result.map((x) => x["datetime:year"]),
["2016", "2019"]
);
assert.deepEqual(
result.map((x) => x.datetime_count),
[5, 1]
);
});
QUnit.test("performRpc: read_group, group by m2m", async function (assert) {
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["partners"],
domain: [],
groupby: ["partners"],
},
});
assert.deepEqual(
result,
[
{
__domain: [["partners", "=", 1]],
partners: [1, "Jean-Michel"],
partners_count: 2,
},
{
__domain: [["partners", "=", 2]],
partners: [2, "Raoul"],
partners_count: 2,
},
{
__domain: [["partners", "=", false]],
partners: false,
partners_count: 3,
},
]
);
});
QUnit.test("performRpc: read_group, group by integer", async function (assert) {
const server = new MockServer(this.data, {});
const result = await server.performRpc("", {
model: "bar",
method: "read_group",
args: [[]],
kwargs: {
fields: ["foo"],
domain: [],
groupby: ["foo"],
},
});
assert.deepEqual(result, [
{
__domain: [["foo", "=", 12]],
foo: 12,
foo_count: 1,
},
{
__domain: [["foo", "=", 1]],
foo: 1,
foo_count: 1,
},
{
__domain: [["foo", "=", 17]],
foo: 17,
foo_count: 1,
},
{
__domain: [["foo", "=", 2]],
foo: 2,
foo_count: 1,
},
{
__domain: [["foo", "=", 0]],
foo: 0,
foo_count: 1,
},
{
__domain: [["foo", "=", 42]],
foo: 42,
foo_count: 1,
},
]);
});
});
@@ -17,7 +17,6 @@
const makeTestPromise = testUtils.makeTestPromise;
const nextTick = testUtils.nextTick;
const addMockEnvironmentOwl = testUtils.mock.addMockEnvironmentOwl;
const {
Component,
@@ -426,40 +425,6 @@
assert.verifySteps(['a', 'b']);
});
QUnit.test("sub widget that calls _rpc", async function (assert) {
assert.expect(3);
const MyWidget = Widget.extend({
willStart: function () {
return this._rpc({ route: 'some/route', params: { val: 2 } });
},
});
class Parent extends LegacyComponent {
constructor() {
super(...arguments);
this.MyWidget = MyWidget;
}
}
Parent.template = xml`
<div>
<ComponentAdapter Component="MyWidget"/>
</div>`;
Parent.components = { ComponentAdapter };
const cleanUp = await addMockEnvironmentOwl(Parent, {
mockRPC: function (route, args) {
assert.step(`${route} ${args.val}`);
return Promise.resolve();
},
});
const target = getFixture();
const parent = await mount(Parent, target, { env: owl.Component.env });
assert.strictEqual(parent.el.innerHTML, '<div></div>');
assert.verifySteps(['some/route 2']);
cleanUp();
});
QUnit.test("sub widget that calls a service", async function (assert) {
assert.expect(1);
@@ -501,34 +466,6 @@
await mount(Parent, target, { env });
});
QUnit.test("sub widget that requests the session", async function (assert) {
assert.expect(1);
const MyWidget = Widget.extend({
start: function () {
assert.strictEqual(this.getSession().key, 'value');
},
});
class Parent extends LegacyComponent {
constructor() {
super(...arguments);
this.MyWidget = MyWidget;
}
}
Parent.template = xml`
<div>
<ComponentAdapter Component="MyWidget"/>
</div>`;
Parent.components = { ComponentAdapter };
const cleanUp = await addMockEnvironmentOwl(Parent, {
session: { key: 'value' },
});
const target = getFixture();
await mount(Parent, target, { env: owl.Component.env });
cleanUp();
});
QUnit.test("sub widgets in a t-if/t-else", async function (assert) {
assert.expect(3);
@@ -2,6 +2,7 @@
import { NameAndSignature } from "@web/legacy/js/widgets/name_and_signature";
import testUtils from "@web/../tests/legacy/helpers/test_utils";
const MockedNameAndSignature = NameAndSignature.extend({
events: {
...NameAndSignature.prototype.events,
@@ -14,10 +15,10 @@
async function MockedNameAndSignatureGenerator (options) {
const parent = $("#qunit-fixture");
const mockedNameAndSignature = new MockedNameAndSignature(parent, options);
await testUtils.mock.addMockEnvironment(mockedNameAndSignature, {
mockRPC: function (route, args) {
if (route == "/web/sign/get_fonts/") {
return Promise.resolve();
testUtils.mock.intercept(mockedNameAndSignature, 'call_service', function ({ data }) {
if (data.service === "ajax" && data.method === "rpc") {
if (data.args[0] === "/web/sign/get_fonts/") {
data.callback(Promise.resolve());
}
}
});
@@ -4242,7 +4242,6 @@ QUnit.module("Fields", (hooks) => {
};
const webClient = await createWebClient({
serverData,
legacyParams: { withLegacyMockServer: true },
mockRPC: function (route, args) {
if (args.method === "get_formview_action") {
assert.step("get_formview_action");
@@ -10443,10 +10443,6 @@ QUnit.module("Fields", (hooks) => {
mockRPC(route, args) {
assert.step(args.method);
},
// we set a fieldDebounce to precisely mock the behavior of the webclient: changes are
// not sent to the model at keystrokes, but when the input is left
// legacyParams: { fieldDebounce: 5000 }, // WOWL keep this in view API?
});
await addRow(target);
-19
View File
@@ -17,7 +17,6 @@ import {
setupControlPanelFavoriteMenuRegistry,
setupControlPanelServiceRegistry,
} from "../search/helpers";
import { addLegacyMockEnvironment } from "../webclient/helpers";
import { Component, useSubEnv, xml } from "@odoo/owl";
@@ -52,11 +51,9 @@ async function _makeView(params, inDialog = false) {
...getDefaultConfig(),
...props.config,
};
const legacyParams = props.legacyParams || {};
delete props.serverData;
delete props.mockRPC;
delete props.legacyParams;
delete props.config;
if (props.arch) {
@@ -73,22 +70,6 @@ async function _makeView(params, inDialog = false) {
const env = await makeTestEnv({ serverData, mockRPC });
Object.assign(env, createDebugContext(env)); // This is needed if the views are in debug mode
/** Legacy Environment, for compatibility sakes
* Remove this as soon as we drop the legacy support
*/
const models = params.serverData.models;
if (legacyParams && legacyParams.withLegacyMockServer && models) {
legacyParams.models = Object.assign({}, 0);
// In lagacy, data may not be sole models, but can contain some other variables
// So we filter them out for our WOWL mockServer
Object.entries(legacyParams.models).forEach(([k, v]) => {
if (!(v instanceof Object) || !("fields" in v)) {
delete models[k];
}
});
}
await addLegacyMockEnvironment(env, legacyParams);
const target = getFixture();
const viewEnv = Object.assign(Object.create(env), { config });
+1 -110
View File
@@ -6,21 +6,12 @@ import { ormService } from "@web/core/orm_service";
import { popoverService } from "@web/core/popover/popover_service";
import { registry } from "@web/core/registry";
import { legacyServiceProvider } from "@web/legacy/legacy_service_provider";
import {
makeLegacyNotificationService,
mapLegacyEnvToWowlEnv,
makeLegacySessionService,
} from "@web/legacy/utils";
import { makeLegacyActionManagerService } from "@web/legacy/backend_utils";
import { viewService } from "@web/views/view_service";
import { actionService } from "@web/webclient/actions/action_service";
import { effectService } from "@web/core/effects/effect_service";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { menuService } from "@web/webclient/menus/menu_service";
import { WebClient } from "@web/webclient/webclient";
import AbstractService from "@web/legacy/js/core/abstract_service";
import core from "@web/legacy/js/services/core";
import makeTestEnvironment from "@web/../tests/legacy/helpers/test_env";
import { registerCleanup } from "../helpers/cleanup";
import { makeTestEnv } from "../helpers/mock_env";
import {
@@ -31,15 +22,10 @@ import {
makeFakeHTTPService,
makeFakeUserService,
} from "../helpers/mock_services";
import { getFixture, mount, nextTick, patchWithCleanup } from "../helpers/utils";
import session from "web.session";
import LegacyMockServer from "@web/../tests/legacy/helpers/mock_server";
import Widget from "@web/legacy/js/core/widget";
import { getFixture, mount, nextTick } from "../helpers/utils";
import { uiService } from "@web/core/ui/ui_service";
import { commandService } from "@web/core/commands/command_service";
import { ConnectionAbortedError } from "@web/core/network/rpc_service";
import { CustomFavoriteItem } from "@web/search/custom_favorite_item/custom_favorite_item";
import { standaloneAdapter } from "@web/legacy/js/owl_compatibility";
import { overlayService } from "@web/core/overlay/overlay_service";
import { Component, xml } from "@odoo/owl";
@@ -99,87 +85,6 @@ export function setupWebClientRegistries() {
}
}
/**
* Remove this as soon as we drop the legacy support
*/
export async function addLegacyMockEnvironment(env, legacyParams = {}) {
// setup a legacy env
let localSession;
if (legacyParams && legacyParams.getTZOffset) {
patchWithCleanup(session, {
getTZOffset: legacyParams.getTZOffset,
});
localSession = { getTZOffset: legacyParams.getTZOffset };
}
const baseEnv = { bus: core.bus, session: localSession };
const legacyEnv = makeTestEnvironment(Object.assign(baseEnv, legacyParams.env));
if (legacyParams.serviceRegistry) {
const legacyServiceMap = core.serviceRegistry.map;
core.serviceRegistry.map = legacyParams.serviceRegistry.map;
// notification isn't a deployed service, but it is added by `makeTestEnvironment`.
// Here, we want full control on the deployed services, so we simply remove it.
delete legacyEnv.services.notification;
AbstractService.prototype.deployServices(legacyEnv);
registerCleanup(() => {
core.serviceRegistry.map = legacyServiceMap;
});
}
Component.env = legacyEnv;
mapLegacyEnvToWowlEnv(legacyEnv, env);
function patchLegacySession() {
const userContext = Object.getOwnPropertyDescriptor(session, "user_context");
registerCleanup(() => {
Object.defineProperty(session, "user_context", userContext);
});
}
patchLegacySession();
serviceRegistry.add("legacy_session", makeLegacySessionService(legacyEnv, session));
// deploy the legacyActionManagerService (in Wowl env)
const legacyActionManagerService = makeLegacyActionManagerService(legacyEnv);
serviceRegistry.add("legacy_action_manager", legacyActionManagerService);
serviceRegistry.add("legacy_notification", makeLegacyNotificationService(legacyEnv));
// deploy wowl services into the legacy env.
const wowlToLegacyServiceMappers = registry.category("wowlToLegacyServiceMappers").getEntries();
for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) {
serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv));
}
if (legacyParams.withLegacyMockServer) {
const adapter = standaloneAdapter({ Component });
registerCleanup(() => adapter.__owl__.app.destroy());
adapter.env = legacyEnv;
const W = Widget.extend({ do_push_state() {} });
const widget = new W(adapter);
const legacyMockServer = new LegacyMockServer(legacyParams.models, { widget });
const originalRPC = env.services.rpc;
const rpc = async (...args) => {
try {
return await originalRPC(...args);
} catch (e) {
if (e.message.includes("Unimplemented")) {
return legacyMockServer._performRpc(...args);
} else {
throw e;
}
}
};
env.services.rpc = function () {
let rejectFn;
const rpcProm = new Promise((resolve, reject) => {
rejectFn = reject;
rpc(...arguments)
.then(resolve)
.catch(reject);
});
rpcProm.abort = () => rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));
return rpcProm;
};
}
}
/**
* This method create a web client instance properly configured.
*
@@ -191,26 +96,12 @@ export async function addLegacyMockEnvironment(env, legacyParams = {}) {
export async function createWebClient(params) {
setupWebClientRegistries();
const legacyParams = params.legacyParams;
params.serverData = params.serverData || {};
const models = params.serverData.models;
if (legacyParams && legacyParams.withLegacyMockServer && models) {
legacyParams.models = Object.assign({}, models);
// In lagacy, data may not be sole models, but can contain some other variables
// So we filter them out for our WOWL mockServer
Object.entries(legacyParams.models).forEach(([k, v]) => {
if (!(v instanceof Object) || !("fields" in v)) {
delete models[k];
}
});
}
const mockRPC = params.mockRPC || undefined;
const env = await makeTestEnv({
serverData: params.serverData,
mockRPC,
});
await addLegacyMockEnvironment(env, legacyParams);
const WebClientClass = params.WebClientClass || WebClient;
const target = params && params.target ? params.target : getFixture();
@@ -2,7 +2,6 @@
import ajax from "@web/legacy/js/core/ajax";
import { MockServer } from "@web/../tests/helpers/mock_server";
import MockServerLegacy from "@web/../tests/legacy/helpers/mock_server";
import testUtils from "@web/../tests/legacy/helpers/test_utils";
import * as coreUtils from "@web/core/utils/patch";
import * as OdooEditorLib from "@web_editor/js/editor/odoo-editor/src/OdooEditor";
@@ -117,28 +116,6 @@ coreUtils.patch(MockServer.prototype, {
return super._performRPC(...arguments);
},
});
MockServerLegacy.include({
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
* @private
* @returns {Promise}
*/
async _performRpc(route, args) {
if (args.model === "ir.ui.view" && args.method === 'render_public_asset') {
if (args.args[0] === "web_editor.colorpicker") {
return COLOR_PICKER_TEMPLATE;
}
if (args.args[0] === "web_editor.snippets") {
return SNIPPETS_TEMPLATE;
}
}
return this._super(...arguments);
},
});
/**
* Options with animation and edition for test.