diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index d0d2378eda2..b42cfa0741f 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -1210,12 +1210,7 @@ var FieldX2Many = AbstractField.extend(WidgetAdapterMixin, { return this.renderer.confirmUpdate(state, command.id, fieldNames, ev.initialEvent); } } - return this._super.apply(this, arguments).then(() => { - if (this.view) { - this._renderButtons(); - this._updateControlPanel(); - } - }); + return this._super.apply(this, arguments); }, /** @@ -1348,7 +1343,7 @@ var FieldX2Many = AbstractField.extend(WidgetAdapterMixin, { columnInvisibleFields: this.currentColInvisibleFields, keepWidths: true, }).then(() => { - this._updateControlPanel({ size: this.value.count }); + return this._updateControlPanel({ size: this.value.count }); }); } var arch = this.view.arch; @@ -1461,9 +1456,16 @@ var FieldX2Many = AbstractField.extend(WidgetAdapterMixin, { */ _updateControlPanel: function (pagingState) { if (this._controlPanelWrapper) { + this._renderButtons(); + const pagerProps = Object.assign(this.pagingState, pagingState, { + // sometimes, we temporarily want to increase the pager limit + // (for instance, when we add a new record on a page that already + // contains the maximum number of records) + limit: Math.max(this.value.limit, this.value.data.length), + }); const newProps = { cp_content: { $buttons: this.$buttons }, - pager: Object.assign(this.pagingState, pagingState), + pager: pagerProps, }; return this._controlPanelWrapper.update(newProps); } @@ -1852,16 +1854,6 @@ var FieldOne2Many = FieldX2Many.extend({ var index = 0; if (self.editable !== 'top') { index = self.value.data.length - 1; - // we consider that a new record, created in the bottom, - // does not count as a record worth mentioning in the - // pager, at least not until the line has been saved. - // We prevent the pager from increasing its size, which - // means that the pager is not displayed when we just - // reach the limit. For example, if limit is 3, if we - // have 3 records, and we click on add, we will see the - // 4 records on the same page, but we do not want a - // pager. - self._updateControlPanel({ size: self.value.count - 1 }); } var newID = self.value.data[index].id; self.renderer.editRecord(newID); diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 81327f36598..0f38649d0bc 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -514,7 +514,10 @@ var FormController = BasicController.extend({ * @private */ _onDiscard: function () { - this._discardChanges(); + this._disableButtons(); + this._discardChanges() + .then(this._enableButtons.bind(this)) + .guardedCatch(this._enableButtons.bind(this)); }, /** * Called when the user clicks on 'Duplicate Record' in the action menus @@ -533,9 +536,13 @@ var FormController = BasicController.extend({ * @private */ _onEdit: function () { + this._disableButtons(); // wait for potential pending changes to be saved (done with widgets // allowing to edit in readonly) - this.mutex.getUnlockedDef().then(this._setMode.bind(this, 'edit')); + this.mutex.getUnlockedDef() + .then(this._setMode.bind(this, 'edit')) + .then(this._enableButtons.bind(this)) + .guardedCatch(this._enableButtons.bind(this)); }, /** * This method is called when someone tries to freeze the order, most likely diff --git a/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js index d7d18dfb86b..ed5fcd7f985 100644 --- a/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js @@ -1383,8 +1383,7 @@ QUnit.module('fields', {}, function () { await testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button')); - var m2m_records = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_data_cell:not(.o_many2many_tags_cell)'); - assert.equal(m2m_records.length, 51, + assert.strictEqual(form.$('.o_data_row').length, 51, 'We should have added all the records present in the search view to the m2m field'); // the 50 in batch + 'gold' await testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_save')); @@ -1411,8 +1410,7 @@ QUnit.module('fields', {}, function () { await testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button')); - pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit'); - assert.equal(pager_limit.text(), '51', + assert.strictEqual(form.$('.o_data_row').length, 51, 'We should have 51 records in the m2m field'); form.destroy(); 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 50e7ae2e6cb..80f5dd89304 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 @@ -3293,13 +3293,16 @@ QUnit.module('fields', {}, function () { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column when the product_id is set"); await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), '', 'keyup'); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsN(form, 'th:not(.o_list_record_remove_header)', 2, "should be 2 columns in the one2many when product_id is not set"); await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column after the value change"); form.destroy(); @@ -3386,13 +3389,16 @@ QUnit.module('fields', {}, function () { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column when the product_id is set"); await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), '', 'keyup'); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsN(form, 'th:not(.o_list_record_remove_header)', 2, "should be 2 columns in the one2many when product_id is not set"); await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column after the value change"); form.destroy(); diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js index 4d0055eb3b1..a34d518684d 100644 --- a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js @@ -3,6 +3,8 @@ odoo.define('web.field_one_to_many_tests', function (require) { var AbstractField = require('web.AbstractField'); var AbstractStorageService = require('web.AbstractStorageService'); +const ControlPanel = require('web.ControlPanel'); +const fieldRegistry = require('web.field_registry'); var FormView = require('web.FormView'); var KanbanRecord = require('web.KanbanRecord'); var ListRenderer = require('web.ListRenderer'); @@ -2557,7 +2559,7 @@ QUnit.module('fields', {}, function () { await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.dom.click(form.$el); - assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_pager').text().trim(), '1-3 / 5', + assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_pager').text().trim(), '1-4 / 5', "pager should display the correct total"); form.destroy(); }); @@ -2869,7 +2871,7 @@ QUnit.module('fields', {}, function () { }); QUnit.test('editable one2many list, adding line when only one page', async function (assert) { - assert.expect(1); + assert.expect(5); this.data.partner.records[0].turtles = [1, 2, 3]; var form = await createView({ @@ -2892,6 +2894,15 @@ QUnit.module('fields', {}, function () { // the record currently being added should not count in the pager assert.containsNone(form, '.o_field_widget[name=turtles] .o_pager'); + // unselect the row + await testUtils.dom.click(form.$el); + assert.containsNone(form, '.o_selected_row'); + assert.containsNone(form, '.o_field_widget[name=turtles] .o_pager'); + + await testUtils.form.clickSave(form); + assert.containsOnce(form, '.o_field_widget[name=turtles] .o_pager'); + assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_pager').text(), "1-3 / 4"); + form.destroy(); }); @@ -2993,13 +3004,13 @@ QUnit.module('fields', {}, function () { // see a confirm dialog await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); - assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_pager').text().trim(), '1-3 / 4', + assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_pager').text().trim(), '1-4 / 5', "pager should still display the correct total"); // click on cancel await testUtils.dom.click($('.modal .modal-footer .btn-secondary')); - assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_pager').text().trim(), '1-3 / 4', + assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_pager').text().trim(), '1-4 / 5', "pager should again display the correct total"); assert.containsOnce(form, '.o_field_one2many input.o_field_invalid', "there should be an invalid input in the one2many"); @@ -3007,7 +3018,7 @@ QUnit.module('fields', {}, function () { }); QUnit.test('editable one2many list, adding, discarding, and pager', async function (assert) { - assert.expect(2); + assert.expect(4); this.data.partner.records[0].turtles = [1]; @@ -3025,15 +3036,15 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - // add a 4 records record (to make the pager appear) + // add 4 records (to have more records than the limit) await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - // go on next page - await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + assert.containsN(form, 'tr.o_data_row', 5); + assert.containsNone(form, '.o_field_widget[name=turtles] .o_pager'); // discard await testUtils.form.clickDiscard(form); @@ -4541,6 +4552,12 @@ QUnit.module('fields', {}, function () { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + // use of owlCompatibilityExtraNextTick because we have an x2many field with a boolean field + // (written in owl), so when we add a line, we sequentially render the list itself + // (including the boolean field), so we have to wait for the next animation frame, and + // then we render the control panel (also in owl), so we have to wait again for the + // next animation frame + await testUtils.owlCompatibilityExtraNextTick(); form.destroy(); }); @@ -4571,6 +4588,12 @@ QUnit.module('fields', {}, function () { }, }); await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + // use of owlCompatibilityExtraNextTick because we have an x2many field with a boolean field + // (written in owl), so when we add a line, we sequentially render the list itself + // (including the boolean field), so we have to wait for the next animation frame, and + // then we render the control panel (also in owl), so we have to wait again for the + // next animation frame + await testUtils.owlCompatibilityExtraNextTick(); assert.verifySteps(['onchange', 'onchange']); form.destroy(); }); @@ -6071,6 +6094,7 @@ QUnit.module('fields', {}, function () { // add a new row (which is invalid at first) await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.owlCompatibilityExtraNextTick(); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0", "int_field should still be 0 (no onchange should have been done yet)"); assert.verifySteps(['load_views', 'read', 'onchange']); @@ -6693,13 +6717,14 @@ QUnit.module('fields', {}, function () { await testUtils.form.clickEdit(form); + // add a new record page 1 (this increases the limit to 4) await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.fields.editInput(form.$('.o_data_row input[name="turtle_foo"]'), 'rainbow dash'); - await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); - await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); + await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); // page 2: 4 records + await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); // page 3: 2 records - assert.containsN(form, 'tr.o_data_row', 3, - "should have 3 data rows on the current page"); + assert.containsN(form, 'tr.o_data_row', 2, + "should have 2 data rows on the current page"); form.destroy(); }); @@ -6885,7 +6910,11 @@ QUnit.module('fields', {}, function () { }); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - + // use of owlCompatibilityExtraNextTick because we have a boolean field (owl) inside the + // x2many, so an update of the x2many requires to wait for 2 animation frames: one + // for the list to be re-rendered (with the boolean field) and one for the control + // panel. + await testUtils.owlCompatibilityExtraNextTick(); await testUtils.dom.click(form.$('.o_field_widget[name=bar] input')); assert.notOk(form.$('.o_field_widget[name=bar] input').prop('checked'), "the checkbox should be unticked"); @@ -8430,13 +8459,16 @@ QUnit.module('fields', {}, function () { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column when the product_id is set"); await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), '', 'keyup'); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsN(form, 'th:not(.o_list_record_remove_header)', 2, "should be 2 columns in the one2many when product_id is not set"); await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column after the value change"); form.destroy(); @@ -8611,13 +8643,16 @@ QUnit.module('fields', {}, function () { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column when the product_id is set"); await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), '', 'keyup'); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsN(form, 'th:not(.o_list_record_remove_header)', 2, "should be 2 columns in the one2many when product_id is not set"); await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column after the value change"); form.destroy(); @@ -9110,6 +9145,12 @@ QUnit.module('fields', {}, function () { // resolve the name_create to trigger the onchange, and the reset of p prom.resolve(); await testUtils.nextTick(); + // use of owlCompatibilityExtraNextTick because we have two sequential updates of the + // fieldX2Many: one because of the onchange, and one because of the click on add a line. + // As an update requires an update of the ControlPanel, which is an Owl Component, and + // waits for it, we need to wait for two animation frames before seeing the new line in + // the DOM + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, '.o_data_row'); assert.hasClass(form.$('.o_data_row'), 'o_selected_row'); @@ -9331,6 +9372,74 @@ QUnit.module('fields', {}, function () { form.destroy(); }); + + QUnit.test('mounted is called only once for x2many control panel', async function (assert) { + // This test could be removed as soon as the field widgets will be converted in owl. + // It comes with a fix for a bug that occurred because in some circonstances, 'mounted' + // is called twice for the x2many control panel. + // Specifically, this occurs when there is 'pad' widget in the form view, because this + // widget does a 'setValue' in its 'start', which thus resets the field x2many. + assert.expect(5); + + const PadLikeWidget = fieldRegistry.get('char').extend({ + start() { + this._setValue("some value"); + } + }); + fieldRegistry.add('pad_like', PadLikeWidget); + + let resolveCP; + const prom = new Promise(r => { + resolveCP = r; + }); + ControlPanel.patch('cp_patch_mock', T => + class extends T { + constructor() { + super(...arguments); + owl.hooks.onMounted(() => { + assert.step('mounted'); + }); + owl.hooks.onWillUnmount(() => { + assert.step('willUnmount'); + }); + } + async update() { + // the issue is a race condition, so we manually delay the update to turn it deterministic + await prom; + super.update(...arguments); + } + } + ); + + const form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: ` +
+ + + + + `, + viewOptions: { + withControlPanel: false, // s.t. there is only one CP: the one of the x2many + }, + }); + + assert.verifySteps(['mounted']); + + resolveCP(); + await testUtils.nextTick(); + + assert.verifySteps([]); + + ControlPanel.unpatch('cp_patch_mock'); + delete fieldRegistry.map.pad_like; + form.destroy(); + + assert.verifySteps(["willUnmount"]); + }); }); }); }); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index a29cfb94895..dd80553379c 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -497,9 +497,11 @@ QUnit.module('relational_fields', { await testUtils.form.clickEdit(form); // add a line (virtual record) await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.owlCompatibilityExtraNextTick(); await testUtils.fields.editInput(form.$('.o_input'), 'pi'); // delete the line above it await testUtils.dom.click(form.$('.o_list_record_remove').first()); + await testUtils.owlCompatibilityExtraNextTick(); // the next line should be displayed below the newly added one assert.strictEqual(form.$('.o_data_row').length, 2, "should have 2 records"); assert.strictEqual(form.$('.o_data_row .o_data_cell:first-child').text(), 'pikawa', @@ -2558,6 +2560,7 @@ QUnit.module('relational_fields', { }); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.owlCompatibilityExtraNextTick(); await testUtils.fields.many2one.searchAndClickItem('product_id', {selector: '.modal', search: 'new record'}); @@ -3093,13 +3096,16 @@ QUnit.module('relational_fields', { await testUtils.form.clickEdit(form); await testUtils.fields.many2one.clickOpenDropdown("product_id"); await testUtils.fields.many2one.clickHighlightedItem("product_id"); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column when the product_id is set"); await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), '', 'keyup'); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsN(form, 'th:not(.o_list_record_remove_header)', 2, "should be 2 columns in the one2many when product_id is not set"); await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column after the value change"); form.destroy(); @@ -3187,13 +3193,16 @@ QUnit.module('relational_fields', { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); await testUtils.fields.many2one.clickHighlightedItem("product_id"); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column when the product_id is set"); await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), '', 'keyup'); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsN(form, 'th:not(.o_list_record_remove_header)', 2, "should be 2 columns in the one2many when product_id is not set"); await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.owlCompatibilityExtraNextTick(); assert.containsOnce(form, 'th:not(.o_list_record_remove_header)', "should be 1 column after the value change"); form.destroy(); @@ -3253,6 +3262,13 @@ QUnit.module('relational_fields', { assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 2, "dropdown is still open"); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + // use of owlCompatibilityExtraNextTick because the x2many field is reset, meaning that + // 1) its list renderer is updated (updateState is called): this is async and as it + // contains a FieldBoolean, which is written in Owl, it completes in the nextAnimationFrame + // 2) when this is done, the control panel is updated: as it is written in owl, this is + // done in the nextAnimationFrame + // -> we need to wait for 2 nextAnimationFrame to ensure that everything is fine + await testUtils.owlCompatibilityExtraNextTick(); assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 0, "dropdown is closed"); var $selectedRow = form.$('.o_field_one2many tr.o_selected_row'); @@ -3352,6 +3368,7 @@ QUnit.module('relational_fields', { which: $.ui.keyCode.TAB, keyCode: $.ui.keyCode.TAB, })); + await testUtils.owlCompatibilityExtraNextTick(); await testUtils.dom.click(document.activeElement); assert.strictEqual(assert.strictEqual(form.$el.find('input[name="turtle_foo"]')[0], document.activeElement, diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index f3b78b3c876..28b172135d2 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -3960,7 +3960,10 @@ QUnit.module('Views', { "Clicking out of a dirty line while editing should trigger a warning modal."); await testUtils.dom.click($('.modal').find('.btn-primary')); - + // use of owlCompatibilityExtraNextTick because there are two sequential updates of the + // control panel (which is written in owl): each of them waits for the next animation frame + // to complete + await testUtils.owlCompatibilityExtraNextTick(); assert.strictEqual(form.$('.o_data_cell').first().text(), "first record", "Value should have been reset to what it was before editing began."); assert.containsOnce(form, '.o_data_row', @@ -4494,6 +4497,7 @@ QUnit.module('Views', { await testUtils.form.clickEdit(form); await testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); + await testUtils.owlCompatibilityExtraNextTick(); assert.strictEqual(form.$('.o_data_row td:first').text(), 'foo changed', "onchange should have been correctly applied on field in o2m list"); @@ -4554,6 +4558,7 @@ QUnit.module('Views', { "the initial value should be the default one"); await testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); + await testUtils.owlCompatibilityExtraNextTick(); assert.strictEqual(form.$('.o_data_row td:first').text(), 'foo changed', "onchange should have been correctly applied on field in o2m list"); @@ -5258,6 +5263,7 @@ QUnit.module('Views', { 'display_name cell should not be visible in edit mode'); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.owlCompatibilityExtraNextTick(); assert.hasClass(form.$('.o_form_view .o_list_view tbody tr:first input[name="display_name"]'), 'oe_read_only', 'display_name input should have oe_read_only class'); @@ -5296,6 +5302,7 @@ QUnit.module('Views', { 'display_name cell should be visible in edit mode'); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.owlCompatibilityExtraNextTick(); assert.hasClass(form.$('.o_form_view .o_list_view tbody tr:first input[name="display_name"]'), 'oe_edit_only', 'display_name input should have oe_edit_only class'); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 256a998028e..c921539a346 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -6161,6 +6161,8 @@ QUnit.module('Views', { // Press 'Tab' -> should get out of the one to many and go to the next field of the form await testUtils.fields.triggerKeydown(form.$('.o_field_widget[name=o2m] .o_selected_row input'), 'tab'); + // use of owlCompatibilityExtraNextTick because the x2many control panel is updated twice + await testUtils.owlCompatibilityExtraNextTick(); assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "the next field should be selected");