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 ed5af4c96bb..1fc6b7006b9 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -877,11 +877,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(); }, @@ -1012,9 +1019,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 @@ -1031,11 +1035,13 @@ var ListRenderer = BasicRenderer.extend({ _onToggleGroup: function (ev) { ev.preventDefault(); ev.stopPropagation(); + var self = this; var group = $(ev.currentTarget).closest('tr').data('group'); if (group.count) { this.trigger_up('toggle_group', { group: group, onSuccess: function() { + self._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 4fea02ae3e9..28eff515ffa 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -1073,6 +1073,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);