diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 818739c6f48..9f7cbba8696 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -162,7 +162,7 @@ ListRenderer.include({ * This is a specialized version of confirmChange, meant to be called when * the change may have affected more than one line (so, for example, an * onchange which add/remove a few lines in a x2many. This does not occur - * in a normal list view) + * in a normal list view). * * The update is more difficult when other rows could have been changed. We * need to potentially remove some lines, add some other lines, update some @@ -172,6 +172,9 @@ ListRenderer.include({ * except the 'main' one (the row which caused the update), then rerender * every new row and add them before/after the main one. * + * Note that this function assumes that the list isn't grouped, which is + * fine as it's never the case for x2many lists. + * * @param {Object} state * @param {string} id * @param {string[]} fields @@ -226,18 +229,17 @@ ListRenderer.include({ } } - // remove all rows except the one being edited, and insert rows - // of the re-rendered body before and after it + // remove all data rows except the one being edited, and insert + // data rows of the re-rendered body before and after it var $editedRow = self._getRow(id); - $editedRow.nextAll().remove(); - $editedRow.prevAll().remove(); + $editedRow.nextAll('.o_data_row').remove(); + $editedRow.prevAll('.o_data_row').remove(); var $newRow = $newBody.find('.o_data_row[data-id="' + id + '"]'); - var $tbody = self.$('tbody'); - $newRow.prevAll().each(function (i, prevRow) { - $tbody.prepend($(prevRow)); + $newRow.prevAll('.o_data_row').get().reverse().forEach(function (row) { + $(row).insertBefore($editedRow); }); - $newRow.nextAll().each(function (i, nextRow) { - $tbody.append($(nextRow)); + $newRow.nextAll('.o_data_row').get().reverse().forEach(function (row) { + $(row).insertAfter($editedRow); }); if (self.currentRow !== null) { 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 85975d99231..0a96e07310e 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 @@ -8290,6 +8290,65 @@ QUnit.module('fields', {}, function () { form.destroy(); }); + + QUnit.test('one2many editable list: edit and click on add a line', async function (assert) { + assert.expect(9); + + this.data.turtle.onchanges = { + turtle_int: function () {}, + }; + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + assert.step('onchange'); + } + return this._super.apply(this, arguments); + }, + // in this test, we want to to accurately mock what really happens, that is, input + // fields only trigger their changes on 'change' event, not on 'input' + fieldDebounce: 100000, + viewOptions: { + mode: 'edit', + }, + }); + + assert.containsOnce(form, '.o_data_row'); + + // edit first row + await testUtils.dom.click(form.$('.o_data_row:first .o_data_cell:first')); + assert.hasClass(form.$('.o_data_row:first'), 'o_selected_row'); + await testUtils.fields.editInput(form.$('.o_selected_row input[name=turtle_int]'), '44'); + + assert.verifySteps([]); + // simulate a long click on 'Add a line' (mousedown [delay] mouseup and click events) + var $addLine = form.$('.o_field_x2many_list_row_add a'); + testUtils.dom.triggerEvents($addLine, 'mousedown'); + // mousedown is supposed to trigger the change event on the edited input, but it doesn't + // in the test environment, for an unknown reason, so we trigger it manually to reproduce + // what really happens + testUtils.dom.triggerEvents(form.$('.o_selected_row input[name=turtle_int]'), 'change'); + await testUtils.nextTick(); + + // release the click + await testUtils.dom.triggerEvents($addLine, ['mouseup', 'click']); + assert.verifySteps(['onchange', 'onchange']); + + assert.containsN(form, '.o_data_row', 2); + assert.strictEqual(form.$('.o_data_row:first').text(), '44'); + assert.hasClass(form.$('.o_data_row:nth(1)'), 'o_selected_row'); + + form.destroy(); + }); }); }); });