summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/tests/helpers/test_env.js34
-rw-r--r--addons/web/static/tests/helpers/test_utils.js2
-rw-r--r--addons/web/static/tests/helpers/test_utils_create.js23
-rw-r--r--addons/web/static/tests/helpers/test_utils_mock.js706
-rw-r--r--addons/web/static/tests/owl_compatibility_tests.js43
-rw-r--r--addons/web/static/tests/views/view_dialogs_tests.js3
6 files changed, 454 insertions, 357 deletions
diff --git a/addons/web/static/tests/helpers/test_env.js b/addons/web/static/tests/helpers/test_env.js
index 5b197451517..5a3256e98fd 100644
--- a/addons/web/static/tests/helpers/test_env.js
+++ b/addons/web/static/tests/helpers/test_env.js
@@ -1,10 +1,8 @@
odoo.define('web.test_env', async function (require) {
"use strict";
- const AbstractStorageService = require('web.AbstractStorageService');
- const Bus = require("web.Bus");
- const RamStorage = require('web.RamStorage');
- const { buildQuery } = require("web.rpc");
+ const Bus = require('web.Bus');
+ const { buildQuery } = require('web.rpc');
const session = require('web.session');
/**
@@ -17,9 +15,6 @@ odoo.define('web.test_env', async function (require) {
* @returns {Proxy}
*/
function makeTestEnvironment(env = {}, providedRPC = null) {
- const RamStorageService = AbstractStorageService.extend({
- storage: new RamStorage(),
- });
const database = {
parameters: {
code: "en_US",
@@ -31,33 +26,26 @@ odoo.define('web.test_env', async function (require) {
time_format: '%H:%M:%S',
},
};
- let testEnv = {};
const defaultEnv = {
_t: env._t || Object.assign((s => s), { database }),
- bus: new Bus(),
+ bus: env.bus || new Bus(),
device: Object.assign({ isMobile: false }, env.device),
isDebug: env.isDebug || (() => false),
qweb: new owl.QWeb({ templates: session.owlTemplates }),
services: Object.assign({
- ajax: { // for legacy subwidgets
+ ajax: {
rpc() {
- const prom = testEnv.session.rpc(...arguments);
- prom.abort = function () {
- throw new Error("Can't abort this request");
- };
- return prom;
- },
+ return env.session.rpc(...arguments); // Compatibility Legacy Widgets
+ }
},
- getCookie() { },
- httpRequest() {
+ getCookie() {},
+ httpRequest(/* route, params = {}, readMethod = 'json' */) {
return Promise.resolve('');
},
rpc(params, options) {
const query = buildQuery(params);
- return testEnv.session.rpc(query.route, query.params, options);
+ return env.session.rpc(query.route, query.params, options);
},
- local_storage: new RamStorageService(),
- session_storage: new RamStorageService(),
notification: { notify() { } },
}, env.services),
session: Object.assign({
@@ -67,10 +55,10 @@ odoo.define('web.test_env', async function (require) {
}
throw new Error(`No method to perform RPC`);
},
+ url: session.url,
}, env.session),
};
- testEnv = Object.assign(env, defaultEnv);
- return testEnv;
+ return Object.assign(env, defaultEnv);
}
/**
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index f6114b84fcd..1a1f38f7f09 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -121,7 +121,7 @@ odoo.define('web.test_utils', async function (require) {
return {
mock: {
addMockEnvironment: testUtilsMock.addMockEnvironment,
- getMockedOwlEnv: testUtilsMock.getMockedOwlEnv,
+ addMockEnvironmentOwl: testUtilsMock.addMockEnvironmentOwl,
intercept: testUtilsMock.intercept,
patch: testUtilsMock.patch,
patchDate: testUtilsMock.patchDate,
diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js
index 494a0aa2436..801120bf64b 100644
--- a/addons/web/static/tests/helpers/test_utils_create.js
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -67,7 +67,6 @@ odoo.define('web.test_utils_create', function (require) {
}
params.server = mockServer;
- Component.env = testUtilsMock.getMockedOwlEnv(params);
const userContext = params.context && params.context.user_context || {};
const actionManager = new ActionManager(widget, userContext);
@@ -142,7 +141,7 @@ odoo.define('web.test_utils_create', function (require) {
if (!(constructor.prototype instanceof Component)) {
throw new Error(`Argument "constructor" must be an Owl Component.`);
}
- const env = Object.assign(testUtilsMock.getMockedOwlEnv(params), params.env);
+ const cleanUp = await testUtilsMock.addMockEnvironmentOwl(Component, params);
class Parent extends Component {
constructor() {
super(...arguments);
@@ -154,7 +153,6 @@ odoo.define('web.test_utils_create', function (require) {
}
}
}
- Parent.env = env;
Parent.template = xml`<t t-component="Component" t-props="state" t-ref="component"/>`;
const parent = new Parent();
await parent.mount(prepareTarget(params.debug), { position: 'first-child' });
@@ -162,6 +160,7 @@ odoo.define('web.test_utils_create', function (require) {
const originalDestroy = child.destroy;
child.destroy = function () {
child.destroy = originalDestroy;
+ cleanUp();
parent.destroy();
};
return child;
@@ -241,9 +240,9 @@ odoo.define('web.test_utils_create', function (require) {
* mock method, assuming that the user has access rights, and is an admin.
*
* @param {Object} [params={}]
- * @returns {DebugManager}
+ * @returns {Promise<DebugManager>}
*/
- function createDebugManager(params = {}) {
+ async function createDebugManager(params = {}) {
const mockRPC = params.mockRPC;
Object.assign(params, {
async mockRPC(route, args) {
@@ -268,7 +267,7 @@ odoo.define('web.test_utils_create', function (require) {
},
});
const debugManager = new DebugManager();
- testUtilsMock.addMockEnvironment(debugManager, params);
+ await testUtilsMock.addMockEnvironment(debugManager, params);
return debugManager;
}
@@ -280,12 +279,12 @@ odoo.define('web.test_utils_create', function (require) {
* @param {Class} params.Model the model class to use
* @returns {Model}
*/
- function createModel(params) {
+ async function createModel(params) {
const widget = new Widget();
const model = new params.Model(widget);
- testUtilsMock.addMockEnvironment(widget, params);
+ await testUtilsMock.addMockEnvironment(widget, params);
// override the model's 'destroy' so that it calls 'destroy' on the widget
// instead, as the widget is the parent of the model and the mockServer.
@@ -304,11 +303,11 @@ odoo.define('web.test_utils_create', function (require) {
*
* @param {Object} params This object will be given to addMockEnvironment, so
* any parameters from that method applies
- * @returns {Widget}
+ * @returns {Promise<Widget>}
*/
- function createParent(params) {
+ async function createParent(params) {
const widget = new Widget();
- testUtilsMock.addMockEnvironment(widget, params);
+ await testUtilsMock.addMockEnvironment(widget, params);
return widget;
}
@@ -358,8 +357,6 @@ odoo.define('web.test_utils_create', function (require) {
const viewInfo = testUtilsMock.fieldsViewGet(mockServer, params);
params.server = mockServer;
- const env = Object.assign(testUtilsMock.getMockedOwlEnv(params));
- Component.env = env;
// create the view
const View = params.View;
diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js
index 414a9c0df64..6df49cf59f3 100644
--- a/addons/web/static/tests/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/helpers/test_utils_mock.js
@@ -10,12 +10,16 @@ odoo.define('web.test_utils_mock', function (require) {
* testUtils file.
*/
+const AbstractStorageService = require('web.AbstractStorageService');
+const AjaxService = require('web.AjaxService');
const basic_fields = require('web.basic_fields');
+const Bus = require('web.Bus');
const config = require('web.config');
const core = require('web.core');
const dom = require('web.dom');
const makeTestEnvironment = require('web.test_env');
const MockServer = require('web.MockServer');
+const RamStorage = require('web.RamStorage');
const session = require('web.session');
const DebouncedField = basic_fields.DebouncedField;
@@ -26,6 +30,199 @@ const DebouncedField = basic_fields.DebouncedField;
//------------------------------------------------------------------------------
/**
+ * 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 {boolean} [params.debug]
+ * @param {Object} [params.env]
+ * @param {Bus} [params.env.bus]
+ * @param {Object} [params.env.dataManager]
+ * @param {Object} [params.env.services]
+ * @param {Object[]} [params.favoriteFilters]
+ * @param {Object} [params.services]
+ * @param {Object} [params.session]
+ * @param {MockServer} [mockServer]
+ * @returns {Promise<Object>} env
+ */
+async function _getMockedOwlEnv(params, mockServer) {
+ params.env = params.env || {};
+
+ // build the env
+ const favoriteFilters = params.favoriteFilters;
+ const debug = params.debug;
+ const services = {};
+ const env = Object.assign({}, params.env, {
+ bus: params.bus || params.env.bus || new Bus(),
+ dataManager: Object.assign({
+ load_action: (actionID, context) => {
+ return mockServer.performRpc('/web/action/load', {
+ action_id: actionID,
+ additional_context: context,
+ });
+ },
+ load_views: (params, options) => {
+ return mockServer.performRpc('/web/dataset/call_kw/' + params.model, {
+ args: [],
+ kwargs: {
+ context: params.context,
+ options: options,
+ views: params.views_descr,
+ },
+ method: 'load_views',
+ model: params.model,
+ }).then(function (views) {
+ views = _.mapObject(views, viewParams => {
+ return fieldsViewGet(mockServer, viewParams);
+ });
+ if (favoriteFilters && 'search' in views) {
+ views.search.favoriteFilters = favoriteFilters;
+ }
+ return views;
+ });
+ },
+ load_filters: params => {
+ if (debug) {
+ console.log('[mock] load_filters', params);
+ }
+ return Promise.resolve([]);
+ },
+ }, params.env.dataManager),
+ services: Object.assign(services, params.env.services),
+ 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 = _.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) {
+ // patch session
+ let initialSession = Object.assign({}, session);
+ session.getTZOffset = function () {
+ return 0; // by default, but may be overridden in specific tests
+ };
+ if ('session' in params) {
+ Object.assign(session, params.session);
+ }
+
+ // 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;
+ if ('session' in params) {
+ for (key in session) {
+ delete session[key];
+ }
+ }
+ Object.assign(session, initialSession);
+ if ('config' in params) {
+ for (key in config) {
+ delete config[key];
+ }
+ _.extend(config, initialConfig);
+ }
+ if ('translateParameters' in params) {
+ for (key in core._t.database.parameters) {
+ delete core._t.database.parameters[key];
+ }
+ _.extend(core._t.database.parameters, initialParameters);
+ }
+ }
+
+ return cleanUp;
+}
+/**
* logs all event going through the target widget.
*
* @param {Widget} widget
@@ -43,15 +240,57 @@ function _observe(widget) {
//------------------------------------------------------------------------------
/**
+ * performs a fields_view_get, and mocks the postprocessing done by the
+ * data_manager to return an equivalent structure.
+ *
+ * @param {MockServer} server
+ * @param {Object} params
+ * @param {string} params.model
+ * @returns {Object} an object with 3 keys: arch, fields and viewFields
+ */
+function fieldsViewGet(server, params) {
+ var fieldsView = server.fieldsViewGet(params);
+ // mock the structure produced by the DataManager
+ fieldsView.viewFields = fieldsView.fields;
+ fieldsView.fields = server.fieldsGet(params.model);
+ return fieldsView;
+}
+
+/**
+ * intercepts an event bubbling up the widget hierarchy. The event intercepted
+ * must be a "custom event", i.e. an event generated by the method 'trigger_up'.
+ *
+ * Note that this method really intercepts the event if @propagate is not set.
+ * It will not be propagated further, and even the handlers on the target will
+ * not fire.
+ *
+ * @param {Widget} widget the target widget (any Odoo widget)
+ * @param {string} eventName description of the event
+ * @param {function} fn callback executed when the even is intercepted
+ * @param {boolean} [propagate=false]
+ */
+function intercept(widget, eventName, fn, propagate) {
+ var _trigger_up = widget._trigger_up.bind(widget);
+ widget._trigger_up = function (event) {
+ if (event.name === eventName) {
+ fn(event);
+ if (!propagate) { return; }
+ }
+ _trigger_up(event);
+ };
+}
+
+/**
* Removes the src attribute on images and iframes to prevent not found errors,
* and optionally triggers an rpc with the src url as route on a widget.
* This method is critical and must be fastest (=> no jQuery, no underscore)
*
* @param {HTMLElement} el
- * @param {[Widget]} widget the widget on which the rpc should be performed
+ * @param {[function]} rpc
*/
-function removeSrcAttribute(el, widget) {
+function removeSrcAttribute(el, rpc) {
var nodes;
+ el = el.nodeType === 8 ? el.nextSibling : el;
if (el.nodeName === 'IMG' || el.nodeName === 'IFRAME') {
nodes = [el];
} else {
@@ -68,8 +307,8 @@ function removeSrcAttribute(el, widget) {
} else {
node.setAttribute('src', 'about:blank');
}
- if (widget) {
- widget._rpc({ route: src });
+ if (rpc) {
+ rpc(src, []);
}
$(node).trigger('load');
}
@@ -77,6 +316,127 @@ function removeSrcAttribute(el, widget) {
}
/**
+ * 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.mockRPC]
+ * @param {number} [params.fieldDebounce=0] the value of the DEBOUNCE attribute
+ * of fields
+ * @param {boolean} [params.debounce=true] if false, patch _.debounce to remove
+ * its behavior
+ * @param {boolean} [params.throttle=false] by default, _.throttle is patched to
+ * remove its behavior, except if this params is set to true
+ * @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.mockRPC) {
+ Server = MockServer.extend({ _performRpc: params.mockRPC });
+ }
+ mockServer = new Server(params.data, {
+ actions: params.actions,
+ archs: params.archs,
+ currentDate: params.currentDate,
+ debug: params.debug,
+ });
+ }
+
+ // make sure the debounce value for input fields is set to 0
+ const initialDebounceValue = DebouncedField.prototype.DEBOUNCE;
+ DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0;
+ const initialDOMDebounceValue = dom.DEBOUNCE;
+ dom.DEBOUNCE = 0;
+
+ // patch underscore debounce/throttle functions
+ const initialDebounce = _.debounce;
+ if (params.debounce === false) {
+ _.debounce = function (func) {
+ return func;
+ };
+ }
+ // fixme: throttle is inactive by default, should we make it explicit ?
+ const initialThrottle = _.throttle;
+ if (!('throttle' in params) || !params.throttle) {
+ _.throttle = function (func) {
+ return func;
+ };
+ }
+
+ // make sure images do not trigger a GET on the server
+ $('body').on('DOMNodeInserted.removeSRC', function (ev) {
+ let rpc;
+ if (params.mockSRC) {
+ rpc = mockServer.performRpc.bind(mockServer);
+ }
+ removeSrcAttribute(ev.target, rpc);
+ });
+
+ // 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;
+ Component.env = makeTestEnvironment(env, mockServer.performRpc.bind(mockServer));
+
+ // 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();
+ }
+ });
+
+ DebouncedField.prototype.DEBOUNCE = initialDebounceValue;
+ dom.DEBOUNCE = initialDOMDebounceValue;
+ _.debounce = initialDebounce;
+ _.throttle = initialThrottle;
+
+ // 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.
@@ -100,6 +460,7 @@ function removeSrcAttribute(el, widget) {
* 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.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.
@@ -130,11 +491,7 @@ function removeSrcAttribute(el, widget) {
* other methods on it.
*/
async function addMockEnvironment(widget, params) {
- var Server = MockServer;
- params.services = params.services || {};
- if (params.mockRPC) {
- Server = MockServer.extend({ _performRpc: params.mockRPC });
- }
+ // log events triggered up if debug flag is true
if (params.debug) {
_observe(widget);
var separator = window.location.href.indexOf('?') !== -1 ? "&" : "?";
@@ -142,6 +499,11 @@ async function addMockEnvironment(widget, params) {
console.log('%c[debug] debug mode activated', 'color: blue; font-weight: bold;', url);
}
+ // instantiate mock server
+ var Server = MockServer;
+ if (params.mockRPC) {
+ Server = MockServer.extend({ _performRpc: params.mockRPC });
+ }
var mockServer = new Server(params.data, {
actions: params.actions,
archs: params.archs,
@@ -150,311 +512,61 @@ async function addMockEnvironment(widget, params) {
widget: widget,
});
- // make sure images do not trigger a GET on the server
- $('body').on('DOMNodeInserted.removeSRC', function (event) {
- removeSrcAttribute(event.target, widget);
- });
-
- // make sure the debounce value for input fields is set to 0
- var initialDebounceValue = DebouncedField.prototype.DEBOUNCE;
- DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0;
- var initialDOMDebounceValue = dom.DEBOUNCE;
- dom.DEBOUNCE = 0;
- var initialSession, initialConfig, initialParameters, initialDebounce, initialThrottle;
- initialSession = _.extend({}, session);
- session.getTZOffset = function () {
- return 0; // by default, but may be overridden in specific tests
- };
- if ('session' in params) {
- _.extend(session, params.session);
- }
- if ('config' in params) {
- initialConfig = _.clone(config);
- initialConfig.device = _.clone(config.device);
- if ('device' in params.config) {
- _.extend(config.device, params.config.device);
- }
- if ('debug' in params.config) {
- odoo.debug = params.config.debug;
- }
- }
- if ('translateParameters' in params) {
- initialParameters = _.extend({}, core._t.database.parameters);
- _.extend(core._t.database.parameters, params.translateParameters);
- }
- if (params.debounce === false) {
- initialDebounce = _.debounce;
- _.debounce = function (func) {
- return func;
- };
- }
- if (!('throttle' in params) || !params.throttle) {
- initialThrottle = _.throttle;
- _.throttle = function (func) {
- return func;
- };
+ // 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(owl.Component, params, mockServer);
+ const env = owl.Component.env;
- var widgetDestroy = widget.destroy;
+ // ensure to clean up everything when the widget will be destroyed
+ const destroy = widget.destroy;
widget.destroy = function () {
- // clear the caches (e.g. data_manager, ModelFieldSelector) when the
- // widget is destroyed, at the end of each test to avoid collisions
- core.bus.trigger('clear_cache');
-
- Object.keys(services).forEach(function(s) {
- var service = services[s];
- if (service && !service.isDestroyed())
- service.destroy();
- });
-
- DebouncedField.prototype.DEBOUNCE = initialDebounceValue;
- dom.DEBOUNCE = initialDOMDebounceValue;
- if (params.debounce === false) {
- _.debounce = initialDebounce;
- }
- if (!('throttle' in params) || !params.throttle) {
- _.throttle = initialThrottle;
- }
-
- var key;
- if ('session' in params) {
- for (key in session) {
- delete session[key];
- }
- }
- _.extend(session, initialSession);
- if ('config' in params) {
- for (key in config) {
- delete config[key];
- }
- _.extend(config, initialConfig);
- }
- if ('translateParameters' in params) {
- for (key in core._t.database.parameters) {
- delete core._t.database.parameters[key];
- }
- _.extend(core._t.database.parameters, initialParameters);
- }
-
- $('body').off('DOMNodeInserted.removeSRC');
- $('body').removeClass('debug');
- $('.blockUI').remove();
-
- widgetDestroy.call(this);
+ cleanUp();
+ destroy.call(this, ...arguments);
};
- // Dispatch service calls
- // Note: some services could call other services at init,
- // Which is why we have to init services after that
- var services = {};
+ // intercept service/data manager calls and redirect them to the env
intercept(widget, 'call_service', function (ev) {
- var args, result;
- if (services[ev.data.service]) {
- var service = services[ev.data.service];
- args = (ev.data.args || []);
- result = service[ev.data.method].apply(service, args);
- } else if (ev.data.service === 'ajax' && ev.data.method === 'rpc') {
- // use ajax service that is mocked by the server
- var route = ev.data.args[0];
- args = ev.data.args[1];
- result = mockServer.performRpc(route, args);
+ 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);
}
- ev.data.callback(result);
});
-
- intercept(widget, 'load_action', function (event) {
- mockServer.performRpc('/web/action/load', {
- action_id: event.data.actionID,
- additional_context: event.data.context,
- }).then(function (action) {
- event.data.on_success(action);
- });
+ intercept(widget, 'load_action', async ev => {
+ const action = await env.dataManager.load_action(ev.data.actionID, ev.data.context);
+ ev.data.on_success(action);
});
-
- intercept(widget, "load_views", function (event) {
- mockServer.performRpc('/web/dataset/call_kw/' + event.data.modelName, {
- args: [],
- kwargs: {
- context: event.data.context,
- options: event.data.options,
- views: event.data.views,
- },
- method: 'load_views',
- model: event.data.modelName,
- }).then(function (views) {
- views = _.mapObject(views, function (viewParams) {
- return fieldsViewGet(mockServer, viewParams);
- });
- if ('search' in views && params.favoriteFilters) {
- views.search.favoriteFilters = params.favoriteFilters;
- }
- event.data.on_success(views);
- });
+ intercept(widget, "load_views", async ev => {
+ const params = {
+ model: ev.data.modelName,
+ context: ev.data.context,
+ views_descr: ev.data.views,
+ };
+ const views = await env.dataManager.load_views(params, ev.data.options);
+ if ('search' in views && params.favoriteFilters) {
+ views.search.favoriteFilters = params.favoriteFilters;
+ }
+ ev.data.on_success(views);
});
-
- intercept(widget, "get_session", function (event) {
- event.data.callback(session);
+ intercept(widget, "get_session", ev => {
+ ev.data.callback(session);
});
-
- intercept(widget, "load_filters", function (event) {
- if (params.debug) {
- console.log('[mock] load_filters', event.data);
- }
- event.data.on_success([]);
+ intercept(widget, "load_filters", async ev => {
+ const filters = await env.dataManager.load_filters(ev.data);
+ ev.data.on_success(filters);
});
- // make sure all Odoo events bubbling up are intercepted
- if ('intercepts' in params) {
- _.each(params.intercepts, function (cb, name) {
- intercept(widget, name, cb);
- });
- }
-
- // Deploy services
- var done = false;
- var servicesToDeploy = _.clone(params.services);
- if (!servicesToDeploy.ajax) {
- services.ajax = null; // use mocked ajax from mocked server
- }
- while (!done) {
- var serviceName = _.findKey(servicesToDeploy, function (Service) {
- return !_.some(Service.prototype.dependencies, function (depName) {
- return !_.has(services, depName);
- });
- });
- if (serviceName) {
- var Service = servicesToDeploy[serviceName];
- var service = services[serviceName] = new Service(widget);
- delete servicesToDeploy[serviceName];
-
- intercept(service, "get_session", function (event) {
- event.data.callback(session);
- });
- service.start();
- } else {
- var serviceNames = _.keys(servicesToDeploy);
- if (serviceNames.length) {
- console.warn("Non loaded services:", serviceNames);
- }
- done = true;
- }
- }
-
- // Wait for asynchronous services to properly start
- await new Promise(setTimeout);
+ // 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;
}
/**
- * performs a fields_view_get, and mocks the postprocessing done by the
- * data_manager to return an equivalent structure.
- *
- * @param {MockServer} server
- * @param {Object} params
- * @param {string} params.model
- * @returns {Object} an object with 3 keys: arch, fields and viewFields
- */
-function fieldsViewGet(server, params) {
- var fieldsView = server.fieldsViewGet(params);
- // mock the structure produced by the DataManager
- fieldsView.viewFields = fieldsView.fields;
- fieldsView.fields = server.fieldsGet(params.model);
- return fieldsView;
-}
-
-/**
- * Returns a mocked environment to be used by OWL components in tests.
- *
- * @param {Object} [params]
- * @param {Object} [params.actions] the actions given to the mock server
- * @param {Object} [params.archs] this archs given to the mock server
- * @param {Object} [params.data] the business data given to the mock server
- * @param {boolean} [params.debug]
- * @param {Object} [params.env]
- * @param {function} [params.mockRPC]
- * @param {function} [params.server] an already instantiated server to avoid
- * creating another one.
- * @returns {Object}
- */
-function getMockedOwlEnv(params = {}) {
- let server;
- if (params.server) {
- server = params.server;
- } else {
- let Server = MockServer;
- if (params.mockRPC) {
- Server = MockServer.extend({ _performRpc: params.mockRPC });
- }
- server = new Server(params.data, {
- actions: params.actions,
- archs: params.archs,
- debug: params.debug,
- });
- }
- const env = Object.assign({
- dataManager: {
- load_action: (actionID, context) => {
- return server.performRpc('/web/action/load', {
- kwargs: {
- action_id: actionID,
- additional_context: context,
- },
- });
- },
- load_views: (params, options) => {
- return server.performRpc('/web/dataset/call_kw/' + params.model, {
- args: [],
- kwargs: {
- context: params.context,
- options: options,
- views: params.views_descr,
- },
- method: 'load_views',
- model: params.model,
- }).then(function (views) {
- return _.mapObject(views, viewParams => {
- return fieldsViewGet(server, viewParams);
- });
- });
- },
- load_filters: params => {
- if (params.debug) {
- console.log('[mock] load_filters', params);
- }
- return Promise.resolve([]);
- },
- },
- session: params.session || {},
- }, params.env);
- return makeTestEnvironment(env, server.performRpc.bind(server));
-}
-
-/**
- * intercepts an event bubbling up the widget hierarchy. The event intercepted
- * must be a "custom event", i.e. an event generated by the method 'trigger_up'.
- *
- * Note that this method really intercepts the event if @propagate is not set.
- * It will not be propagated further, and even the handlers on the target will
- * not fire.
- *
- * @param {Widget} widget the target widget (any Odoo widget)
- * @param {string} eventName description of the event
- * @param {function} fn callback executed when the even is intercepted
- * @param {boolean} [propagate=false]
- */
-function intercept(widget, eventName, fn, propagate) {
- var _trigger_up = widget._trigger_up.bind(widget);
- widget._trigger_up = function (event) {
- if (event.name === eventName) {
- fn(event);
- if (!propagate) { return; }
- }
- _trigger_up(event);
- };
-}
-
-/**
* Patch window.Date so that the time starts its flow from the provided Date.
*
* Usage:
@@ -644,7 +756,7 @@ function patchSetTimeout() {
return {
addMockEnvironment: addMockEnvironment,
fieldsViewGet: fieldsViewGet,
- getMockedOwlEnv: getMockedOwlEnv,
+ addMockEnvironmentOwl: addMockEnvironmentOwl,
intercept: intercept,
patchDate: patchDate,
patch: patch,
diff --git a/addons/web/static/tests/owl_compatibility_tests.js b/addons/web/static/tests/owl_compatibility_tests.js
index f49fee5f78a..1cc463d0eeb 100644
--- a/addons/web/static/tests/owl_compatibility_tests.js
+++ b/addons/web/static/tests/owl_compatibility_tests.js
@@ -5,9 +5,9 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
const testUtils = require('web.test_utils');
const Widget = require('web.Widget');
- const getMockedOwlEnv = testUtils.mock.getMockedOwlEnv;
const makeTestPromise = testUtils.makeTestPromise;
const nextTick = testUtils.nextTick;
+ const addMockEnvironmentOwl = testUtils.mock.addMockEnvironmentOwl;
const { Component, tags, useState } = owl;
const { xml } = tags;
@@ -448,17 +448,17 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
this.MyWidget = MyWidget;
}
}
- Parent.env = getMockedOwlEnv({
- mockRPC: function (route, args) {
- assert.step(`${route} ${args.val}`);
- return Promise.resolve();
- },
- });
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 = testUtils.prepareTarget();
const parent = new Parent();
@@ -468,6 +468,7 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
assert.verifySteps(['some/route 2']);
parent.destroy();
+ cleanUp();
});
QUnit.test("sub widget that calls a service", async function (assert) {
@@ -493,16 +494,14 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
this.MyWidget = MyWidget;
}
}
- const env = getMockedOwlEnv();
- env.services.math = {
- sqrt: v => Math.sqrt(v),
- };
- Parent.env = env;
Parent.template = xml`
<div>
<ComponentAdapter Component="MyWidget"/>
</div>`;
Parent.components = { ComponentAdapter };
+ Parent.env.services.math = {
+ sqrt: v => Math.sqrt(v),
+ };
const target = testUtils.prepareTarget();
const parent = new Parent();
@@ -525,20 +524,21 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
this.MyWidget = MyWidget;
}
}
- Parent.env = getMockedOwlEnv({
- session: { key: 'value' },
- });
Parent.template = xml`
<div>
<ComponentAdapter Component="MyWidget"/>
</div>`;
Parent.components = { ComponentAdapter };
+ const cleanUp = await addMockEnvironmentOwl(Parent, {
+ session: { key: 'value' },
+ });
const target = testUtils.prepareTarget();
const parent = new Parent();
await parent.mount(target);
parent.destroy();
+ cleanUp();
});
QUnit.test("sub widget that calls load_views", async function (assert) {
@@ -555,7 +555,12 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
this.MyWidget = MyWidget;
}
}
- Parent.env = getMockedOwlEnv({
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+ const cleanUp = await addMockEnvironmentOwl(Parent, {
mockRPC: function (route, args) {
assert.strictEqual(route, '/web/dataset/call_kw/some_model');
assert.deepEqual(args.kwargs.context, { x: 2 });
@@ -563,11 +568,6 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
return Promise.resolve();
},
});
- Parent.template = xml`
- <div>
- <ComponentAdapter Component="MyWidget"/>
- </div>`;
- Parent.components = { ComponentAdapter };
const target = testUtils.prepareTarget();
const parent = new Parent();
@@ -576,6 +576,7 @@ odoo.define('web.OwlCompatibilityTests', function (require) {
assert.strictEqual(parent.el.innerHTML, '<div></div>');
parent.destroy();
+ cleanUp();
});
QUnit.test("sub widgets in a t-if/t-else", async function (assert) {
diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js
index 26fc002605a..7d1554dc78d 100644
--- a/addons/web/static/tests/views/view_dialogs_tests.js
+++ b/addons/web/static/tests/views/view_dialogs_tests.js
@@ -12,8 +12,7 @@ var createView = testUtils.createView;
async function createParent(params) {
var widget = new Widget();
- params.server = await testUtils.mock.addMockEnvironment(widget, params);
- owl.Component.env = testUtils.mock.getMockedOwlEnv(params);
+ params.server = await testUtils.mock.addMockEnvironment(widget, params);
return widget;
}