diff options
| author | Vincent Schippefilt <vsc@odoo.com> | 2019-12-04 10:44:32 +0000 |
|---|---|---|
| committer | Vincent Schippefilt <vsc@odoo.com> | 2019-12-09 11:09:10 +0000 |
| commit | b0941d19a07b08fabc64a284b58e57edc46203dd (patch) | |
| tree | a6b25026177d26a64c6c7f6005e27c41fca5c1b6 | |
| parent | 29726e8fd000cb09075f9b6b2a6a8d2ddaf4ef58 (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>
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) { |
