diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index 94609544b10..5daa33125a7 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -996,11 +996,18 @@ var ListRenderer = BasicRenderer.extend({ * @private */ _updateSelection: function () { - var $selectedRows = this.$('tbody .o_list_record_selector input:checked') - .closest('tr'); - this.selection = _.map($selectedRows, function (row) { - return $(row).data('id'); + this.selection = []; + var self = this; + var $inputs = this.$('tbody .o_list_record_selector input:visible:not(:disabled)'); + var allChecked = $inputs.length > 0; + $inputs.each(function (index, input) { + if (input.checked) { + self.selection.push($(input).closest('tr').data('id')); + } else { + allChecked = false; + } }); + this.$('thead .o_list_record_selector input').prop('checked', allChecked); this.trigger_up('selection_changed', { selection: this.selection }); this._updateFooter(); }, @@ -1186,9 +1193,6 @@ var ListRenderer = BasicRenderer.extend({ _onSelectRecord: function (ev) { ev.stopPropagation(); this._updateSelection(); - if (!$(ev.currentTarget).find('input').prop('checked')) { - this.$('thead .o_list_record_selector input').prop('checked', false); - } }, /** * @private @@ -1209,7 +1213,8 @@ var ListRenderer = BasicRenderer.extend({ if (group.count) { this.trigger_up('toggle_group', { group: group, - onSuccess: function () { + onSuccess: () => { + this._updateSelection(); // Refocus the header after re-render unless the user // already focused something else by now if (document.activeElement.tagName === 'BODY') { diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 27305720fbb..a62fd322f0a 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -1143,6 +1143,49 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('head selector is toggled by the other selectors', async function (assert) { + assert.expect(6); + + const list = await createView({ + arch: '', + data: this.data, + groupBy: ['bar'], + model: 'foo', + View: ListView, + }); + + assert.ok(!list.$('thead .o_list_record_selector input')[0].checked, + "Head selector should be unchecked"); + + await testUtils.dom.click(list.$('.o_group_header:first()')); + await testUtils.dom.click(list.$('thead .o_list_record_selector input')); + + assert.containsN(list, 'tbody .o_list_record_selector input:checked', + 3, "All visible checkboxes should be checked"); + + await testUtils.dom.click(list.$('.o_group_header:last()')); + + assert.ok(!list.$('thead .o_list_record_selector input')[0].checked, + "Head selector should be unchecked"); + + await testUtils.dom.click(list.$('tbody .o_list_record_selector input:last()')); + + assert.ok(list.$('thead .o_list_record_selector input')[0].checked, + "Head selector should be checked"); + + await testUtils.dom.click(list.$('tbody .o_list_record_selector:first() input')); + + assert.ok(!list.$('thead .o_list_record_selector input')[0].checked, + "Head selector should be unchecked"); + + await testUtils.dom.click(list.$('.o_group_header:first()')); + + assert.ok(list.$('thead .o_list_record_selector input')[0].checked, + "Head selector should be checked"); + + list.destroy(); + }); + QUnit.test('selection is reset on reload', async function (assert) { assert.expect(5);