From d971e4f4c7dfe0bba60828948174a399e4ebc4d3 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 4 Apr 2019 08:24:52 +0000 Subject: [PATCH] [FIX] web: x2many list: edit and long click 'Add a line' Assume an x2many editable list where the user edits a record (input field), and then clicks on 'Add a line' (and keeps it pressed for a while, before releasing the click). When the 'mousedown' event occurs, the 'change' event is triggered on the edited field, and (once potential onchanges have been applied), the list is updated, i.e. all rows except for the one being edited a re-rendered. However, the 'Add a line' handler listens to the 'click' event, which never occurs if we release the click after the rows have been re-rendered. This is an issue for every rows, not only 'Add a line', but for data rows, the issue is already present in former versions, and is not that easy to fix). The 'Add a line' case is a consequence of the recent work on list views (see 234d92c4f0c0), so we can fix it independently. Task 1915702 --- .../js/views/list/list_editable_renderer.js | 22 +++---- .../relational_fields/field_one2many_tests.js | 59 +++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) 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(); + }); }); }); });