summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Schippefilt <vsc@odoo.com>2019-12-04 10:44:32 +0000
committerVincent Schippefilt <vsc@odoo.com>2019-12-09 11:09:10 +0000
commitb0941d19a07b08fabc64a284b58e57edc46203dd (patch)
treea6b25026177d26a64c6c7f6005e27c41fca5c1b6
parent29726e8fd000cb09075f9b6b2a6a8d2ddaf4ef58 (diff)
[FIX] web,*: fix all tests and test utils to use native events
In this commit: - transformed test_utils helpers to always trigger native events - introduced new test_utils_create helper: prepareTarget - updated qunit asserts to support Owl Components - updated all misused helpers that would crash with the updated test_utils Co-authored-by: Julien Mougenot <jum@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com> Co-authored-by: Pierre Rousseau <pro@odoo.com>
-rw-r--r--addons/snailmail/static/tests/chatter_snailmail_tests.js16
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js2
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js6
-rw-r--r--addons/web/static/tests/fields/relational_fields/field_many2one_tests.js13
-rw-r--r--addons/web/static/tests/helpers/qunit_asserts.js104
-rw-r--r--addons/web/static/tests/helpers/test_utils.js14
-rw-r--r--addons/web/static/tests/helpers/test_utils_create.js61
-rw-r--r--addons/web/static/tests/helpers/test_utils_dom.js298
-rw-r--r--addons/web/static/tests/helpers/test_utils_fields.js35
-rw-r--r--addons/web/static/tests/helpers/test_utils_mock.js4
-rw-r--r--addons/web/static/tests/views/calendar_tests.js19
-rw-r--r--addons/web/static/tests/views/kanban_tests.js13
-rw-r--r--addons/web/static/tests/views/list_tests.js6
-rw-r--r--addons/web/static/tests/views/pivot_tests.js8
-rw-r--r--addons/web/static/tests/views/view_dialogs_tests.js5
-rw-r--r--addons/web_editor/static/tests/field_html_tests.js20
16 files changed, 373 insertions, 251 deletions
diff --git a/addons/snailmail/static/tests/chatter_snailmail_tests.js b/addons/snailmail/static/tests/chatter_snailmail_tests.js
index 4b403030a69..8fcf0ce587b 100644
--- a/addons/snailmail/static/tests/chatter_snailmail_tests.js
+++ b/addons/snailmail/static/tests/chatter_snailmail_tests.js
@@ -127,7 +127,7 @@ QUnit.test('Sent', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_sent'), 'mouseenter');
+ await testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_sent'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-check').length,
@@ -162,7 +162,7 @@ QUnit.test('Canceled', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_canceled'), 'mouseenter');
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_canceled'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-trash-o').length,
@@ -198,7 +198,7 @@ QUnit.test('Pending', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_pending'), 'mouseenter');
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_pending'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-clock-o').length,
@@ -241,7 +241,7 @@ QUnit.test('No Price Available', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_no_price_available'), 'mouseenter');
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_no_price_available'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
@@ -301,7 +301,7 @@ QUnit.test('Format Error', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_format_error'), 'mouseenter');
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_format_error'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
@@ -350,7 +350,7 @@ QUnit.test('Credit Error', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_credit_error'), 'mouseenter');
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_credit_error'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
@@ -418,7 +418,7 @@ QUnit.test('Trial Error', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_trial_error'), 'mouseenter');
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_trial_error'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
@@ -487,7 +487,7 @@ QUnit.test('Missing Required Fields', async function (assert) {
assert.containsNone(form, '.o_thread_tooltip_snailmail',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_missing_required_fields'), 'mouseenter');
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_missing_required_fields'), 'mouseover');
assert.ok($('.o_thread_tooltip_snailmail:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index c064f67dcef..5d8e5715261 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -329,7 +329,7 @@ var FormRenderer = BasicRenderer.extend({
_addOnClickAction: function ($el, node) {
if (node.attrs.special || node.attrs.confirm || node.attrs.type || $el.hasClass('oe_stat_button')) {
var self = this;
- $el.click(function () {
+ $el.on("click", function () {
self.trigger_up('button_clicked', {
attrs: node.attrs,
record: self.state,
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index 7c9e84239e3..d9d26ddfea1 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -2435,7 +2435,7 @@ QUnit.module('basic_fields', {
function waitForChangeTriggered() {
return def.then(function () {
def = testUtils.makeTestPromise();
- return concurrency.delay(0);
+ return testUtils.nextTick();
});
}
});
@@ -5988,7 +5988,7 @@ QUnit.module('basic_fields', {
// Focusing the field selector input should open the field selector
// popover
- await testUtils.dom.triggerEvents($fieldSelector, ['focusin']);
+ await testUtils.dom.triggerEvents($fieldSelector, 'focus');
var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
assert.ok($fieldSelectorPopover.is(":visible"),
"field selector popover should be visible");
@@ -6057,7 +6057,7 @@ QUnit.module('basic_fields', {
"there should be a field selector");
// Focusing its input should open the field selector popover
- await testUtils.dom.triggerEvents($fieldSelector, ['focusin']);
+ await testUtils.dom.triggerEvents($fieldSelector, 'focus');
var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
assert.ok($fieldSelectorPopover.is(":visible"),
"field selector popover should be visible");
diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
index 20ae37486f2..8c84cf4edf2 100644
--- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
@@ -1824,7 +1824,7 @@ QUnit.module('fields', {}, function () {
});
QUnit.test('list in form: call button in sub view', async function (assert) {
- assert.expect(6);
+ assert.expect(11);
this.data.partner.records[0].p = [2];
var form = await createView({
@@ -1855,6 +1855,7 @@ QUnit.module('fields', {}, function () {
'should call with correct currentID in env');
assert.deepEqual(event.data.env.resIDs, [37],
'should call with correct resIDs in env');
+ assert.step(event.data.action_data.name);
},
},
archs: {
@@ -1872,8 +1873,14 @@ QUnit.module('fields', {}, function () {
await testUtils.dom.click(form.$('td.o_data_cell:first'));
await testUtils.dom.click(form.$('.o_external_button'));
await testUtils.dom.click($('button:contains("Just do it !")'));
+ assert.verifySteps(['action']);
await testUtils.dom.click($('button:contains("Just don\'t do it !")'));
+ assert.verifySteps([]); // the second button is disabled, it can't be clicked
+ await testUtils.dom.click($('.modal .btn-secondary:contains(Discard)'));
+ await testUtils.dom.click(form.$('.o_external_button'));
+ await testUtils.dom.click($('button:contains("Just don\'t do it !")'));
+ assert.verifySteps(['object']);
form.destroy();
});
@@ -2149,9 +2156,9 @@ QUnit.module('fields', {}, function () {
},
});
- form.$('.o_field_many2one input').focus();
+ await testUtils.dom.triggerEvent(form.$('.o_field_many2one input'),'focus');
await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'),
- 'new partner', ['keyup', 'focusout']);
+ 'new partner', ['keyup', 'blur']);
await testUtils.dom.click($('.modal .modal-footer .btn-primary').first());
form.destroy();
diff --git a/addons/web/static/tests/helpers/qunit_asserts.js b/addons/web/static/tests/helpers/qunit_asserts.js
index e8f9948d347..bb4438eb4cc 100644
--- a/addons/web/static/tests/helpers/qunit_asserts.js
+++ b/addons/web/static/tests/helpers/qunit_asserts.js
@@ -23,25 +23,25 @@ var Widget = require('web.Widget');
* - has (or has not) a css class
*
* @private
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} className
* @param {boolean} shouldHaveClass
* @param {string} [msg]
*/
function _checkClass(w, className, shouldHaveClass, msg) {
- var $el = w instanceof Widget ? w.$el :
- w instanceof HTMLElement ? $(w) :
- w; // jquery element
-
- if ($el.length !== 1) {
- var descr = `${shouldHaveClass ? 'hasClass' : 'doesNotHaveClass'} ${className}`;
- QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
- } else {
- msg = msg || `target should ${shouldHaveClass ? '' : 'not'} have class ${className}`;
- var hasClass = $el.hasClass(className);
- var condition = shouldHaveClass ? hasClass : !hasClass;
- QUnit.assert.ok(condition, msg);
+ if (w instanceof jQuery && w.length !== 1) {
+ const assertion = shouldHaveClass ? 'hasClass' : 'doesNotHaveClass';
+ QUnit.assert.ok(false, `Assertion '${assertion} ${className}' targets ${w.length} elements instead of 1`);
}
+
+ const el = w instanceof Widget || w instanceof owl.Component ? w.el :
+ w instanceof jQuery ? w[0] : w;
+
+ msg = msg || `target should ${shouldHaveClass ? 'have' : 'not have'} class ${className}`;
+ const hasClass = className.split(" ").reduce((acc, cls) =>
+ acc && el.classList.contains(cls), true);
+ const condition = shouldHaveClass ? hasClass : !hasClass;
+ QUnit.assert.ok(condition, msg);
}
/**
@@ -50,31 +50,31 @@ function _checkClass(w, className, shouldHaveClass, msg) {
* - is (or not) visible
*
* @private
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {boolean} shouldBeVisible
* @param {string} [msg]
*/
function _checkVisible(w, shouldBeVisible, msg) {
- var $el = w instanceof Widget ? w.$el :
- w instanceof HTMLElement ? $(w) :
- w; // jquery element
+ if (w instanceof jQuery && w.length !== 1) {
+ const assertion = shouldHaveClass ? 'hasClass' : 'doesNotHaveClass';
+ QUnit.assert.ok(false, `Assertion '${assertion} ${className}' targets ${w.length} elements instead of 1`);
+ }
- if ($el.length !== 1) {
- var descr = `${shouldBeVisible ? 'isVisible' : 'isNotVisible'}`;
- QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
- } else {
- 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);
+ const el = w instanceof Widget || w instanceof owl.Component ? w.el :
+ w instanceof jQuery ? w[0] : w;
+
+ msg = msg || `target should ${shouldBeVisible ? '' : 'not'} be visible`;
+ let isVisible = el &&
+ el.offsetWidth &&
+ el.offsetHeight;
+ if (isVisible) {
+ // This computation is a little more heavy and we only want to perform it
+ // if the above assertion has failed.
+ const rect = el.getBoundingClientRect();
+ isVisible = rect.width + rect.height;
}
+ const condition = shouldBeVisible ? isVisible : !isVisible;
+ QUnit.assert.ok(condition, msg);
}
//------------------------------------------------------------------------------
@@ -87,32 +87,38 @@ function _checkVisible(w, shouldBeVisible, msg) {
*
* Example: assert.containsN(document.body, '.modal', 0)
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} selector
* @param {number} n
* @param {string} [msg]
*/
function containsN(w, selector, n, msg) {
if (typeof n !== 'number') {
- throw Error("containsN assert should be called with a number as third argument");
+ throw new Error("containsN assert should be called with a number as third argument");
+ }
+ let matches = [];
+ if (w instanceof owl.Component) {
+ if (!w.el) {
+ throw new Error(`containsN assert with selector '${selector}' called on an unmounted component`);
+ }
+ matches = w.el.querySelectorAll(selector);
+ } else {
+ const $el = w instanceof Widget ? w.$el :
+ w instanceof HTMLElement ? $(w) :
+ w; // jquery element
+ matches = $el.find(selector);
}
- var $el = w instanceof Widget ? w.$el :
- w instanceof HTMLElement ? $(w) :
- w; // jquery element
-
- var $matches = $el.find(selector);
if (!msg) {
- msg = `Selector '${selector}' should have exactly ${n} matches`;
- msg += ` (inside the target)`;
+ msg = `Selector '${selector}' should have exactly ${n} matches (inside the target)`;
}
- QUnit.assert.strictEqual($matches.length, n, msg);
+ QUnit.assert.strictEqual(matches.length, n, msg);
}
/**
* Checks that the target element (described by widget/jquery or html element)
* contains exactly 1 match for the selector.
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} selector
* @param {string} [msg]
*/
@@ -124,7 +130,7 @@ function containsOnce(w, selector, msg) {
* Checks that the target element (described by widget/jquery or html element)
* contains exactly 0 match for the selector.
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} selector
* @param {string} [msg]
*/
@@ -144,7 +150,7 @@ function containsNone(w, selector, msg) {
*
* div.a.b.c: has class 'a b c', but does not have class 'a c b'
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} className
* @param {string} [msg]
*/
@@ -158,7 +164,7 @@ function hasClass(w, className, msg) {
* - is unique
* - does not have the given class (specified by className)
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} className
* @param {string} [msg]
*/
@@ -172,7 +178,7 @@ function doesNotHaveClass(w, className, msg) {
* - is unique
* - has the given attribute with the proper value
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} attr
* @param {string} value
* @param {string} [msg]
@@ -196,7 +202,7 @@ function hasAttrValue(w, attr, value, msg) {
* - exists
* - is visible (as far as jQuery can tell: not in display none, ...)
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} [msg]
*/
function isVisible(w, msg) {
@@ -208,7 +214,7 @@ function isVisible(w, msg) {
* - exists
* - is not visible (as far as jQuery can tell: display none, ...)
*
- * @param {Widget|jQuery|HTMLElement} w
+ * @param {Widget|jQuery|HTMLElement|owl.Component} w
* @param {string} [msg]
*/
function isNotVisible(w, msg) {
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 60428ad552d..447c39240a7 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -86,13 +86,15 @@ async function nextMicrotaskTick() {
}
/**
- * 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.
+ * Returns a promise that will be resolved after the tick after the
+ * nextAnimationFrame
*
- * @return {Promise} a promise that will be fulfilled in the next jobqueue tick
+ * This is usefull to guarantee that OWL has had the time to render
+ *
+ * @returns {Promise}
*/
async function nextTick() {
- return concurrency.delay(0);
+ return testUtilsDom.returnAfterNextAnimationFrame();
}
// Loading static files cannot be properly simulated when their real content is
@@ -167,7 +169,7 @@ return Promise.all([
many2one: {
clickOpenDropdown: testUtilsFields.clickOpenM2ODropdown,
clickHighlightedItem: testUtilsFields.clickM2OHighlightedItem,
- clickItem: testUtilsFields.clickM2OItem,
+ clickItem: testUtilsFields.clickM2OItem,
searchAndClickItem: testUtilsFields.searchAndClickM2OItem,
},
editInput: testUtilsFields.editInput,
@@ -196,6 +198,8 @@ return Promise.all([
makeTestPromiseWithAssert: makeTestPromiseWithAssert,
nextMicrotaskTick: nextMicrotaskTick,
nextTick: nextTick,
+ prepareTarget: testUtilsCreate.prepareTarget,
+ returnAfterNextAnimationFrame: testUtilsDom.returnAfterNextAnimationFrame,
// 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 ee23648a49b..684485347a4 100644
--- a/addons/web/static/tests/helpers/test_utils_create.js
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -27,17 +27,12 @@ var Widget = require('web.Widget');
* @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}
*/
-var createActionManager = async function (params) {
+async function createActionManager(params) {
params = params || {};
- var $target = $('#qunit-fixture');
- if (params.debug) {
- $target = $('body');
- $target.addClass('debug');
- }
+ const target = prepareTarget(params.debug);
var widget = new Widget();
// when 'document' addon is installed, the sidebar does a 'search_read' on
@@ -57,7 +52,7 @@ var createActionManager = async function (params) {
},
});
testUtilsMock.addMockEnvironment(widget, _.defaults(params, { debounce: false }));
- await widget.prependTo($target);
+ await widget.prependTo(target);
widget.$el.addClass('o_web_client');
if (config.device.isMobile) {
widget.$el.addClass('o_touch_device');
@@ -79,7 +74,7 @@ var createActionManager = async function (params) {
});
return actionManager;
});
-};
+}
/**
* create a view from various parameters. Here, a view means a javascript
@@ -94,8 +89,6 @@ var createActionManager = async function (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.
@@ -106,17 +99,15 @@ var createActionManager = async function (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
+ * @param {Boolean} [params.doNotDisableAHref=false] will not preventDefault on the A elements of the view if true.
+ * Default is false.
* @returns {Promise<AbstractController>} resolves with the instance of the view
*/
async function createView(params) {
- var $target = $('#qunit-fixture');
+ const target = prepareTarget(params.debug);
var widget = new Widget();
- if (params.debug) {
- $target = $('body');
- $target.addClass('debug');
- }
// reproduce the DOM environment of views
- var $webClient = $('<div>').addClass('o_web_client').prependTo($target);
+ var $webClient = $('<div>').addClass('o_web_client').prependTo(target);
var $actionManager = $('<div>').addClass('o_action_manager').appendTo($webClient);
@@ -184,15 +175,19 @@ async function createView(params) {
// 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 () {
+ return view.appendTo(fragment).then( () => {
dom.prepend($actionManager, fragment, {
callbacks: [{ widget: view }],
in_DOM: true,
});
- view.$el.on('click', 'a', function (ev) {
- ev.preventDefault();
- });
+ if (!params.doNotDisableAHref) {
+ [...view.el.getElementsByTagName('A')].forEach((elem) => {
+ elem.addEventListener('click', (ev) => {
+ ev.preventDefault();
+ });
+ });
+ }
return view;
});
});
@@ -236,8 +231,6 @@ async function createCalendarView(params, options) {
* @param {Object} [params={}]
* @param {Object} [params.action={}]
* @param {Object} [params.context={}]
- * @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.domain=[]]
* @param {integer} [params.fieldDebounce=0] the debounce value to use for the
* duration of the test.
@@ -264,13 +257,9 @@ async function createCalendarView(params, options) {
*/
function createControlPanel(params) {
params = params || {};
- var $target = $('#qunit-fixture');
- if (params.debug) {
- $target = $('body');
- $target.addClass('debug');
- }
+ const target = prepareTarget(params.debug);
// reproduce the DOM environment of a view control panel
- var $webClient = $('<div>').addClass('o_web_client').prependTo($target);
+ var $webClient = $('<div>').addClass('o_web_client').prependTo(target);
var $actionManager = $('<div>').addClass('o_action_manager').appendTo($webClient);
var $action = $('<div>').addClass('o_action').appendTo($actionManager);
@@ -395,6 +384,19 @@ function createParent(params) {
return widget;
}
+/**
+ * Get the target (fixture or body) of the document and adds event listeners
+ * to intercept custom or DOM events.
+ *
+ * @param {boolean} [debug=false] if true, the widget will be appended in
+ * the DOM. Also, RPCs and uncaught OdooEvent will be logged
+ * @returns {HTMLElement}
+ */
+function prepareTarget(debug=false) {
+ document.body.classList.toggle('debug', debug);
+ return debug ? document.body : document.querySelector("#qunit-fixture");
+}
+
return {
createActionManager: createActionManager,
createCalendarView: createCalendarView,
@@ -403,6 +405,7 @@ return {
createModel: createModel,
createParent: createParent,
createView: createView,
+ prepareTarget: prepareTarget,
};
});
diff --git a/addons/web/static/tests/helpers/test_utils_dom.js b/addons/web/static/tests/helpers/test_utils_dom.js
index a4315002cab..e18726138d1 100644
--- a/addons/web/static/tests/helpers/test_utils_dom.js
+++ b/addons/web/static/tests/helpers/test_utils_dom.js
@@ -12,6 +12,37 @@ var concurrency = require('web.concurrency');
* testUtils file.
*/
+/**
+ * Returns a promise that will be resolved after the tick after the
+ * nextAnimationFrame
+ *
+ * This is useful to guarantee that OWL has had the time to render
+ *
+ * @returns {Promise}
+ */
+function returnAfterNextAnimationFrame() {
+ return new Promise((resolve) => {
+ window.requestAnimationFrame(async () => {
+ await concurrency.delay(0);
+ resolve();
+ });
+ });
+}
+
+/**
+ * Check if an object is an instance of EventTarget.
+ *
+ * @param {Object} node
+ * @returns {boolean}
+ */
+function isEventTarget(node) {
+ if (node instanceof window.top.EventTarget) {
+ return true;
+ }
+ const contextWindow = node.defaultView || // document
+ (node.ownerDocument && node.ownerDocument.defaultView); // iframe node
+ return contextWindow && node instanceof contextWindow.EventTarget;
+}
/**
* simulate a drag and drop operation between 2 jquery nodes: $el and $to.
@@ -23,8 +54,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|HTMLElement} $el
- * @param {jqueryElement|HTMLElement} $to
+ * @param {jQuery|EventTarget} $el
+ * @param {jQuery|EventTarget} $to
* @param {Object} [options]
* @param {string|Object} [options.position='center'] target position:
* can either be one of {'top', 'bottom', 'left', 'right'} or
@@ -36,20 +67,20 @@ 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
- * @param {jQuery|HTMLElement} [options.ctrlKey=undefined] if the ctrl key should be considered pressed at the time of mouseup
+ * @param {jQuery|EventTarget} [options.mouseenterTarget=undefined] target of the mouseenter event
+ * @param {jQuery|EventTarget} [options.mousedownTarget=undefined] target of the mousedown event
+ * @param {jQuery|EventTarget} [options.mousemoveTarget=undefined] target of the mousemove event
+ * @param {jQuery|EventTarget} [options.mouseupTarget=undefined] target of the mouseup event
+ * @param {jQuery|EventTarget} [options.ctrlKey=undefined] if the ctrl key should be considered pressed at the time of mouseup
* @returns {Promise}
*/
-function dragAndDrop($el, $to, options) {
+async function dragAndDrop($el, $to, options) {
let el = null;
- if ($el instanceof EventTarget) {
+ if (isEventTarget($el)) {
el = $el;
$el = $(el);
}
- if ($to instanceof EventTarget) {
+ if (isEventTarget($to)) {
$to = $($to);
}
options = options || {};
@@ -58,8 +89,8 @@ function dragAndDrop($el, $to, options) {
var toOffset = $to.offset();
if (_.isObject(position)) {
- toOffset.top += position.top;
- toOffset.left += position.left;
+ toOffset.top += position.top + 1;
+ toOffset.left += position.left + 1;
} else {
toOffset.top += $to.outerHeight() / 2;
toOffset.left += $to.outerWidth() / 2;
@@ -81,33 +112,32 @@ function dragAndDrop($el, $to, options) {
toOffset.left += bound.left;
toOffset.top += bound.top;
}
- triggerEvent(options.mouseenterTarget || el || $el, 'mouseenter');
+ await triggerEvent(options.mouseenterTarget || el || $el, 'mouseover', {}, true);
if (!(options.continueMove)) {
elementCenter.left += $el.outerWidth() / 2;
elementCenter.top += $el.outerHeight() / 2;
- triggerEvent(options.mousedownTarget || el || $el, 'mousedown', {
+ await triggerEvent(options.mousedownTarget || el || $el, 'mousedown', {
which: 1,
pageX: elementCenter.left,
pageY: elementCenter.top
- });
+ }, true);
}
-
- triggerEvent(options.mousemoveTarget || el || $el, 'mousemove', {
+ await triggerEvent(options.mousemoveTarget || el || $el, 'mousemove', {
which: 1,
pageX: toOffset.left,
pageY: toOffset.top
- });
+ }, true);
if (!options.disableDrop) {
- triggerEvent(options.mouseupTarget || el || $el, 'mouseup', {
+ await triggerEvent(options.mouseupTarget || el || $el, 'mouseup', {
which: 1,
pageX: toOffset.left,
pageY: toOffset.top,
ctrlKey: options.ctrlKey,
- });
+ }, true);
if (options.withTrailingClick) {
- triggerEvent(options.mouseupTarget || el || $el, 'click');
+ await triggerEvent(options.mouseupTarget || el || $el, 'click', {}, true);
}
} else {
// It's impossible to drag another element when one is already
@@ -115,10 +145,10 @@ function dragAndDrop($el, $to, options) {
// over otherwise it's impossible for the next tests to drag and
// drop elements.
$el.on('remove', function () {
- triggerEvent($el, 'mouseup');
+ triggerEvent($el, 'mouseup', {}, true);
});
}
- return concurrency.delay(0);
+ return returnAfterNextAnimationFrame();
}
/**
@@ -126,7 +156,7 @@ 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 {jQuery|HTMLElement} $el
+ * @param {jQuery|EventTarget} $el
* @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
* @returns {Promise}
*/
@@ -180,7 +210,7 @@ function triggerPositionalMouseEvent(x, y, type) {
*/
function triggerKeypressEvent(char) {
var keycode;
- if (char === "Enter") {
+ if (char === 'Enter') {
keycode = $.ui.keyCode.ENTER;
} else if (char === "Tab") {
keycode = $.ui.keyCode.TAB;
@@ -188,6 +218,7 @@ function triggerKeypressEvent(char) {
keycode = char.charCodeAt(0);
}
return triggerEvent(document.body, 'keypress', {
+ key: char,
keyCode: keycode,
which: keycode,
});
@@ -197,24 +228,30 @@ function triggerKeypressEvent(char) {
* Click on a specified element. If the option first or last is not specified,
* this method also check the unicity and the visibility of the target.
*
- * @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector)
- * @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 {string|EventTarget|EventTarget[]} el (if string: it is a (jquery) selector)
+ * @param {Object} [options={}] click options
* @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
+ * @param {boolean} [options.first=false] if true, clicks on the first element
+ * @param {boolean} [options.last=false] if true, clicks on the last element
* @returns {Promise}
*/
-function click(el, options) {
- options = options || {};
- var matches = typeof el === 'string' ? $(el) : el;
- var selectorMsg = typeof el === 'string' ? `(selector: ${el})` : '';
- if (!matches.filter) { // it might be an array of dom elements
- matches = $(matches);
+async function click(el, options = {}) {
+ let matches, target;
+ let selectorMsg = "";
+ if (typeof el === 'string') {
+ el = $(el);
+ }
+ if (isEventTarget(el)) {
+ // EventTarget
+ matches = [el];
+ } else {
+ // Any other iterable object containing EventTarget objects (jQuery, HTMLCollection, etc.)
+ matches = [...el];
}
- var validMatches = options.allowInvisible ? matches : matches.filter(':visible');
+
+ const validMatches = options.allowInvisible ?
+ matches : matches.filter(t => $(t).is(':visible'));
if (options.first) {
if (validMatches.length === 1) {
@@ -222,27 +259,24 @@ function click(el, options) {
' you are sure that there is exactly one target, please use the ' +
'click function instead of the clickFirst function');
}
- validMatches = validMatches.first();
+ target = validMatches[0];
} else if (options.last) {
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');
}
- validMatches = validMatches.last();
- }
- if (validMatches.length === 0 && matches.length > 0) {
- throw new Error(`Element to click on is not visible ${selectorMsg}`);
+ target = validMatches[validMatches.length - 1];
} else if (validMatches.length !== 1) {
throw new Error(`Found ${validMatches.length} elements to click on, instead of 1 ${selectorMsg}`);
- }
- if (options.shouldTriggerClick) {
- return triggerEvent(validMatches, 'click');
} else {
- validMatches.click();
+ target = validMatches[0];
+ }
+ if (validMatches.length === 0 && matches.length > 0) {
+ throw new Error(`Element to click on is not visible ${selectorMsg}`);
}
- return concurrency.delay(0);
+ return triggerEvent(target, 'click');
}
/**
@@ -250,15 +284,14 @@ function click(el, options) {
* only one visible element, we trigger an error. In that case, it is better to
* use the click helper instead.
*
- * @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector)
+ * @param {string|EventTarget|EventTarget[]} el (if string: it is a (jquery) selector)
+ * @param {boolean} [options={}] click options
* @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
* @returns {Promise}
*/
function clickFirst(el, options) {
- return click(el, _.extend({}, options, {first: true}));
+ return click(el, _.extend({}, options, { first: true }));
}
/**
@@ -266,64 +299,154 @@ function clickFirst(el, options) {
* only one visible element, we trigger an error. In that case, it is better to
* use the click helper instead.
*
- * @param {string|NodeList|jQuery} el
+ * @param {string|EventTarget|EventTarget[]} el (if string: it is a (jquery) selector)
+ * @param {boolean} [options={}] click options
* @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
* @returns {Promise}
*/
function clickLast(el, options) {
- return click(el, _.extend({}, options, {last: true}));
+ return click(el, _.extend({}, options, { last: true }));
}
/**
- * Trigger events on the specified target
- * @param {jQuery|HTMLElement} $el should target a single dom node
+ * Triggers multiple events on the specified element.
+ *
+ * @param {EventTarget|EventTarget[]} el
* @param {string[]} events the events you want to trigger
* @returns {Promise}
*/
-function triggerEvents($el, events) {
- if ($el instanceof jQuery) {
- if ($el.length !== 1) {
- throw new Error(`target has length ${$el.length} instead of 1`);
+async function triggerEvents(el, events) {
+ 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];
}
- return events.reduce((previous, event) => {
- return previous.then(() => triggerEvent($el, event));
- }, Promise.resolve());
+
+ for (let e = 0; e < events.length; e++) {
+ await triggerEvent(el, events[e], {});
+ }
}
+function onlyBubble(args) {
+ return Object.assign({}, args, {
+ bubbles: true
+ });
+}
+
+function mouseEventMapping(args) {
+ return Object.assign({}, args, {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: args ? args.pageX : undefined,
+ clientY: args ? args.pageY : undefined,
+ });
+}
+
+function mouseEventNoBubble(args) {
+ return Object.assign({}, args, {
+ view: window,
+ bubbles: false,
+ cancelable: false,
+ clientX: args ? args.pageX : undefined,
+ clientY: args ? args.pageY : undefined,
+ });
+}
+
+function keyboardEventBubble(args) {
+ return Object.assign({}, args, { bubbles: true, keyCode: args.which });
+}
+
+const eventTypes = {
+ auxclick: { constructor: MouseEvent, processParameters: mouseEventMapping },
+ click: { constructor: MouseEvent, processParameters: mouseEventMapping },
+ contextmenu: { constructor: MouseEvent, processParameters: mouseEventMapping },
+ dblclick: { constructor: MouseEvent, processParameters: mouseEventMapping },
+ mousedown: { constructor: MouseEvent, processParameters: mouseEventMapping },
+ mouseup: { constructor: MouseEvent, processParameters: mouseEventMapping },
+
+ mousemove: { constructor: MouseEvent, processParameters: mouseEventMapping },
+ mouseenter: { constructor: MouseEvent, processParameters: mouseEventNoBubble },
+ mouseleave: { constructor: MouseEvent, processParameters: mouseEventNoBubble },
+ mouseover: { constructor: MouseEvent, processParameters: mouseEventMapping },
+ mouseout: { constructor: MouseEvent, processParameters: mouseEventMapping },
+
+ focus: {
+ constructor: FocusEvent, processParameters: (args) => {
+ return Object.assign({}, args, { bubbles: false });
+ }
+ },
+ focusin: { constructor: FocusEvent, processParameters: onlyBubble },
+ blur: {
+ constructor: FocusEvent, processParameters: (args) => {
+ return Object.assign({}, args, { bubbles: false });
+ }
+ },
+
+ cut: { constructor: ClipboardEvent, processParameters: onlyBubble },
+ copy: { constructor: ClipboardEvent, processParameters: onlyBubble },
+ paste: { constructor: ClipboardEvent, processParameters: onlyBubble },
+
+ keydown: { constructor: KeyboardEvent, processParameters: keyboardEventBubble },
+ keypress: { constructor: KeyboardEvent, processParameters: keyboardEventBubble },
+ keyup: { constructor: KeyboardEvent, processParameters: keyboardEventBubble },
+
+ drag: { constructor: DragEvent, processParameters: onlyBubble },
+ dragend: { constructor: DragEvent, processParameters: onlyBubble },
+ dragenter: { constructor: DragEvent, processParameters: onlyBubble },
+ dragstart: { constructor: DragEvent, processParameters: onlyBubble },
+ dragleave: { constructor: DragEvent, processParameters: onlyBubble },
+ dragover: { constructor: DragEvent, processParameters: onlyBubble },
+ drop: { constructor: DragEvent, processParameters: onlyBubble },
+};
+
/**
- * 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
+ * Triggers an event on the specified target.
+ * This function will dispatch a native event to an EventTarget or a
+ * jQuery event to a jQuery object. This behaviour can be overridden by the
+ * jquery option.
+ *
+ * @param {EventTarget|EventTarget[]} el
+ * @param {string} eventType event type
+ * @param {Object} [eventAttrs] event attributes
+ * on a jQuery element with the `$.fn.trigger` function
+ * @param {Boolean} [fast=false] true if the trigger event have to wait for a single tick instead of waiting for the next animation frame
* @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);
+async function triggerEvent(el, eventType, eventAttrs = {}, fast = false) {
+ let matches;
+ let selectorMsg = "";
+ if (isEventTarget(el)) {
+ matches = [el];
+ } else {
+ matches = [...el];
+ }
+
+ if (matches.length !== 1) {
+ throw new Error(`Found ${matches.length} elements to trigger "${eventType}" on, instead of 1 ${selectorMsg}`);
+ }
+
+ const target = matches[0];
+ let event;
+
+ if (!eventTypes[eventType] && !eventTypes[eventType.type]) {
+ event = new Event(eventType, Object.assign({}, eventAttrs, { bubbles: true }));
} else {
- const event = Object.keys(options).length ?
- $.Event(type, options) :
- type;
- $el.trigger(event);
+ if (typeof eventType === "object") {
+ const { constructor, processParameters } = eventTypes[eventType.type];
+ const eventParameters = processParameters(eventType);
+ event = new constructor(eventType.type, eventParameters);
+ } else {
+ const { constructor, processParameters } = eventTypes[eventType];
+ event = new constructor(eventType, processParameters(eventAttrs));
+ }
}
- return concurrency.delay(0);
+ target.dispatchEvent(event);
+ return fast ? undefined : returnAfterNextAnimationFrame();
}
@@ -348,6 +471,7 @@ return {
clickLast: clickLast,
triggerEvents: triggerEvents,
triggerEvent: triggerEvent,
+ returnAfterNextAnimationFrame: returnAfterNextAnimationFrame,
};
});
diff --git a/addons/web/static/tests/helpers/test_utils_fields.js b/addons/web/static/tests/helpers/test_utils_fields.js
index dcc9329bcbe..6532aa7033c 100644
--- a/addons/web/static/tests/helpers/test_utils_fields.js
+++ b/addons/web/static/tests/helpers/test_utils_fields.js
@@ -24,17 +24,21 @@ var domUtils = require('web.test_utils_dom');
* Example:
* testUtils.fields.editAndTrigger($('selector'), 'test', ['input', 'change']);
*
- * @param {jQuery} $el should target an input, textarea or select
+ * @param {jQuery|EventTarget} 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) {
- throw new Error(`target ${$el.selector} has length ${$el.length} instead of 1`);
+function editAndTrigger(el, value, events) {
+ if (el instanceof jQuery) {
+ if (el.length !== 1) {
+ throw new Error(`target ${el.selector} has length ${el.length} instead of 1`);
+ }
+ el.val(value);
+ } else {
+ el.value = value;
}
- $el.val(value);
- return domUtils.triggerEvents($el, events);
+ return domUtils.triggerEvents(el, events);
}
/**
@@ -45,12 +49,12 @@ function editAndTrigger($el, value, events) {
* Example:
* testUtils.fields.editInput($('selector'), 'somevalue');
*
- * @param {jQuery} $el
+ * @param {jQuery|EventTarget} el should target an input, textarea or select
* @param {string|number} value
* @returns {Promise}
*/
-function editInput($el, value) {
- return editAndTrigger($el, value, ['input']);
+function editInput(el, value) {
+ return editAndTrigger(el, value, ['input']);
}
/**
@@ -61,12 +65,12 @@ function editInput($el, value) {
* Example:
* testUtils.fields.editSelect($('selector'), 'somevalue');
*
- * @param {jQuery} $el
+ * @param {jQuery|EventTarget} el should target an input, textarea or select
* @param {string|number} value
* @returns {Promise}
*/
-function editSelect($el, value) {
- return editAndTrigger($el, value, ['change']);
+function editSelect(el, value) {
+ return editAndTrigger(el, value, ['change']);
}
/**
@@ -83,10 +87,9 @@ async function clickOpenM2ODropdown(fieldName, selector) {
if (matches.length !== 1) {
throw new Error(`cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`);
}
- matches[0].click();
- return await concurrency.delay(0).then(function () {
- return matches[0];
- });
+
+ await domUtils.click(matches[0]);
+ return matches[0];
}
/**
diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js
index 8758d162a7b..b4c722b7de1 100644
--- a/addons/web/static/tests/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/helpers/test_utils_mock.js
@@ -19,6 +19,7 @@ var session = require('web.session');
var DebouncedField = basic_fields.DebouncedField;
+
//------------------------------------------------------------------------------
// Private functions
//------------------------------------------------------------------------------
@@ -45,7 +46,7 @@ function _observe(widget) {
* 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 {DOM Node} el
+ * @param {HTMLElement} el
* @param {[Widget]} widget the widget on which the rpc should be performed
*/
function removeSrcAttribute(el, widget) {
@@ -569,7 +570,6 @@ function patchSetTimeout() {
};
}
-
return {
addMockEnvironment: addMockEnvironment,
fieldsViewGet: fieldsViewGet,
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 6ef9f8f17d8..fb0a64b7705 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -2538,7 +2538,7 @@ QUnit.module('Views', {
);
await testUtils.nextTick();
- assert.strictEqual(calendar.$('.fc-event:eq(0)').text().trim(), "event 6");
+ assert.strictEqual(calendar.$('.fc-event:first').text().trim(), "event 6");
await testUtils.dom.click(calendar.$('.fc-event:eq(0)'));
assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "11/27/2016 16:00:00");
@@ -2583,8 +2583,8 @@ QUnit.module('Views', {
// Create event (on 20 december)
var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)');
- testUtils.triggerMouseEvent($cell, "mousedown");
- testUtils.triggerMouseEvent($cell, "mouseup");
+ await testUtils.triggerMouseEvent($cell, "mousedown");
+ await testUtils.triggerMouseEvent($cell, "mouseup");
await testUtils.nextTick();
var $input = $('.modal-body input:first');
await testUtils.fields.editInput($input, "An event");
@@ -2689,8 +2689,8 @@ QUnit.module('Views', {
// Create event (on 20 december)
var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)');
- testUtils.triggerMouseEvent($cell, "mousedown");
- testUtils.triggerMouseEvent($cell, "mouseup");
+ await testUtils.triggerMouseEvent($cell, "mousedown");
+ await testUtils.triggerMouseEvent($cell, "mouseup");
await testUtils.nextTick();
var $input = $('.modal-body input:first');
await testUtils.fields.editInput($input, "An event");
@@ -2702,6 +2702,7 @@ QUnit.module('Views', {
calendar.$('.fc-event').first(),
calendar.$('.fc-day-top').first()
);
+ await testUtils.nextTick();
calendar.destroy();
});
@@ -2742,8 +2743,8 @@ QUnit.module('Views', {
});
var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
- testUtils.dom.triggerMouseEvent($cell, "mousedown");
- testUtils.dom.triggerMouseEvent($cell, "mouseup");
+ await testUtils.dom.triggerMouseEvent($cell, "mousedown");
+ await testUtils.dom.triggerMouseEvent($cell, "mouseup");
await testUtils.nextTick();
var $input = $('.modal-body input:first');
@@ -2783,8 +2784,8 @@ QUnit.module('Views', {
});
var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
- testUtils.dom.triggerMouseEvent($cell, "mousedown");
- testUtils.dom.triggerMouseEvent($cell, "mouseup");
+ await testUtils.dom.triggerMouseEvent($cell, "mousedown");
+ await testUtils.dom.triggerMouseEvent($cell, "mouseup");
await testUtils.nextTick();
calendar.destroy();
});
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index 7e5fd94d062..0b8334472ad 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -183,7 +183,7 @@ QUnit.module('Views', {
var records = this.data.partner.records
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
- return record.id === partnerID;
+ return record.id === partnerID;
}).active = false;
})
this.data.partner.records[0].active;
@@ -240,7 +240,7 @@ QUnit.module('Views', {
var records = this.data.partner.records
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
- return record.id === partnerID;
+ return record.id === partnerID;
}).active = false;
})
this.data.partner.records[0].active;
@@ -2380,6 +2380,7 @@ QUnit.module('Views', {
throw new Error("should not switch view");
},
},
+ doNotDisableAHref: true,
});
var $record = kanban.$('.o_kanban_record:not(.o_kanban_ghost)');
@@ -2392,10 +2393,6 @@ QUnit.module('Views', {
assert.ok(!!$testLink[0].href,
"link inside kanban record should have non-empty href");
- // Mocked views prevent accessing a link with href. This is intented
- // most of the time, but not in this test which specifically needs to
- // let the browser access a link with href.
- kanban.$el.off('click', 'a');
// Prevent the browser default behaviour when clicking on anything.
// This includes clicking on a `<a>` with `href`, so that it does not
// change the URL in the address bar.
@@ -4944,7 +4941,7 @@ QUnit.module('Views', {
var records = this.data.partner.records
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
- return record.id === partnerID;
+ return record.id === partnerID;
}).active = false;
})
this.data.partner.records[0].active;
@@ -5001,7 +4998,7 @@ QUnit.module('Views', {
var records = this.data.partner.records
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
- return record.id === partnerID;
+ return record.id === partnerID;
}).active = false;
})
this.data.partner.records[0].active;
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index ac66ef641eb..d42bd2e1edd 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -4853,11 +4853,9 @@ QUnit.module('Views', {
await list.update({groupBy: []});
- await testUtils.dom.click(list.$('.o_data_cell:eq(0)'));
+ await testUtils.dom.clickFirst(list.$('.o_data_cell'));
- await testUtils.dom.click(list.$('.o_selected_row .o_data_cell .o_field_many2one input'));
- await testUtils.dom.triggerEvents($('.ui-autocomplete a:contains(Search More)'),
- ['mouseenter', 'click']);
+ await testUtils.fields.many2one.searchAndClickItem('m2o', { item: 'Search More' });
assert.containsOnce($('body'), '.modal-content');
diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js
index 567d7a4ae66..f6fd77349b6 100644
--- a/addons/web/static/tests/views/pivot_tests.js
+++ b/addons/web/static/tests/views/pivot_tests.js
@@ -336,21 +336,21 @@ QUnit.module('Views', {
// check column highlighting
// hover third measure
- await testUtils.dom.triggerEvents(pivot.$('th.o_pivot_measure_row:nth(2)'), 'mouseenter');
+ await testUtils.dom.triggerEvents(pivot.$('th.o_pivot_measure_row:nth(2)'), 'mouseover');
assert.containsN(pivot, '.o_cell_hover', 3);
for (var i = 0; i < 3; i++) {
assert.hasClass(pivot.$('tbody tr:nth(' + i + ') td:nth(2)'), 'o_cell_hover');
}
- await testUtils.dom.triggerEvents(pivot.$('th.o_pivot_measure_row:nth(2)'), 'mouseleave');
+ await testUtils.dom.triggerEvents(pivot.$('th.o_pivot_measure_row:nth(2)'), 'mouseout');
assert.containsNone(pivot, '.o_cell_hover');
// hover second cell, second row
- await testUtils.dom.triggerEvents(pivot.$('tbody tr:nth(1) td:nth(1)'), 'mouseenter');
+ await testUtils.dom.triggerEvents(pivot.$('tbody tr:nth(1) td:nth(1)'), 'mouseover');
assert.containsN(pivot, '.o_cell_hover', 3);
for (i = 0; i < 3; i++) {
assert.hasClass(pivot.$('tbody tr:nth(' + i + ') td:nth(1)'), 'o_cell_hover');
}
- await testUtils.dom.triggerEvents(pivot.$('tbody tr:nth(1) td:nth(1)'), 'mouseleave');
+ await testUtils.dom.triggerEvents(pivot.$('tbody tr:nth(1) td:nth(1)'), 'mouseout');
assert.containsNone(pivot, '.o_cell_hover');
pivot.destroy();
diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js
index b68d676a125..1086d157475 100644
--- a/addons/web/static/tests/views/view_dialogs_tests.js
+++ b/addons/web/static/tests/views/view_dialogs_tests.js
@@ -531,13 +531,12 @@ QUnit.module('Views', {
},
});
- await testUtils.dom.click(form.$('.o_field_widget[name="instrument"] .o_input'));
+ await testUtils.fields.many2one.clickOpenDropdown('instrument');
assert.notOk($('.ui-autocomplete a:contains(Create and Edit)').length,
'Create and edit not present in dropdown');
- await testUtils.dom.triggerEvents($('.ui-autocomplete a:contains(Search More)'),
- ['mouseenter', 'click']);
+ await testUtils.fields.many2one.clickItem('instrument', 'Search More...');
var $modal = $('.modal-dialog.modal-lg');
diff --git a/addons/web_editor/static/tests/field_html_tests.js b/addons/web_editor/static/tests/field_html_tests.js
index 612277bcd08..6c4c3f9c5a3 100644
--- a/addons/web_editor/static/tests/field_html_tests.js
+++ b/addons/web_editor/static/tests/field_html_tests.js
@@ -380,26 +380,6 @@ QUnit.module('web_editor', {}, function () {
form.destroy();
});
- QUnit.test('save immediately before iframe is rendered in edit mode', async function (assert) {
- assert.expect(1);
-
- var form = await testUtils.createAsyncView({
- View: FormView,
- model: 'note.note',
- data: this.data,
- arch: '<form>' +
- '<field name="body" widget="html" style="height: 100px" options="{\'cssEdit\': \'template.assets\'}"/>' +
- '</form>',
- res_id: 1,
- });
- await testUtils.form.clickEdit(form);
- await testUtils.nextTick();
- await testUtils.form.clickSave(form);
- await testUtils.nextTick();
- assert.ok(true, "No traceback encountered. The wysiwyg was cut while not loaded.");
- form.destroy();
- });
-
QUnit.module('translation');
QUnit.test('field html translatable', async function (assert) {