diff --git a/addons/mail/static/tests/activity_tests.js b/addons/mail/static/tests/activity_tests.js
index 6ab924973ec..6725aab2e33 100644
--- a/addons/mail/static/tests/activity_tests.js
+++ b/addons/mail/static/tests/activity_tests.js
@@ -179,7 +179,7 @@ QUnit.test('activity view: simple activity rendering', async function (assert) {
assert.containsN(activity, td, 2, 'should contain an empty cell as no activity scheduled yet.');
// schedule an activity (this triggers a do_action)
- testUtils.fields.editAndTrigger(activity.$(td + ':first'), null, ['mouseenter', 'click']);
+ await testUtils.fields.editAndTrigger(activity.$(td + ':first'), null, ['mouseenter', 'click']);
assert.containsOnce(activity, 'table tfoot tr .o_record_selector',
'should contain search more selector to choose the record to schedule an activity for it');
diff --git a/addons/mail/static/tests/discuss_typing_notification_tests.js b/addons/mail/static/tests/discuss_typing_notification_tests.js
index d77d32b9f1f..4d434b80afd 100644
--- a/addons/mail/static/tests/discuss_typing_notification_tests.js
+++ b/addons/mail/static/tests/discuss_typing_notification_tests.js
@@ -205,13 +205,13 @@ QUnit.test('notify is typing', async function (assert) {
// STEP 1: current user is typing something
step = 1;
$input.focus();
- testUtils.fields.editInput($input, '1');
+ await testUtils.fields.editInput($input, '1');
assert.verifySteps(['notify_typing']);
// STEP 2: current user clears input
step = 2;
- testUtils.fields.editInput($input, '');
+ await testUtils.fields.editInput($input, '');
assert.verifySteps(['notify_typing']);
diff --git a/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js
index c605c9bc33c..60fa4679f03 100644
--- a/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js
+++ b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js
@@ -360,11 +360,11 @@ odoo.define('partner_autocomplete.tests', function (require) {
'' +
'' +
'',
- }).then(function (form) {
+ }).then(async function (form) {
var $input = form.$('.o_field_many2one[name="parent_id"] input:visible');
assert.strictEqual($input.length, 1, "there should be an for the Many2one");
- testUtils.fields.editInput($input, 'odoo');
+ await testUtils.fields.editInput($input, 'odoo');
concurrency.delay(0).then(function () {
var $dropdown = $input.autocomplete('widget');
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index a67d4e19cbc..112a1c74b7b 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -2967,17 +2967,17 @@ QUnit.module('basic_fields', {
// switch to edit mode
await testUtils.form.clickEdit(form);
// open datepicker and select another value
- testUtils.dom.openDatepicker(form.$('.o_datepicker'));
- testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
- testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
- testUtils.dom.click($('.bootstrap-datetimepicker-widget .year').eq(11));
- testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(11));
- testUtils.dom.click($('.day:contains(31)'));
+ await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year').eq(11));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(11));
+ await testUtils.dom.click($('.day:contains(31)'));
var $warn = form.$('.o_datepicker_warning:visible');
assert.strictEqual($warn.length, 1, "should have a warning in the form view");
- testUtils.fields.editSelect(form.$('.o_field_widget[name=date] input'), ''); // remove the value
+ await testUtils.fields.editSelect(form.$('.o_field_widget[name=date] input'), ''); // remove the value
$warn = form.$('.o_datepicker_warning:visible');
assert.strictEqual($warn.length, 0, "the warning in the form view should be hidden");
@@ -3100,7 +3100,7 @@ QUnit.module('basic_fields', {
assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017',
'the date should be correct in edit mode');
- testUtils.fields.editAndTrigger(form.$('.o_datepicker_input'), '', ['input', 'change', 'focusout']);
+ await testUtils.fields.editAndTrigger(form.$('.o_datepicker_input'), '', ['input', 'change', 'focusout']);
assert.strictEqual(form.$('.o_datepicker_input').val(), '',
'should have correctly removed the value');
diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
index eb4cd9986cc..d9bb4a9624b 100644
--- a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
@@ -6611,7 +6611,7 @@ QUnit.module('fields', {}, function () {
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
prom = testUtils.makeTestPromise();
- testUtils.fields.editAndTrigger(form.$('.o_field_widget[name=int_field]'), '44',
+ await testUtils.fields.editAndTrigger(form.$('.o_field_widget[name=int_field]'), '44',
['input', { type: 'keydown', which: $.ui.keyCode.TAB }]);
prom.resolve();
await testUtils.nextTick();
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 90a581e7b33..24ebb0db105 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -135,6 +135,7 @@ return Promise.all([
clickFirst: testUtilsDom.clickFirst,
clickLast: testUtilsDom.clickLast,
triggerEvents: testUtilsDom.triggerEvents,
+ triggerEvent: testUtilsDom.triggerEvent,
},
form: {
clickEdit: testUtilsForm.clickEdit,
diff --git a/addons/web/static/tests/helpers/test_utils_dom.js b/addons/web/static/tests/helpers/test_utils_dom.js
index 4c1ef8fbbcc..87013bd4696 100644
--- a/addons/web/static/tests/helpers/test_utils_dom.js
+++ b/addons/web/static/tests/helpers/test_utils_dom.js
@@ -23,8 +23,8 @@ var concurrency = require('web.concurrency');
* merged. This is not the default as of now, because handlers are triggered
* synchronously, which is not the same as the 'reality'.
*
- * @param {jqueryElement} $el
- * @param {jqueryElement} $to
+ * @param {jqueryElement|HTMLElement} $el
+ * @param {jqueryElement|HTMLElement} $to
* @param {Object} [options]
* @param {string|Object} [options.position='center'] target position:
* can either be one of {'top', 'bottom', 'left', 'right'} or
@@ -36,8 +36,21 @@ var concurrency = require('web.concurrency');
* @param {boolean} [options.withTrailingClick=false] if true, this utility
* function will also trigger a click on the target after the mouseup event
* (this is actually what happens when a drag and drop operation is done)
+ * @param {jQuery|HTMLElement} [options.mouseenterTarget=undefined] target of the mouseenter event
+ * @param {jQuery|HTMLElement} [options.mousedownTarget=undefined] target of the mousedown event
+ * @param {jQuery|HTMLElement} [options.mousemoveTarget=undefined] target of the mousemove event
+ * @param {jQuery|HTMLElement} [options.mouseupTarget=undefined] target of the mouseup event
+ * @returns {Promise}
*/
function dragAndDrop($el, $to, options) {
+ let el = null;
+ if ($el instanceof EventTarget) {
+ el = $el;
+ $el = $(el);
+ }
+ if ($to instanceof EventTarget) {
+ $to = $($to);
+ }
options = options || {};
var position = options.position || 'center';
var elementCenter = $el.offset();
@@ -67,40 +80,40 @@ function dragAndDrop($el, $to, options) {
toOffset.left += bound.left;
toOffset.top += bound.top;
}
- $el.trigger($.Event("mouseenter"));
+ triggerEvent(options.mouseenterTarget || el || $el, 'mouseenter');
if (!(options.continueMove)) {
elementCenter.left += $el.outerWidth() / 2;
elementCenter.top += $el.outerHeight() / 2;
- $el.trigger($.Event("mousedown", {
+ triggerEvent(options.mousedownTarget || el || $el, 'mousedown', {
which: 1,
pageX: elementCenter.left,
pageY: elementCenter.top
- }));
+ });
}
- $el.trigger($.Event("mousemove", {
+ triggerEvent(options.mousemoveTarget || el || $el, 'mousemove', {
which: 1,
pageX: toOffset.left,
pageY: toOffset.top
- }));
+ });
if (!options.disableDrop) {
- $el.trigger($.Event("mouseup", {
+ triggerEvent(options.mouseupTarget || el || $el, 'mouseup', {
which: 1,
pageX: toOffset.left,
pageY: toOffset.top
- }));
+ });
if (options.withTrailingClick) {
- $el.click();
+ triggerEvent(options.mouseupTarget || el || $el, 'click');
}
} else {
// It's impossible to drag another element when one is already
// being dragged. So it's necessary to finish the drop when the test is
// over otherwise it's impossible for the next tests to drag and
// drop elements.
- $el.on("remove", function () {
- $el.trigger($.Event("mouseup"));
+ $el.on('remove', function () {
+ triggerEvent($el, 'mouseup');
});
}
return concurrency.delay(0);
@@ -111,17 +124,24 @@ function dragAndDrop($el, $to, options) {
* sometimes necessary because the basic way to trigger an event (such as
* $el.trigger('mousemove')); ) is too crude for some uses.
*
- * @param {jqueryElement} $el
+ * @param {jQuery|HTMLElement} $el
* @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
+ * @returns {Promise}
*/
function triggerMouseEvent($el, type) {
- var pos = $el.offset();
- var e = new $.Event(type);
+ const el = $el instanceof jQuery ? $el[0] : $el;
+ if (!el) {
+ throw new Error(`no target found to trigger MouseEvent`);
+ }
+ const rect = el.getBoundingClientRect();
// 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);
+ const left = rect.x + 1;
+ const top = rect.y;
+ return triggerEvent($el, type, {
+ which: 1,
+ pageX: left, layerX: left, screenX: left,
+ pageY: top, layerY: top, screenY: top,
+ });
}
/**
@@ -132,6 +152,7 @@ function triggerMouseEvent($el, type) {
* @param {integer} x
* @param {integer} y
* @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
+ * @returns {HTMLElement}
*/
function triggerPositionalMouseEvent(x, y, type) {
var ev = document.createEvent("MouseEvent");
@@ -153,6 +174,7 @@ function triggerPositionalMouseEvent(x, y, type) {
* simulate a keypress event for a given character
*
* @param {string} char the character, or 'ENTER'
+ * @returns {Promise}
*/
function triggerKeypressEvent(char) {
var keycode;
@@ -163,7 +185,10 @@ function triggerKeypressEvent(char) {
} else {
keycode = char.charCodeAt(0);
}
- return $('body').trigger($.Event('keypress', { which: keycode, keyCode: keycode }));
+ return triggerEvent(document.body, 'keypress', {
+ keyCode: keycode,
+ which: keycode,
+ });
}
/**
@@ -178,6 +203,7 @@ function triggerKeypressEvent(char) {
* element event if it is invisible
* @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
* click event without calling the function click of jquery
+ * @returns {Promise}
*/
function click(el, options) {
options = options || {};
@@ -209,7 +235,7 @@ function click(el, options) {
throw new Error(`Found ${validMatches.length} elements to click on, instead of 1 ${selectorMsg}`);
}
if (options.shouldTriggerClick) {
- validMatches.trigger('click');
+ return triggerEvent(validMatches, 'click');
} else {
validMatches.click();
}
@@ -227,6 +253,7 @@ function click(el, options) {
* element event if it is invisible
* @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
* click event without calling the function click of jquery
+ * @returns {Promise}
*/
function clickFirst(el, options) {
return click(el, _.extend({}, options, {first: true}));
@@ -242,28 +269,58 @@ function clickFirst(el, options) {
* element event if it is invisible
* @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
* click event without calling the function click of jquery
+ * @returns {Promise}
*/
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
+ * Trigger events on the specified target
+ * @param {jQuery|HTMLElement} $el should target a single dom node
* @param {string[]} events the events you want to trigger
- * @returns Promise
+ * @returns {Promise}
*/
function triggerEvents($el, events) {
- if ($el.length !== 1) {
- throw new Error(`target has length ${$el.length} instead of 1`);
+ if ($el instanceof jQuery) {
+ if ($el.length !== 1) {
+ throw new Error(`target has length ${$el.length} instead of 1`);
+ }
+ } else if (!($el instanceof EventTarget)) {
+ throw new Error(`target is neither a jQuery element nor an HTML element`);
}
if (typeof events === 'string') {
events = [events];
}
- events.forEach(function (event) {
+ return events.reduce((previous, event) => {
+ return previous.then(() => triggerEvent($el, event));
+ }, Promise.resolve());
+}
+
+/**
+ * Trigger an event on the specified target.
+ * This function will dispatch a native event to an HTMLElement or a
+ * jQuery event to a jQuery object.
+ * @param {jQuery|HTMLElement} $el event target.
+ * @param {string} type event type
+ * @param {Object} [options] event attributes
+ * @returns {Promise}
+ */
+function triggerEvent($el, type, options={}) {
+ if ($el instanceof EventTarget) {
+ const event = new Event(type);
+ Object.assign(event, options);
+ Object.defineProperty(event, 'target', {
+ writable: false,
+ value: $el,
+ });
+ $el.dispatchEvent(event);
+ } else {
+ const event = Object.keys(options).length ?
+ $.Event(type, options) :
+ type;
$el.trigger(event);
- });
+ }
return concurrency.delay(0);
}
@@ -274,7 +331,7 @@ function triggerEvents($el, events) {
* @param {jQuery} $datepickerEl element to which a datepicker is attached
*/
function openDatepicker($datepickerEl) {
- click($datepickerEl.find('.o_datepicker_input'));
+ return click($datepickerEl.find('.o_datepicker_input'));
}
@@ -288,6 +345,7 @@ return {
clickFirst: clickFirst,
clickLast: clickLast,
triggerEvents: triggerEvents,
+ triggerEvent: triggerEvent,
};
});
diff --git a/addons/web/static/tests/helpers/test_utils_fields.js b/addons/web/static/tests/helpers/test_utils_fields.js
index 359f5f56b08..dcc9329bcbe 100644
--- a/addons/web/static/tests/helpers/test_utils_fields.js
+++ b/addons/web/static/tests/helpers/test_utils_fields.js
@@ -27,6 +27,7 @@ var domUtils = require('web.test_utils_dom');
* @param {jQuery} $el should target an input, textarea or select
* @param {string|number} value
* @param {string[]} events
+ * @returns {Promise}
*/
function editAndTrigger($el, value, events) {
if ($el.length !== 1) {
@@ -46,6 +47,7 @@ function editAndTrigger($el, value, events) {
*
* @param {jQuery} $el
* @param {string|number} value
+ * @returns {Promise}
*/
function editInput($el, value) {
return editAndTrigger($el, value, ['input']);
@@ -61,10 +63,10 @@ function editInput($el, value) {
*
* @param {jQuery} $el
* @param {string|number} value
+ * @returns {Promise}
*/
function editSelect($el, value) {
- editAndTrigger($el, value, ['change']);
- return concurrency.delay(0);
+ return editAndTrigger($el, value, ['change']);
}
/**
@@ -112,6 +114,7 @@ function clickM2OHighlightedItem(fieldName, selector) {
*
* @param {string} fieldName
* @param {string} searchText
+ * @returns {Promise}
*/
function clickM2OItem(fieldName, searchText) {
var m2oSelector = `.o_field_many2one[name=${fieldName}] input`;
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index d3ff979de8b..9e6b8ddd11d 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -1036,7 +1036,7 @@ QUnit.module('Views', {
keyCode: $.ui.keyCode.ENTER,
which: $.ui.keyCode.ENTER,
};
- testUtils.fields.editAndTrigger(
+ await testUtils.fields.editAndTrigger(
kanban.$('.o_kanban_quick_create').find('input[name=foo]'),
'new partner',
['input', $.Event('keydown', enterEvent), $.Event('keydown', enterEvent)]
@@ -1189,7 +1189,7 @@ QUnit.module('Views', {
which: $.ui.keyCode.ENTER,
};
- testUtils.fields.editAndTrigger(
+ await testUtils.fields.editAndTrigger(
kanban.$('.o_kanban_quick_create').find('input[name=foo]'),
'new partner',
['input', $.Event('keydown', enterEvent), $.Event('keydown', enterEvent)]
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 05791ad9d74..b35b7c64362 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -969,7 +969,7 @@ QUnit.module('Views', {
n += 1;
});
await testUtils.dom.click($td);
- testUtils.fields.editInput($td.find('input'), 'abc');
+ await testUtils.fields.editInput($td.find('input'), 'abc');
assert.strictEqual(n, 1, "field_changed should have been triggered");
await testUtils.dom.click(list.$('td:not(.o_list_record_selector)').eq(2));
assert.strictEqual(n, 1, "field_changed should not have been triggered");