summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/helpers
diff options
context:
space:
mode:
authorVincent Schippefilt <vsc@odoo.com>2019-03-06 14:02:32 +0100
committerAaron Bohy <aab@odoo.com>2019-03-06 20:07:17 +0100
commitab56e637b7f97ddcdbcc9baea99ae097e342409c (patch)
tree4cb47da9f7ef89b19d18429220a7507470ec12a9 /addons/web/static/tests/helpers
parenteeed894fd50e477135e1ac9a82f6e98ce063ab90 (diff)
[REF] web: adapt code after jQuery update
Part of task 1896658 Co-authored-by: Aaron Bohy <aab@odoo.com> Co-authored-by: Christophe Matthieu <chm@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com> Co-authored-by: David Monjoie <dmo@odoo.com> Co-authored-by: Martin Geubelle <mge@odoo.com> Co-authored-by: svs-odoo <svs@odoo.com> Co-authored-by: Vincent Schippefilt <vsc@odoo.com>
Diffstat (limited to 'addons/web/static/tests/helpers')
-rw-r--r--addons/web/static/tests/helpers/mock_server.js71
-rw-r--r--addons/web/static/tests/helpers/qunit_asserts.js17
-rw-r--r--addons/web/static/tests/helpers/qunit_config.js58
-rw-r--r--addons/web/static/tests/helpers/test_utils.js85
-rw-r--r--addons/web/static/tests/helpers/test_utils_create.js94
-rw-r--r--addons/web/static/tests/helpers/test_utils_dom.js78
-rw-r--r--addons/web/static/tests/helpers/test_utils_fields.js102
-rw-r--r--addons/web/static/tests/helpers/test_utils_file.js35
-rw-r--r--addons/web/static/tests/helpers/test_utils_form.js10
-rw-r--r--addons/web/static/tests/helpers/test_utils_graph.js2
-rw-r--r--addons/web/static/tests/helpers/test_utils_kanban.js15
-rw-r--r--addons/web/static/tests/helpers/test_utils_mock.js31
-rw-r--r--addons/web/static/tests/helpers/test_utils_modal.js2
-rw-r--r--addons/web/static/tests/helpers/test_utils_pivot.js6
14 files changed, 440 insertions, 166 deletions
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index ff75722b333..44cc276e1c8 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -103,9 +103,9 @@ var MockServer = Class.extend({
*
* @param {string} route
* @param {Object} args
- * @returns {Deferred<any>}
+ * @returns {Promise<any>}
* Resolved with the result of the RPC, stringified then parsed.
- * If the RPC should fail, the deferred will be rejected with the
+ * If the RPC should fail, the promise will be rejected with the
* error object, stringified then parsed.
*/
performRpc: function (route, args) {
@@ -132,17 +132,18 @@ var MockServer = Class.extend({
console.log('%c[rpc] response' + route, 'color: blue; font-weight: bold;', JSON.parse(resultString));
}
return JSON.parse(resultString);
- }, function (result, event) {
- var errorString = JSON.stringify(result || false);
+ }, function (result) {
+ var message = result && result.message;
+ var event = result && result.event;
+ var errorString = JSON.stringify(message || false);
if (debug) {
console.log('%c[rpc] response (error) ' + route, 'color: orange; font-weight: bold;', JSON.parse(errorString));
}
- return $.Deferred().reject(errorString, event || $.Event());
+ return Promise.reject({message: errorString, event: event || $.Event()});
});
- var promise = def.promise();
- promise.abort = abort;
- return promise;
+ def.abort = abort;
+ return def;
},
//--------------------------------------------------------------------------
@@ -861,7 +862,7 @@ var MockServer = Class.extend({
}
}
if (type === 'many2one') {
- var ids = _.pluck(records, aggregatedFields[i]);
+ var ids = _.pluck(records, aggregatedFields[i]);
group[aggregatedFields[i]] = _.uniq(ids).length || null;
}
}
@@ -1064,7 +1065,7 @@ var MockServer = Class.extend({
var result = this._mockSearchReadController({
model: model,
domain: kwargs.domain || args[0],
- fields: kwargs.fields || args[1],
+ fields: kwargs.fields || args[1],
offset: kwargs.offset || args[2],
limit: kwargs.limit || args[3],
order: kwargs.order || args[4],
@@ -1181,83 +1182,83 @@ var MockServer = Class.extend({
* @private
* @param {string} route
* @param {Object} args
- * @returns {Deferred<any>}
+ * @returns {Promise<any>}
* Resolved with the result of the RPC. If the RPC should fail, the
- * deferred should either be rejected or the call should throw an
+ * promise should either be rejected or the call should throw an
* exception (@see performRpc for error handling).
*/
_performRpc: function (route, args) {
switch (route) {
case '/web/action/load':
- return $.when(this._mockLoadAction(args.kwargs));
+ return Promise.resolve(this._mockLoadAction(args.kwargs));
case '/web/dataset/search_read':
- return $.when(this._mockSearchReadController(args));
+ return Promise.resolve(this._mockSearchReadController(args));
case '/web/dataset/resequence':
- return $.when(this._mockResequence(args));
+ return Promise.resolve(this._mockResequence(args));
}
if (route.indexOf('/web/image') >= 0 || _.contains(['.png', '.jpg'], route.substr(route.length - 4))) {
- return $.when();
+ return Promise.resolve();
}
switch (args.method) {
case 'copy':
- return $.when(this._mockCopy(args.model, args.args[0]));
+ return Promise.resolve(this._mockCopy(args.model, args.args[0]));
case 'create':
- return $.when(this._mockCreate(args.model, args.args[0]));
+ return Promise.resolve(this._mockCreate(args.model, args.args[0]));
case 'default_get':
- return $.when(this._mockDefaultGet(args.model, args.args, args.kwargs));
+ return Promise.resolve(this._mockDefaultGet(args.model, args.args, args.kwargs));
case 'fields_get':
- return $.when(this._mockFieldsGet(args.model, args.args));
+ return Promise.resolve(this._mockFieldsGet(args.model, args.args));
case 'search_panel_select_range':
- return $.when(this._mockSearchPanelSelectRange(args.model, args.args, args.kwargs));
+ return Promise.resolve(this._mockSearchPanelSelectRange(args.model, args.args, args.kwargs));
case 'search_panel_select_multi_range':
- return $.when(this._mockSearchPanelSelectMultiRange(args.model, args.args, args.kwargs));
+ return Promise.resolve(this._mockSearchPanelSelectMultiRange(args.model, args.args, args.kwargs));
case 'load_views':
- return $.when(this._mockLoadViews(args.model, args.kwargs));
+ return Promise.resolve(this._mockLoadViews(args.model, args.kwargs));
case 'name_get':
- return $.when(this._mockNameGet(args.model, args.args));
+ return Promise.resolve(this._mockNameGet(args.model, args.args));
case 'name_create':
- return $.when(this._mockNameCreate(args.model, args.args));
+ return Promise.resolve(this._mockNameCreate(args.model, args.args));
case 'name_search':
- return $.when(this._mockNameSearch(args.model, args.args, args.kwargs));
+ return Promise.resolve(this._mockNameSearch(args.model, args.args, args.kwargs));
case 'onchange':
- return $.when(this._mockOnchange(args.model, args.args));
+ return Promise.resolve(this._mockOnchange(args.model, args.args));
case 'read':
- return $.when(this._mockRead(args.model, args.args, args.kwargs));
+ return Promise.resolve(this._mockRead(args.model, args.args, args.kwargs));
case 'read_group':
- return $.when(this._mockReadGroup(args.model, args.kwargs));
+ return Promise.resolve(this._mockReadGroup(args.model, args.kwargs));
case 'read_progress_bar':
- return $.when(this._mockReadProgressBar(args.model, args.kwargs));
+ return Promise.resolve(this._mockReadProgressBar(args.model, args.kwargs));
case 'search_count':
- return $.when(this._mockSearchCount(args.model, args.args));
+ return Promise.resolve(this._mockSearchCount(args.model, args.args));
case 'search_read':
- return $.when(this._mockSearchRead(args.model, args.args, args.kwargs));
+ return Promise.resolve(this._mockSearchRead(args.model, args.args, args.kwargs));
case 'unlink':
- return $.when(this._mockUnlink(args.model, args.args));
+ return Promise.resolve(this._mockUnlink(args.model, args.args));
case 'write':
- return $.when(this._mockWrite(args.model, args.args));
+ return Promise.resolve(this._mockWrite(args.model, args.args));
}
var model = this.data[args.model];
if (model && typeof model[args.method] === 'function') {
- return $.when(this.data[args.model][args.method](args.args, args.kwargs));
+ return Promise.resolve(this.data[args.model][args.method](args.args, args.kwargs));
}
throw new Error("Unimplemented route: " + route);
diff --git a/addons/web/static/tests/helpers/qunit_asserts.js b/addons/web/static/tests/helpers/qunit_asserts.js
index bce6ef2a777..e8f9948d347 100644
--- a/addons/web/static/tests/helpers/qunit_asserts.js
+++ b/addons/web/static/tests/helpers/qunit_asserts.js
@@ -37,7 +37,7 @@ function _checkClass(w, className, shouldHaveClass, msg) {
var descr = `${shouldHaveClass ? 'hasClass' : 'doesNotHaveClass'} ${className}`;
QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
} else {
- msg = msg || `target ${$el.selector || ''} should ${shouldHaveClass ? '' : 'not'} have class ${className}`;
+ msg = msg || `target should ${shouldHaveClass ? '' : 'not'} have class ${className}`;
var hasClass = $el.hasClass(className);
var condition = shouldHaveClass ? hasClass : !hasClass;
QUnit.assert.ok(condition, msg);
@@ -63,8 +63,15 @@ function _checkVisible(w, shouldBeVisible, msg) {
var descr = `${shouldBeVisible ? 'isVisible' : 'isNotVisible'}`;
QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
} else {
- msg = msg || `target ${$el.selector} should ${shouldBeVisible ? '' : 'not'} be visible`;
+ msg = msg || `target should ${shouldBeVisible ? '' : 'not'} be visible`;
var isVisible = $el.is(':visible');
+ if (isVisible) {
+ // Additional test to see if $el is really visible, since jQuery
+ // considers an element visible if it has a DOWRect, even if its
+ // width and height are equal to zero.
+ var boundingClientRect = $el[0].getBoundingClientRect();
+ isVisible = boundingClientRect.width + boundingClientRect.height;
+ }
var condition = shouldBeVisible ? isVisible : !isVisible;
QUnit.assert.ok(condition, msg);
}
@@ -96,9 +103,7 @@ function containsN(w, selector, n, msg) {
var $matches = $el.find(selector);
if (!msg) {
msg = `Selector '${selector}' should have exactly ${n} matches`;
- if ($el.selector) {
- msg += ` (inside '${$el.selector}')`;
- }
+ msg += ` (inside the target)`;
}
QUnit.assert.strictEqual($matches.length, n, msg);
}
@@ -181,7 +186,7 @@ function hasAttrValue(w, attr, value, msg) {
var descr = `hasAttrValue (${attr}: ${value})`;
QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
} else {
- msg = msg || `attribute '${attr}' of target ${$el.selector || ''} should be '${value}'`;
+ msg = msg || `attribute '${attr}' of target should be '${value}'`;
QUnit.assert.strictEqual($el.attr(attr), value, msg);
}
}
diff --git a/addons/web/static/tests/helpers/qunit_config.js b/addons/web/static/tests/helpers/qunit_config.js
index 511b9cd2fce..8c6496bb8f9 100644
--- a/addons/web/static/tests/helpers/qunit_config.js
+++ b/addons/web/static/tests/helpers/qunit_config.js
@@ -92,6 +92,64 @@ QUnit.moduleDone(function(result) {
});
/**
+ * After each test, we check that there is no leftover in the DOM.
+ *
+ * Note: this event is not QUnit standard, we added it for this specific use case.
+ */
+QUnit.on('OdooAfterTestHook', function () {
+ // check for leftover elements in the body
+ var $bodyChilds = $('body > *');
+ var validElements = [
+ // always in the body:
+ {tagName: 'DIV', attrToCompare: 'id', value: 'qunit'},
+ {tagName: 'DIV', attrToCompare: 'id', value: 'qunit-fixture'},
+ {tagName: 'SCRIPT', attrToCompare: 'id', value: ''},
+ // shouldn't be in the body after a test but are tolerated:
+ {tagName: 'DIV', attrToCompare: 'className', value: 'o_notification_manager'},
+ {tagName: 'DIV', attrToCompare: 'className', value: 'tooltip fade bs-tooltip-auto'},
+ {tagName: 'DIV', attrToCompare: 'className', value: 'tooltip fade bs-tooltip-auto show'},
+ {tagName: 'I', attrToCompare: 'title', value: 'Raphaël Colour Picker'},
+ {tagName: 'SPAN', attrToCompare: 'className', value: 'select2-hidden-accessible'},
+ // Due to a Document Kanban bug (already present in 12.0)
+ {tagName: 'DIV', attrToCompare: 'className', value: 'ui-helper-hidden-accessible'},
+ {tagName: 'UL', attrToCompare: 'className', value: 'ui-menu ui-widget ui-widget-content ui-autocomplete ui-front'},
+ ];
+ if ($bodyChilds.length > 3) {
+ for (var i = 0; i < $bodyChilds.length; i++) {
+ var bodyChild = $bodyChilds[i];
+ var isValid = false;
+
+ for (var j = 0; j < validElements.length; j++) {
+ var toleratedElement = validElements[j];
+ if (toleratedElement.tagName === bodyChild.tagName) {
+ var attr = toleratedElement.attrToCompare;
+ if (toleratedElement.value === bodyChild[attr]) {
+ isValid = true;
+ break;
+ }
+ }
+ }
+
+ if (!isValid) {
+ if (!document.body.classList.contains('debug')) {
+ $(bodyChild).remove();
+ }
+ QUnit.pushFailure(`Body still contains undesirable elements`);
+ }
+ }
+ }
+
+ // check for leftovers in #qunit-fixture
+ var qunitFixture = document.getElementById('qunit-fixture');
+ if (qunitFixture.children.length) {
+ QUnit.pushFailure(`#qunit-fixture still contains elements`);
+ if (!document.body.classList.contains('debug')) {
+ $(qunitFixture.children).remove();
+ }
+ }
+});
+
+/**
* Add a sort button on top of the QUnit result page, so we can see which tests
* take the most time.
*/
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 2897b02214c..ccf5d8daf6b 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -11,7 +11,9 @@ odoo.define('web.test_utils', function (require) {
*/
var ajax = require('web.ajax');
+var concurrency = require('web.concurrency');
var core = require('web.core');
+var relationalFields = require('web.relational_fields');
var session = require('web.session');
var testUtilsCreate = require('web.test_utils_create');
var testUtilsDom = require('web.test_utils_dom');
@@ -25,22 +27,89 @@ var testUtilsModal = require('web.test_utils_modal');
var testUtilsPivot = require('web.test_utils_pivot');
var tools = require('web.tools');
+
function deprecated(fn, type) {
var msg = `Helper 'testUtils.${fn.name}' is deprecated. ` +
`Please use 'testUtils.${type}.${fn.name}' instead.`;
return tools.deprecated(fn, msg);
}
+/**
+ * Helper function, make a promise with a public resolve function. Note that
+ * this is not standard and should not be used outside of tests...
+ *
+ * @returns {Promise + resolve and reject function}
+ */
+function makeTestPromise() {
+ var resolve;
+ var reject;
+ var promise = new Promise(function (_resolve, _reject) {
+ resolve = _resolve;
+ reject = _reject;
+ });
+ promise.resolve = function () {
+ resolve.apply(null, arguments);
+ return promise;
+ };
+ promise.reject = function () {
+ reject.apply(null, arguments);
+ return promise;
+ };
+ return promise;
+}
+
+/**
+ * Make a promise with public resolve and reject functions (see
+ * @makeTestPromise). Perform an assert.step when the promise is
+ * resolved/rejected.
+ *
+ * @param {Object} assert instance object with the assertion methods
+ * @param {function} assert.step
+ * @param {string} str message to pass to assert.step
+ * @returns {Promise + resolve and reject function}
+ */
+function makeTestPromiseWithAssert(assert, str) {
+ var prom = makeTestPromise();
+ prom.then(() => assert.step('ok ' + str)).catch(function () {});
+ prom.catch(() => assert.step('ko ' + str));
+ return prom;
+}
+
+/**
+ * Create a new promise that can be waited by the caller in order to execute
+ * code after the next microtask tick and before the next jobqueue tick.
+ *
+ * @return {Promise} an already fulfilled promise
+ */
+async function nextMicrotaskTick() {
+ return Promise.resolve();
+}
+
+/**
+ * Returns a promise that is resolved in the next jobqueue tick so that the
+ * caller can wait on it in order to execute code in the next jobqueue tick.
+ *
+ * @return {Promise} a promise that will be fulfilled in the next jobqueue tick
+ */
+async function nextTick() {
+ return concurrency.delay(0);
+}
+
// Loading static files cannot be properly simulated when their real content is
// really needed. This is the case for static XML files so we load them here,
// before starting the qunit test suite.
// (session.js is in charge of loading the static xml bundle and we also have
// to load xml files that are normally lazy loaded by specific widgets).
-return $.when(
+return Promise.all([
session.is_bound,
ajax.loadXML('/web/static/src/xml/dialog.xml', core.qweb)
-).then(function () {
+]).then(function () {
setTimeout(function () {
+ // jquery autocomplete refines the search in a setTimeout() parameterized
+ // with a delay, so we force this delay to 0 s.t. the dropdown is filtered
+ // directly on the next tick
+ relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
+
// this is done with the hope that tests are
// only started all together...
QUnit.start();
@@ -53,6 +122,7 @@ return $.when(
patchDate: testUtilsMock.patchDate,
unpatch: testUtilsMock.unpatch,
fieldsViewGet: testUtilsMock.fieldsViewGet,
+ patchSetTimeout: testUtilsMock.patchSetTimeout,
},
dom: {
triggerKeypressEvent: testUtilsDom.triggerKeypressEvent,
@@ -63,6 +133,7 @@ return $.when(
click: testUtilsDom.click,
clickFirst: testUtilsDom.clickFirst,
clickLast: testUtilsDom.clickLast,
+ triggerEvents: testUtilsDom.triggerEvents,
},
form: {
clickEdit: testUtilsForm.clickEdit,
@@ -99,6 +170,9 @@ return $.when(
editInput: testUtilsFields.editInput,
editSelect: testUtilsFields.editSelect,
editAndTrigger: testUtilsFields.editAndTrigger,
+ triggerKey: testUtilsFields.triggerKey,
+ triggerKeydown: testUtilsFields.triggerKeydown,
+ triggerKeyup: testUtilsFields.triggerKeyup,
},
file: {
createFile: testUtilsFile.createFile,
@@ -108,11 +182,16 @@ return $.when(
createActionManager: testUtilsCreate.createActionManager,
createDebugManager: testUtilsCreate.createDebugManager,
- createAsyncView: testUtilsCreate.createAsyncView,
+ createAsyncView: testUtilsCreate.createView,
+ createCalendarView: testUtilsCreate.createCalendarView,
createControlPanel: testUtilsCreate.createControlPanel,
createView: testUtilsCreate.createView,
createModel: testUtilsCreate.createModel,
createParent: testUtilsCreate.createParent,
+ makeTestPromise: makeTestPromise,
+ makeTestPromiseWithAssert: makeTestPromiseWithAssert,
+ nextMicrotaskTick: nextMicrotaskTick,
+ nextTick: nextTick,
// backward-compatibility
addMockEnvironment: deprecated(testUtilsMock.addMockEnvironment, 'mock'),
diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js
index 5a64512260e..7e4de1060bc 100644
--- a/addons/web/static/tests/helpers/test_utils_create.js
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -13,6 +13,7 @@ odoo.define('web.test_utils_create', function (require) {
var ActionManager = require('web.ActionManager');
var config = require('web.config');
var ControlPanelView = require('web.ControlPanelView');
+var concurrency = require('web.concurrency');
var DebugManager = require('web.DebugManager');
var dom = require('web.dom');
var testUtilsMock = require('web.test_utils_mock');
@@ -30,7 +31,7 @@ var Widget = require('web.Widget');
* @param {function} [params.mockRPC]
* @returns {ActionManager}
*/
-function createActionManager (params) {
+var createActionManager = async function (params) {
params = params || {};
var $target = $('#qunit-fixture');
if (params.debug) {
@@ -47,7 +48,7 @@ function createActionManager (params) {
_.extend(params, {
mockRPC: function (route, args) {
if (args.model === 'ir.attachment') {
- return $.when([]);
+ return Promise.resolve([]);
}
if (mockRPC) {
return mockRPC.apply(this, arguments);
@@ -56,7 +57,7 @@ function createActionManager (params) {
},
});
testUtilsMock.addMockEnvironment(widget, _.defaults(params, { debounce: false }));
- widget.prependTo($target);
+ await widget.prependTo($target);
widget.$el.addClass('o_web_client');
if (config.device.isMobile) {
widget.$el.addClass('o_touch_device');
@@ -71,14 +72,14 @@ function createActionManager (params) {
widget.destroy();
};
var fragment = document.createDocumentFragment();
- actionManager.appendTo(fragment).then(function () {
+ return actionManager.appendTo(fragment).then(function () {
dom.append(widget.$el, fragment, {
callbacks: [{ widget: actionManager }],
in_DOM: true,
});
+ return actionManager;
});
- return actionManager;
-}
+};
/**
* create a view from various parameters. Here, a view means a javascript
@@ -89,10 +90,6 @@ function createActionManager (params) {
* 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
@@ -109,9 +106,9 @@ function createActionManager (params) {
* 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
+ * @returns {Promise<AbstractController>} resolves with the instance of the view
*/
-function createAsyncView(params) {
+async function createView(params) {
var $target = $('#qunit-fixture');
var widget = new Widget();
if (params.debug) {
@@ -175,6 +172,7 @@ function createAsyncView(params) {
// when it will be called the second time (by its parent)
delete view.destroy;
widget.destroy();
+ $webClient.remove();
testUtilsMock.unpatch(View);
};
@@ -187,14 +185,45 @@ function createAsyncView(params) {
callbacks: [{ widget: view }],
in_DOM: true,
});
+
view.$el.on('click', 'a', function (ev) {
ev.preventDefault();
});
-
return view;
});
});
}
+
+/**
+ * Similar as createView, but specific for calendar views. Some calendar
+ * tests need to trigger positional clicks on the DOM produced by fullcalendar.
+ * Those tests must use this helper with option positionalClicks set to true.
+ * This will move the rendered calendar to the body (required to do positional
+ * clicks), and wait for a setTimeout(0) before returning, because fullcalendar
+ * makes the calendar scroll to 6:00 in a setTimeout(0), which might have an
+ * impact according to where we want to trigger positional clicks.
+ *
+ * @param {Object} params see @createView
+ * @param {Object} [options]
+ * @param {boolean} [options.positionalClicks=false]
+ * @returns {Promise<CalendarController>}
+ */
+async function createCalendarView(params, options) {
+ var calendar = await createView(params);
+ if (!options || !options.positionalClicks) {
+ return calendar;
+ }
+ var $view = $('#qunit-fixture').contents();
+ $view.prependTo('body');
+ var destroy = calendar.destroy;
+ calendar.destroy = function () {
+ $view.remove();
+ destroy();
+ };
+ await concurrency.delay(0);
+ return calendar;
+}
+
/**
* create a controlPanel from various parameters.
*
@@ -227,7 +256,7 @@ function createAsyncView(params) {
* @param {boolean} [params.withSearchBar=true] if set to false, no default
* search bar will be rendered
*
- * @returns {Deferred<ControlPanel>} resolves with an instance of the ControlPanelController
+ * @returns {Promise<ControlPanel>} resolves with an instance of the ControlPanelController
*/
function createControlPanel(params) {
params = params || {};
@@ -270,6 +299,7 @@ function createControlPanel(params) {
// children when it will be called the second time (by its parent)
delete controlPanel.destroy;
widget.destroy();
+ $webClient.remove();
};
// render the controlPanel in a fragment as it must be able to render
@@ -290,16 +320,16 @@ function createControlPanel(params) {
*
* @param {Object} [params={}]
*/
-function createDebugManager (params) {
+var createDebugManager = function (params) {
params = params || {};
var mockRPC = params.mockRPC;
_.extend(params, {
mockRPC: function (route, args) {
if (args.method === 'check_access_rights') {
- return $.when(true);
+ return Promise.resolve(true);
}
if (args.method === 'xmlid_to_res_id') {
- return $.when(true);
+ return Promise.resolve(true);
}
if (mockRPC) {
return mockRPC.apply(this, arguments);
@@ -309,7 +339,7 @@ function createDebugManager (params) {
session: {
user_has_group: function (group) {
if (group === 'base.group_no_one') {
- return $.when(true);
+ return Promise.resolve(true);
}
return this._super.apply(this, arguments);
},
@@ -318,7 +348,7 @@ function createDebugManager (params) {
var debugManager = new DebugManager();
testUtilsMock.addMockEnvironment(debugManager, params);
return debugManager;
-}
+};
/**
* create a model from given parameters.
@@ -360,32 +390,10 @@ function createParent(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,
+ createView: createView,
+ createCalendarView: createCalendarView,
createControlPanel: createControlPanel,
createDebugManager: createDebugManager,
createModel: createModel,
diff --git a/addons/web/static/tests/helpers/test_utils_dom.js b/addons/web/static/tests/helpers/test_utils_dom.js
index dcf9eab6e15..315a00cbfe7 100644
--- a/addons/web/static/tests/helpers/test_utils_dom.js
+++ b/addons/web/static/tests/helpers/test_utils_dom.js
@@ -1,6 +1,8 @@
-odoo.define('web.test_utils_dom', function () {
+odoo.define('web.test_utils_dom', function (require) {
"use strict";
+var concurrency = require('web.concurrency');
+
/**
* DOM Test Utils
*
@@ -101,6 +103,7 @@ function dragAndDrop($el, $to, options) {
$el.trigger($.Event("mouseup"));
});
}
+ return concurrency.delay(0);
}
/**
@@ -114,7 +117,8 @@ function dragAndDrop($el, $to, options) {
function triggerMouseEvent($el, type) {
var pos = $el.offset();
var e = new $.Event(type);
- e.pageX = e.layerX = e.screenX = pos.left;
+ // little fix since it seems on chrome, it triggers 1px too on the left
+ e.pageX = e.layerX = e.screenX = pos.left + 1;
e.pageY = e.layerY = e.screenY = pos.top;
e.which = 1;
$el.trigger(e);
@@ -154,6 +158,8 @@ function triggerKeypressEvent(char) {
var keycode;
if (char === "Enter") {
keycode = $.ui.keyCode.ENTER;
+ } else if (char === "Tab") {
+ keycode = $.ui.keyCode.TAB;
} else {
keycode = char.charCodeAt(0);
}
@@ -177,37 +183,47 @@ function openDatepicker($datepickerEl) {
* @param {Object} [options]
* @param {boolean} [options.first=false] if true, clicks on the first element
* @param {boolean} [options.last=false] if true, clicks on the last element
+ * @param {boolean} [options.allowInvisible=false] if true, clicks on the
+ * element event if it is invisible
+ * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
+ * click event without calling the function click of jquery
*/
function click(el, options) {
options = options || {};
var matches = typeof el === 'string' ? $(el) : el;
- var selectorMsg = el.selector ? `(selector: ${el.selector})` : '';
+ var selectorMsg = typeof el === 'string' ? `(selector: ${el})` : '';
if (!matches.filter) { // it might be an array of dom elements
matches = $(matches);
}
+ var validMatches = options.allowInvisible ? matches : matches.filter(':visible');
- var visibleMatches = matches.filter(':visible');
if (options.first) {
- if (visibleMatches.length === 1) {
+ if (validMatches.length === 1) {
throw new Error(`There should be more than one visible target ${selectorMsg}. If` +
' you are sure that there is exactly one target, please use the ' +
'click function instead of the clickFirst function');
}
- visibleMatches = visibleMatches.first();
+ validMatches = validMatches.first();
} else if (options.last) {
- if (visibleMatches.length === 1) {
+ if (validMatches.length === 1) {
throw new Error(`There should be more than one visible target ${selectorMsg}. If` +
' you are sure that there is exactly one target, please use the ' +
'click function instead of the clickLast function');
}
- visibleMatches = visibleMatches.last();
+ validMatches = validMatches.last();
}
- if (visibleMatches.length === 0 && matches.length > 0) {
+ if (validMatches.length === 0 && matches.length > 0) {
throw new Error(`Element to click on is not visible ${selectorMsg}`);
- } else if (visibleMatches.length !== 1) {
- throw new Error(`Found ${visibleMatches.length} elements to click on, instead of 1 ${selectorMsg}`);
+ } else if (validMatches.length !== 1) {
+ throw new Error(`Found ${validMatches.length} elements to click on, instead of 1 ${selectorMsg}`);
+ }
+ if (options.shouldTriggerClick) {
+ validMatches.trigger('click');
+ } else {
+ validMatches.click();
}
- visibleMatches.click();
+
+ return concurrency.delay(0);
}
/**
@@ -216,9 +232,13 @@ function click(el, options) {
* use the click helper instead.
*
* @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector)
+ * @param {boolean} [options.allowInvisible=false] if true, clicks on the
+ * element event if it is invisible
+ * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
+ * click event without calling the function click of jquery
*/
-function clickFirst(el) {
- click(el, {first: true});
+function clickFirst(el, options) {
+ return click(el, _.extend({}, options, {first: true}));
}
/**
@@ -227,11 +247,36 @@ function clickFirst(el) {
* use the click helper instead.
*
* @param {string|NodeList|jQuery} el
+ * @param {boolean} [options.allowInvisible=false] if true, clicks on the
+ * element event if it is invisible
+ * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
+ * click event without calling the function click of jquery
+ */
+function clickLast(el, options) {
+ return click(el, _.extend({}, options, {last: true}));
+}
+
+
+/**
+ * trigger events on the specified target
+ * @param {jQuery} $el should target a single dom node
+ * @param {string[]} events the events you want to trigger
+ * @returns Promise
*/
-function clickLast(el) {
- click(el, {last: true});
+function triggerEvents($el, events) {
+ if ($el.length !== 1) {
+ throw new Error(`target has length ${$el.length} instead of 1`);
+ }
+ if (typeof events === 'string') {
+ events = [events];
+ }
+ events.forEach(function (event) {
+ $el.trigger(event);
+ });
+ return concurrency.delay(0);
}
+
return {
triggerKeypressEvent: triggerKeypressEvent,
triggerMouseEvent: triggerMouseEvent,
@@ -241,6 +286,7 @@ return {
click: click,
clickFirst: clickFirst,
clickLast: clickLast,
+ triggerEvents: triggerEvents,
};
});
diff --git a/addons/web/static/tests/helpers/test_utils_fields.js b/addons/web/static/tests/helpers/test_utils_fields.js
index f0f29839a9d..359f5f56b08 100644
--- a/addons/web/static/tests/helpers/test_utils_fields.js
+++ b/addons/web/static/tests/helpers/test_utils_fields.js
@@ -11,6 +11,7 @@ odoo.define('web.test_utils_fields', function (require) {
*/
var concurrency = require('web.concurrency');
+var domUtils = require('web.test_utils_dom');
//------------------------------------------------------------------------------
// Public functions
@@ -32,9 +33,7 @@ function editAndTrigger($el, value, events) {
throw new Error(`target ${$el.selector} has length ${$el.length} instead of 1`);
}
$el.val(value);
- events.forEach(function (event) {
- $el.trigger(event);
- });
+ return domUtils.triggerEvents($el, events);
}
/**
@@ -49,7 +48,7 @@ function editAndTrigger($el, value, events) {
* @param {string|number} value
*/
function editInput($el, value) {
- editAndTrigger($el, value, ['input']);
+ return editAndTrigger($el, value, ['input']);
}
/**
@@ -65,6 +64,7 @@ function editInput($el, value) {
*/
function editSelect($el, value) {
editAndTrigger($el, value, ['change']);
+ return concurrency.delay(0);
}
/**
@@ -75,14 +75,16 @@ function editSelect($el, value) {
* input
* @returns {HTMLInputElement} the main many2one input
*/
-function clickOpenM2ODropdown(fieldName, selector) {
+async function clickOpenM2ODropdown(fieldName, selector) {
var m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`;
var matches = document.querySelectorAll(m2oSelector);
if (matches.length !== 1) {
throw new Error(`cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`);
}
matches[0].click();
- return matches[0];
+ return await concurrency.delay(0).then(function () {
+ return matches[0];
+ });
}
/**
@@ -97,6 +99,7 @@ function clickM2OHighlightedItem(fieldName, selector) {
var $dropdown = $(m2oSelector).autocomplete('widget');
// clicking on an li (no matter which one), will select the focussed one
$dropdown.find('li:first()').click();
+ return concurrency.delay(0);
}
/**
@@ -117,7 +120,10 @@ function clickM2OItem(fieldName, searchText) {
if ($target.length !== 1 || !$target.is(':visible')) {
throw new Error('Menu item should be unique and visible');
}
- $target.mouseenter().click();
+ $target.mouseenter();
+ $target.click();
+
+ return concurrency.delay(0);
}
/**
@@ -140,30 +146,69 @@ function clickM2OItem(fieldName, searchText) {
function searchAndClickM2OItem(fieldName, options) {
options = options || {};
- var input = clickOpenM2ODropdown(fieldName, options.selector);
-
- var def;
- if (options.search) {
- // jquery autocomplete refines the search in a setTimeout() parameterized
- // with a delay, so we force this delay to 0 s.t. the dropdown is filtered
- // directly on the next tick
- var $input = $(input);
- var delay = $input.autocomplete('option', 'delay');
- $input.autocomplete('option', 'delay', 0);
- input.value = options.search;
- input.dispatchEvent(new Event('input'));
- $input.autocomplete('option', 'delay', delay);
- def = concurrency.delay(0);
- }
+ return clickOpenM2ODropdown(fieldName, options.selector).then(function (input) {
+ var def;
+ if (options.search) {
+ input.value = options.search;
+ input.dispatchEvent(new Event('input'));
+ def = concurrency.delay(0);
+ }
+ return Promise.resolve(def).then(function () {
+ if (options.item) {
+ return clickM2OItem(fieldName, options.item);
+ } else {
+ return clickM2OHighlightedItem(fieldName, options.selector);
+ }
+ });
+ });
+}
- return $.when(def).then(function () {
- if (options.item) {
- clickM2OItem(fieldName, options.item);
+/**
+ * Helper to trigger a key event on an element.
+ *
+ * @param {string} type type of key event ('press', 'up' or 'down')
+ * @param {jQuery} $el
+ * @param {number|string} keyCode used as number, but if string, it'll check if
+ * the string corresponds to a key -otherwise it will keep only the first
+ * char to get a letter key- and convert it into a keyCode.
+ * @returns {Promise}
+ */
+function triggerKey(type, $el, keyCode) {
+ type = 'key' + type;
+ if (typeof keyCode === 'string') {
+ if (keyCode.length > 1) {
+ keyCode = keyCode.toUpperCase();
+ keyCode = $.ui.keyCode[keyCode];
} else {
- clickM2OHighlightedItem(fieldName, options.selector);
+ keyCode = keyCode.charCodeAt(0);
}
- });
+ }
+ $el.trigger({type: type, which: keyCode, keyCode: keyCode});
+ return concurrency.delay(0);
}
+
+/**
+ * Helper to trigger a keydown event on an element.
+ *
+ * @param {jQuery} $el
+ * @param {number|string} keyCode @see triggerKey
+ * @returns {Promise}
+ */
+function triggerKeydown($el, keyCode) {
+ return triggerKey('down', $el, keyCode);
+}
+
+/**
+ * Helper to trigger a keyup event on an element.
+ *
+ * @param {jQuery} $el
+ * @param {number|string} keyCode @see triggerKey
+ * @returns {Promise}
+ */
+function triggerKeyup($el, keyCode) {
+ return triggerKey('up', $el, keyCode);
+}
+
return {
clickOpenM2ODropdown: clickOpenM2ODropdown,
clickM2OHighlightedItem: clickM2OHighlightedItem,
@@ -172,6 +217,9 @@ return {
editInput: editInput,
editSelect: editSelect,
searchAndClickM2OItem: searchAndClickM2OItem,
+ triggerKey: triggerKey,
+ triggerKeydown: triggerKeydown,
+ triggerKeyup: triggerKeyup,
};
});
diff --git a/addons/web/static/tests/helpers/test_utils_file.js b/addons/web/static/tests/helpers/test_utils_file.js
index 64e044e3180..e5b68a28e0b 100644
--- a/addons/web/static/tests/helpers/test_utils_file.js
+++ b/addons/web/static/tests/helpers/test_utils_file.js
@@ -47,29 +47,30 @@ function _createFakeDataTransfer(file) {
* @param {string} data.name
* @param {string} data.content
* @param {string} data.contentType
- * @returns {$.Promise<Object>} resolved with file created
+ * @returns {Promise<Object>} resolved with file created
*/
function createFile(data) {
- var def = $.Deferred();
- var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
- if (!requestFileSystem) {
- throw new Error('FileSystem API is not supported');
- }
- requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fileSystem) {
- fileSystem.root.getFile(data.name, { create: true }, function (fileEntry) {
- fileEntry.createWriter(function (fileWriter) {
- fileWriter.onwriteend = function (e) {
- fileSystem.root.getFile(data.name, {}, function (fileEntry) {
- fileEntry.file(function (file) {
- def.resolve(file);
+ // Note: this is only supported by Chrome, and does not work in Incognito mode
+ return new Promise(function (resolve, reject) {
+ var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
+ if (!requestFileSystem) {
+ throw new Error('FileSystem API is not supported');
+ }
+ requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fileSystem) {
+ fileSystem.root.getFile(data.name, { create: true }, function (fileEntry) {
+ fileEntry.createWriter(function (fileWriter) {
+ fileWriter.onwriteend = function (e) {
+ fileSystem.root.getFile(data.name, {}, function (fileEntry) {
+ fileEntry.file(function (file) {
+ resolve(file);
+ });
});
- });
- };
- fileWriter.write(new Blob([ data.content ], { type: data.contentType }));
+ };
+ fileWriter.write(new Blob([ data.content ], { type: data.contentType }));
+ });
});
});
});
- return def;
}
/**
diff --git a/addons/web/static/tests/helpers/test_utils_form.js b/addons/web/static/tests/helpers/test_utils_form.js
index 379350e1ca6..b4bff154ce7 100644
--- a/addons/web/static/tests/helpers/test_utils_form.js
+++ b/addons/web/static/tests/helpers/test_utils_form.js
@@ -20,7 +20,7 @@ var testUtilsDom = require('web.test_utils_dom');
* @param {FormController} form
*/
function clickEdit(form) {
- testUtilsDom.click(form.$buttons.find('.o_form_button_edit'));
+ return testUtilsDom.click(form.$buttons.find('.o_form_button_edit'));
}
/**
@@ -30,7 +30,7 @@ function clickEdit(form) {
* @param {FormController} form
*/
function clickSave(form) {
- testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
+ return testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
}
/**
@@ -40,7 +40,7 @@ function clickSave(form) {
* @param {FormController} form
*/
function clickCreate(form) {
- testUtilsDom.click(form.$buttons.find('.o_form_button_create'));
+ return testUtilsDom.click(form.$buttons.find('.o_form_button_create'));
}
/**
@@ -50,7 +50,7 @@ function clickCreate(form) {
* @param {FormController} form
*/
function clickDiscard(form) {
- testUtilsDom.click(form.$buttons.find('.o_form_button_cancel'));
+ return testUtilsDom.click(form.$buttons.find('.o_form_button_cancel'));
}
/**
@@ -60,7 +60,7 @@ function clickDiscard(form) {
* @param {[Object]} params given to the controller reload method
*/
function reload(form, params) {
- form.reload(params);
+ return form.reload(params);
}
return {
diff --git a/addons/web/static/tests/helpers/test_utils_graph.js b/addons/web/static/tests/helpers/test_utils_graph.js
index 8867f82c349..f6b50a1b571 100644
--- a/addons/web/static/tests/helpers/test_utils_graph.js
+++ b/addons/web/static/tests/helpers/test_utils_graph.js
@@ -18,7 +18,7 @@ odoo.define('web.test_utils_graph', function () {
* @param {[Object]} params given to the controller reload method
*/
function reload(graph, params) {
- graph.reload(params);
+ return graph.reload(params);
}
return {
diff --git a/addons/web/static/tests/helpers/test_utils_kanban.js b/addons/web/static/tests/helpers/test_utils_kanban.js
index 166ae2a2572..76af632b417 100644
--- a/addons/web/static/tests/helpers/test_utils_kanban.js
+++ b/addons/web/static/tests/helpers/test_utils_kanban.js
@@ -18,22 +18,24 @@ var testUtilsFields = require('web.test_utils_fields');
* the Create button is visible.
*
* @param {KanbanController} kanban
+ * @returns {Promise}
*/
function clickCreate(kanban) {
- testUtilsDom.click(kanban.$buttons.find('.o-kanban-button-new'));
+ return testUtilsDom.click(kanban.$buttons.find('.o-kanban-button-new'));
}
/**
* Open the settings menu for a column (in a grouped kanban view)
*
* @param {jQuery} $column
+ * @returns {Promise}
*/
function toggleGroupSettings($column) {
var $dropdownToggler = $column.find('.o_kanban_config > a.dropdown-toggle');
if (!$dropdownToggler.is(':visible')) {
$dropdownToggler.css('display', 'block');
}
- testUtilsDom.click($dropdownToggler);
+ return testUtilsDom.click($dropdownToggler);
}
/**
@@ -43,6 +45,7 @@ function toggleGroupSettings($column) {
* @param {kanbanController} kanban
* @param {string|number} value
* @param {[string]} fieldName
+ * @returns {Promise}
*/
function quickCreate(kanban, value, fieldName) {
var additionalSelector = fieldName ? ('[name=' + fieldName + ']'): '';
@@ -53,7 +56,7 @@ function quickCreate(kanban, value, fieldName) {
keyCode: $.ui.keyCode.ENTER,
}
);
- testUtilsFields.editAndTrigger(
+ return testUtilsFields.editAndTrigger(
kanban.$('.o_kanban_quick_create input' + additionalSelector),
value,
['input', enterEvent]
@@ -65,9 +68,10 @@ function quickCreate(kanban, value, fieldName) {
*
* @param {KanbanController} kanban
* @param {[Object]} params given to the controller reload method
+ * @returns {Promise}
*/
function reload(kanban, params) {
- kanban.reload(params);
+ return kanban.reload(params);
}
/**
@@ -76,13 +80,14 @@ function reload(kanban, params) {
* does not comply with the usual dom structure.
*
* @param {jQuery} $record
+ * @returns {Promise}
*/
function toggleRecordDropdown($record) {
var $dropdownToggler = $record.find('.o_dropdown_kanban > a.dropdown-toggle');
if (!$dropdownToggler.is(':visible')) {
$dropdownToggler.css('display', 'block');
}
- testUtilsDom.click($dropdownToggler);
+ return testUtilsDom.click($dropdownToggler);
}
diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js
index 2ecc655db7e..ccb4d20959e 100644
--- a/addons/web/static/tests/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/helpers/test_utils_mock.js
@@ -124,7 +124,7 @@ function removeSrcAttribute(el, widget) {
* is completely removed by default.
*
* @returns {MockServer} the instance of the mock server, created by this
- * function. It is necessary for createAsyncView so that method can call some
+ * function. It is necessary for createView so that method can call some
* other methods on it.
*/
function addMockEnvironment(widget, params) {
@@ -199,10 +199,11 @@ function addMockEnvironment(widget, params) {
// widget is destroyed, at the end of each test to avoid collisions
core.bus.trigger('clear_cache');
- _(services).chain()
- .compact() // services can be defined but null (e.g. ajax)
- .reject(function (s) { return s.isDestroyed(); })
- .invoke('destroy');
+ Object.keys(services).forEach(function(s) {
+ var service = services[s];
+ if (service && !service.isDestroyed())
+ service.destroy();
+ });
DebouncedField.prototype.DEBOUNCE = initialDebounceValue;
dom.DEBOUNCE = initialDOMDebounceValue;
@@ -543,6 +544,25 @@ function unpatch(target) {
delete target.__patchID;
}
+window.originalSetTimeout = window.setTimeout;
+function patchSetTimeout() {
+ var original = window.setTimeout;
+ var self = this;
+ window.setTimeout = function (handler, delay) {
+ console.log("calling setTimeout on " + (handler.name || "some function") + "with delay of " + delay);
+ console.trace();
+ var handlerArguments = Array.prototype.slice.call(arguments, 1);
+ return original(function () {
+ handler.bind(self, handlerArguments)();
+ console.log('after doing the action of the setTimeout');
+ }, delay);
+ };
+
+ return function () {
+ window.setTimeout = original;
+ };
+}
+
return {
addMockEnvironment: addMockEnvironment,
@@ -551,6 +571,7 @@ return {
patchDate: patchDate,
patch: patch,
unpatch: unpatch,
+ patchSetTimeout: patchSetTimeout,
};
});
diff --git a/addons/web/static/tests/helpers/test_utils_modal.js b/addons/web/static/tests/helpers/test_utils_modal.js
index d2162aebfa0..5587eecfc01 100644
--- a/addons/web/static/tests/helpers/test_utils_modal.js
+++ b/addons/web/static/tests/helpers/test_utils_modal.js
@@ -11,6 +11,7 @@ odoo.define('web.test_utils_modal', function (require) {
*/
var core = require('web.core');
+var concurrency = require('web.concurrency');
/**
* Click on a button in the footer of a modal (which contains a given string).
@@ -25,6 +26,7 @@ function clickButton(text) {
throw new Error(`Found ${$button.length} button(s) containing '${text}'`);
}
$button.click();
+ return concurrency.delay(0);
}
return {
diff --git a/addons/web/static/tests/helpers/test_utils_pivot.js b/addons/web/static/tests/helpers/test_utils_pivot.js
index 18a5a7307f3..fe059b533ed 100644
--- a/addons/web/static/tests/helpers/test_utils_pivot.js
+++ b/addons/web/static/tests/helpers/test_utils_pivot.js
@@ -24,7 +24,7 @@ var testUtilsDom = require('web.test_utils_dom');
* @param {string} measure
*/
function clickMeasure(pivot, measure) {
- testUtilsDom.click(pivot.$buttons.find(`.dropdown-item[data-field=${measure}]`));
+ return testUtilsDom.click(pivot.$buttons.find(`.dropdown-item[data-field=${measure}]`));
}
/**
@@ -35,7 +35,7 @@ function clickMeasure(pivot, measure) {
* @param {PivotController} pivot
*/
function toggleMeasuresDropdown(pivot) {
- testUtilsDom.click(pivot.$buttons.filter('.btn-group:first').find('> button'));
+ return testUtilsDom.click(pivot.$buttons.filter('.btn-group:first').find('> button'));
}
/**
@@ -45,7 +45,7 @@ function toggleMeasuresDropdown(pivot) {
* @param {[Object]} params given to the controller reload method
*/
function reload(pivot, params) {
- pivot.reload(params);
+ return pivot.reload(params);
}
return {