summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/helpers/test_utils_create.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/tests/helpers/test_utils_create.js')
-rw-r--r--addons/web/static/tests/helpers/test_utils_create.js288
1 files changed, 288 insertions, 0 deletions
diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js
new file mode 100644
index 00000000000..d4d4e2a6b55
--- /dev/null
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -0,0 +1,288 @@
+odoo.define('web.test_utils_create', function (require) {
+"use strict";
+
+/**
+ * Create Test Utils
+ *
+ * This module defines various utility functions to help creating mock widgets
+ *
+ * Note that all methods defined in this module are exported in the main
+ * testUtils file.
+ */
+
+var ActionManager = require('web.ActionManager');
+var config = require('web.config');
+var ControlPanel = require('web.ControlPanel');
+var dom = require('web.dom');
+var DebugManager = require('web.DebugManager');
+var testUtilsMock = require('web.test_utils_mock');
+var Widget = require('web.Widget');
+
+/**
+ * create and return an instance of ActionManager with all rpcs going through a
+ * mock method using the data, actions and archs objects as sources.
+ *
+ * @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 {function} [params.mockRPC]
+ * @returns {ActionManager}
+ */
+function createActionManager (params) {
+ params = params || {};
+ var $target = $('#qunit-fixture');
+ if (params.debug) {
+ $target = $('body');
+ $target.addClass('debug');
+ }
+
+ var widget = new Widget();
+ // when 'document' addon is installed, the sidebar does a 'search_read' on
+ // model 'ir_attachment' each time a record is open, so we monkey-patch
+ // 'mockRPC' to mute those RPCs, so that the tests can be written uniformly,
+ // whether or not 'document' is installed
+ var mockRPC = params.mockRPC;
+ _.extend(params, {
+ mockRPC: function (route, args) {
+ if (args.model === 'ir.attachment') {
+ return $.when([]);
+ }
+ if (mockRPC) {
+ return mockRPC.apply(this, arguments);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ testUtilsMock.addMockEnvironment(widget, _.defaults(params, { debounce: false }));
+ widget.prependTo($target);
+ widget.$el.addClass('o_web_client');
+ if (config.device.isMobile) {
+ widget.$el.addClass('o_touch_device');
+ }
+
+ var userContext = params.context && params.context.user_context || {};
+ var actionManager = new ActionManager(widget, userContext);
+
+ var originalDestroy = ActionManager.prototype.destroy;
+ actionManager.destroy = function () {
+ actionManager.destroy = originalDestroy;
+ widget.destroy();
+ };
+ actionManager.appendTo(widget.$el);
+
+ return actionManager;
+}
+
+/**
+ * create a view from various parameters. Here, a view means a javascript
+ * instance of an AbstractView class, such as a form view, a list view or a
+ * kanban view.
+ *
+ * It returns the instance of the view, properly created, with all rpcs going
+ * through a mock method using the data object as source, and already loaded/
+ * started.
+ *
+ * Most views can be tested synchronously (@see createView), but some view have
+ * external dependencies (like lazy loaded libraries). In that case, it is
+ * necessary to use this method.
+ *
+ * @param {Object} params
+ * @param {string} params.arch the xml (arch) of the view to be instantiated
+ * @param {any[]} [params.domain] the initial domain for the view
+ * @param {Object} [params.context] the initial context for the view
+ * @param {Object} [params.debug=false] if true, the widget will be appended in
+ * the DOM. Also, RPCs and uncaught OdooEvent will be logged
+ * @param {string[]} [params.groupBy] the initial groupBy for the view
+ * @param {integer} [params.fieldDebounce=0] the debounce value to use for the
+ * duration of the test.
+ * @param {AbstractView} params.View the class that will be instantiated
+ * @param {string} params.model a model name, will be given to the view
+ * @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
+ * @returns {Deferred<AbstractView>} resolves with the instance of the view
+ */
+function createAsyncView(params) {
+ var $target = $('#qunit-fixture');
+ var widget = new Widget();
+ if (params.debug) {
+ $target = $('body');
+ $target.addClass('debug');
+ }
+
+ // add mock environment: mock server, session, fieldviewget, ...
+ var mockServer = testUtilsMock.addMockEnvironment(widget, params);
+ var viewInfo = testUtilsMock.fieldsViewGet(mockServer, params);
+ // create the view
+ var viewOptions = {
+ modelName: params.model || 'foo',
+ ids: 'res_id' in params ? [params.res_id] : undefined,
+ currentId: 'res_id' in params ? params.res_id : undefined,
+ domain: params.domain || [],
+ context: params.context || {},
+ groupBy: params.groupBy || [],
+ };
+ if (params.hasSelectors) {
+ viewOptions.hasSelectors = params.hasSelectors;
+ }
+
+ _.extend(viewOptions, params.viewOptions);
+
+ var view = new params.View(viewInfo, viewOptions);
+
+ // reproduce the DOM environment of views
+ var $web_client = $('<div>').addClass('o_web_client').prependTo($target);
+ var controlPanel = new ControlPanel(widget);
+ controlPanel.appendTo($web_client);
+ var $content = $('<div>').addClass('o_content').appendTo($web_client);
+
+ if (params.interceptsPropagate) {
+ _.each(params.interceptsPropagate, function (cb, name) {
+ testUtilsMock.intercept(widget, name, cb, true);
+ });
+ }
+
+ return view.getController(widget).then(function (view) {
+ // override the view's 'destroy' so that it calls 'destroy' on the widget
+ // instead, as the widget is the parent of the view and the mockServer.
+ view.__destroy = view.destroy;
+ view.destroy = function () {
+ // remove the override to properly destroy the view and its children
+ // when it will be called the second time (by its parent)
+ delete view.destroy;
+ widget.destroy();
+ };
+
+ // link the view to the control panel
+ view.set_cp_bus(controlPanel.get_bus());
+
+ // render the view in a fragment as they must be able to render correctly
+ // without being in the DOM
+ var fragment = document.createDocumentFragment();
+ return view.appendTo(fragment).then(function () {
+ dom.append($content, fragment, {
+ callbacks: [{ widget: view }],
+ in_DOM: true,
+ });
+ view.$el.on('click', 'a', function (ev) {
+ ev.preventDefault();
+ });
+
+ return view;
+ });
+ });
+}
+
+/**
+ * Create and return an instance of DebugManager with all rpcs going through a
+ * mock method, assuming that the user has access rights, and is an admin.
+ *
+ * @param {Object} [params={}]
+ */
+function createDebugManager (params) {
+ params = params || {};
+ var mockRPC = params.mockRPC;
+ _.extend(params, {
+ mockRPC: function (route, args) {
+ if (args.method === 'check_access_rights') {
+ return $.when(true);
+ }
+ if (args.method === 'xmlid_to_res_id') {
+ return $.when(true);
+ }
+ if (mockRPC) {
+ return mockRPC.apply(this, arguments);
+ }
+ return this._super.apply(this, arguments);
+ },
+ session: {
+ user_has_group: function (group) {
+ if (group === 'base.group_no_one') {
+ return $.when(true);
+ }
+ return this._super.apply(this, arguments);
+ },
+ },
+ });
+ var debugManager = new DebugManager();
+ testUtilsMock.addMockEnvironment(debugManager, params);
+ return debugManager;
+}
+
+/**
+ * create a model from given parameters.
+ *
+ * @param {Object} params This object will be given to addMockEnvironment, so
+ * any parameters from that method applies
+ * @param {Class} params.Model the model class to use
+ * @returns {Model}
+ */
+function createModel(params) {
+ var widget = new Widget();
+
+ var model = new params.Model(widget);
+
+ 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.
+ model.destroy = function () {
+ // remove the override to properly destroy the model when it will be
+ // called the second time (by its parent)
+ delete model.destroy;
+ widget.destroy();
+ };
+
+ return model;
+}
+
+/**
+ * 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 {Widget}
+ */
+function createParent(params) {
+ var widget = new Widget();
+ testUtilsMock.addMockEnvironment(widget, params);
+ return widget;
+}
+
+/**
+ * create a view synchronously. This method uses the createAsyncView method.
+ * Most views are synchronous, so the deferred can be resolved immediately and
+ * this method will work.
+ *
+ * Be careful, if for some reason a view is async, this method will crash.
+ * @see createAsyncView
+ *
+ * @param {Object} params will be given to createAsyncView
+ * @returns {AbstractView}
+ */
+function createView(params) {
+ var view;
+ createAsyncView(params).then(function (result) {
+ view = result;
+ });
+ if (!view) {
+ throw "The view that you are trying to create is async. Please use createAsyncView instead";
+ }
+ return view;
+}
+
+
+return {
+ createActionManager: createActionManager,
+ createAsyncView: createAsyncView,
+ createDebugManager: createDebugManager,
+ createModel: createModel,
+ createParent: createParent,
+ createView: createView,
+};
+
+});