[REF] web,*: native event dispatching in test utils

*: mail,partner_autocomplete: adapted tests

Added a new function to trigger the adequate type of event on
an element (native JS event to DOM Nodes and jQuery event to jQuery objects),
and refactored the generic event-triggering functions to call this new function.
This commit is contained in:
Julien Mougenot
2019-08-26 07:36:28 +00:00
parent 496fc0882d
commit 91ea5a2ca4
10 changed files with 111 additions and 49 deletions
+1 -1
View File
@@ -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');
@@ -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']);
@@ -360,11 +360,11 @@ odoo.define('partner_autocomplete.tests', function (require) {
'<field name="name"/>' +
'<field name="parent_id" widget="res_partner_many2one"/>' +
'</form>',
}).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 <input/> for the Many2one");
testUtils.fields.editInput($input, 'odoo');
await testUtils.fields.editInput($input, 'odoo');
concurrency.delay(0).then(function () {
var $dropdown = $input.autocomplete('widget');
@@ -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');
@@ -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();
@@ -135,6 +135,7 @@ return Promise.all([
clickFirst: testUtilsDom.clickFirst,
clickLast: testUtilsDom.clickLast,
triggerEvents: testUtilsDom.triggerEvents,
triggerEvent: testUtilsDom.triggerEvent,
},
form: {
clickEdit: testUtilsForm.clickEdit,
@@ -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,
};
});
@@ -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`;
@@ -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)]
+1 -1
View File
@@ -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");