diff --git a/addons/web/static/src/legacy/js/views/basic/basic_model.js b/addons/web/static/src/legacy/js/views/basic/basic_model.js
index 609df5652be..416c5055ce0 100644
--- a/addons/web/static/src/legacy/js/views/basic/basic_model.js
+++ b/addons/web/static/src/legacy/js/views/basic/basic_model.js
@@ -382,6 +382,11 @@ var BasicModel = AbstractModel.extend({
if (parent && parent.type === 'list') {
parent.data = _.without(parent.data, record.id);
delete self.localData[record.id];
+ // Check if we are on last page and all records are deleted from current
+ // page i.e. if there is no state.data.length then go to previous page
+ if (!parent.data.length && parent.offset > 0) {
+ parent.offset = Math.max(parent.offset - parent.limit, 0);
+ }
} else {
record.res_ids.splice(record.offset, 1);
record.offset = Math.min(record.offset, record.res_ids.length - 1);
@@ -1330,14 +1335,27 @@ var BasicModel = AbstractModel.extend({
// optionally clear the DataManager's cache
self._invalidateCache(parent);
if (!_.isEmpty(action)) {
- return self.do_action(action, {
- on_close: function () {
- return self.trigger_up('reload');
- }
+ return new Promise(function (resolve, reject) {
+ self.do_action(action, {
+ on_close: function (result) {
+ return self.trigger_up('reload', {
+ onSuccess: resolve,
+ });
+ }
+ });
});
} else {
return self.reload(parentID);
}
+ }).then(function (datapoint) {
+ // if there are no records to display and we are not on first page(we check it
+ // by checking offset is greater than limit i.e. we are not on first page)
+ // reason for adding logic after reload to make sure there is no records after operation
+ if (parent && parent.type === 'list' && !parent.data.length && parent.offset > 0) {
+ parent.offset = Math.max(parent.offset - parent.limit, 0);
+ return self.reload(parentID);
+ }
+ return datapoint;
});
},
/**
@@ -1359,14 +1377,27 @@ var BasicModel = AbstractModel.extend({
// optionally clear the DataManager's cache
self._invalidateCache(parent);
if (!_.isEmpty(action)) {
- return self.do_action(action, {
- on_close: function () {
- return self.trigger_up('reload');
- }
+ return new Promise(function (resolve, reject) {
+ self.do_action(action, {
+ on_close: function () {
+ return self.trigger_up('reload', {
+ onSuccess: resolve,
+ });
+ }
+ });
});
} else {
return self.reload(parentID);
}
+ }).then(function (datapoint) {
+ // if there are no records to display and we are not on first page(we check it
+ // by checking offset is greater than limit i.e. we are not on first page)
+ // reason for adding logic after reload to make sure there is no records after operation
+ if (parent && parent.type === 'list' && !parent.data.length && parent.offset > 0) {
+ parent.offset = Math.max(parent.offset - parent.limit, 0);
+ return self.reload(parentID);
+ }
+ return datapoint;
});
},
/**
diff --git a/addons/web/static/tests/legacy/views/kanban_tests.js b/addons/web/static/tests/legacy/views/kanban_tests.js
index 0bfbfc3599a..0101e1e1daa 100644
--- a/addons/web/static/tests/legacy/views/kanban_tests.js
+++ b/addons/web/static/tests/legacy/views/kanban_tests.js
@@ -444,6 +444,47 @@ QUnit.module('Views', {
kanban.destroy();
});
+ QUnit.test('pager, ungrouped, deleting all records from last page should move to previous page', async function (assert) {
+ assert.expect(5);
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ `
+
+
+
+
+
+ `,
+ });
+
+ assert.strictEqual(cpHelpers.getPagerValue(kanban), "1-3",
+ "should have 3 records on current page");
+ assert.strictEqual(cpHelpers.getPagerSize(kanban), "4",
+ "should have 4 records");
+
+ // move to next page
+ await cpHelpers.pagerNext(kanban);
+ assert.strictEqual(cpHelpers.getPagerValue(kanban), "4-4",
+ "should be on second page");
+
+ // delete a record
+ await testUtils.dom.click(kanban.$('.o_kanban_record:first a:first'));
+ await testUtils.dom.click($('.modal-footer button:first'));
+ assert.strictEqual(cpHelpers.getPagerValue(kanban), "1-3",
+ "should have 1 page only");
+ assert.strictEqual(cpHelpers.getPagerSize(kanban), "3",
+ "should have 4 records");
+
+ kanban.destroy();
+ });
+
QUnit.test('create in grouped on m2o', async function (assert) {
assert.expect(5);
diff --git a/addons/web/static/tests/legacy/views/list_tests.js b/addons/web/static/tests/legacy/views/list_tests.js
index 0aa773b1b72..4691da872aa 100644
--- a/addons/web/static/tests/legacy/views/list_tests.js
+++ b/addons/web/static/tests/legacy/views/list_tests.js
@@ -25,7 +25,7 @@ var _t = core._t;
const cpHelpers = testUtils.controlPanel;
var createView = testUtils.createView;
-const { legacyExtraNextTick } = require("@web/../tests/helpers/utils");
+const { click, legacyExtraNextTick } = require("@web/../tests/helpers/utils");
const { createWebClient, doAction, loadState } = require('@web/../tests/webclient/helpers');
let serverData;
@@ -3698,6 +3698,67 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('archive/unarchive handles returned action', async function (assert) {
+ assert.expect(6);
+
+ // add active field on foo model and make all records active
+ serverData.models.foo.fields.active = { string: 'Active', type: 'boolean', default: true };
+
+ serverData.actions = {
+ 11: {
+ id: 11,
+ name: 'Action 11',
+ res_model: 'foo',
+ type: 'ir.actions.act_window',
+ views: [[3, 'list']],
+ search_view_id: [9, 'search'],
+ },
+ };
+ serverData.views = {
+ 'foo,3,list': '',
+ 'foo,9,search': `
+
+
+ `,
+ 'bar,false,form': '
',
+ };
+
+ const mockRPC = (route, args) => {
+ if (route === '/web/dataset/call_kw/foo/action_archive') {
+ return Promise.resolve({
+ 'type': 'ir.actions.act_window',
+ 'name': 'Archive Action',
+ 'res_model': 'bar',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'views': [[false, 'form']]
+ });
+ }
+ };
+
+ const webClient = await createWebClient({ serverData, mockRPC });
+ await doAction(webClient, 11);
+
+ assert.containsNone(webClient, '.o_cp_action_menus', 'sidebar should be invisible');
+ assert.containsN(webClient, 'tbody td.o_list_record_selector', 4, "should have 4 records");
+
+ await click(webClient.el.querySelector('tbody td.o_list_record_selector input'));
+ assert.containsOnce(webClient, '.o_cp_action_menus', 'sidebar should be visible');
+
+ await click(webClient.el.querySelector('.o_cp_action_menus .o_dropdown_toggler'));
+ const archiveItem = [...webClient.el.querySelectorAll('.o_cp_action_menus .o_dropdown_menu li > a')]
+ .filter((elem) => elem.textContent === 'Archive');
+ await click(archiveItem[0]);
+ assert.strictEqual(document.querySelectorAll('.modal').length, 1,
+ 'a confirm modal should be displayed');
+
+ await click(document.querySelector('.modal .modal-footer .btn-primary'));
+ assert.strictEqual(document.querySelectorAll('.modal').length, 1,
+ 'archive action dialog should be displayed');
+ assert.strictEqual(document.querySelector('.modal .modal-title').textContent.trim(), 'Archive Action',
+ "action wizard should have been opened");
+ });
+
QUnit.test('pager (ungrouped and grouped mode), default limit', async function (assert) {
assert.expect(4);
@@ -9159,6 +9220,153 @@ QUnit.module('Views', {
assert.verifySteps(['clear_cache']); // triggered by the test environment on destroy
});
+ QUnit.test('list view move to previous page when all records from last page deleted', async function (assert) {
+ assert.expect(5);
+
+ let checkSearchRead = false;
+ const list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '' +
+ '' +
+ '',
+ mockRPC: function (route, args) {
+ if (checkSearchRead && route === '/web/dataset/search_read') {
+ assert.strictEqual(args.limit, 3, "limit should 3");
+ assert.notOk(args.offset, "offset should not be passed i.e. offset 0 by default");
+ }
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: {
+ hasActionMenus: true,
+ },
+ });
+
+ assert.strictEqual(list.$('.o_pager_counter').text().trim(), '1-3 / 4',
+ "should have 2 pages and current page should be first page");
+
+ // move to next page
+ await testUtils.dom.click(list.$('.o_pager_next'));
+ assert.strictEqual(list.$('.o_pager_counter').text().trim(), '4-4 / 4',
+ "should be on second page");
+
+ // delete a record
+ await testUtils.dom.click(list.$('tbody .o_data_row:first td.o_list_record_selector:first input'));
+ checkSearchRead = true;
+ await testUtils.dom.click(list.$('.o_dropdown_toggler:contains(Action)'));
+ await testUtils.dom.click(list.$('a:contains(Delete)'));
+ await testUtils.dom.click($('body .modal button span:contains(Ok)'));
+ assert.strictEqual(list.$('.o_pager_counter').text().trim(), '1-3 / 3',
+ "should have 1 page only");
+
+ list.destroy();
+ });
+
+ QUnit.test('grouped list view move to previous page of group when all records from last page deleted', async function (assert) {
+ assert.expect(7);
+
+ let checkSearchRead = false;
+ const list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '' +
+ '' +
+ '',
+ mockRPC: function (route, args) {
+ if (checkSearchRead && route === '/web/dataset/search_read') {
+ assert.strictEqual(args.limit, 2, "limit should 2");
+ assert.notOk(args.offset, "offset should not be passed i.e. offset 0 by default");
+ }
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: {
+ hasActionMenus: true,
+ },
+ groupBy: ['m2o'],
+ });
+
+ assert.strictEqual(list.$('th:contains(Value 1 (3))').length, 1,
+ "Value 1 should contain 3 records");
+ assert.strictEqual(list.$('th:contains(Value 2 (1))').length, 1,
+ "Value 2 should contain 1 record");
+
+ await testUtils.dom.click(list.$('th.o_group_name:nth(0)'));
+ assert.strictEqual(list.$('th.o_group_name:eq(0) .o_pager_counter').text().trim(), '1-2 / 3',
+ "should have 2 pages and current page should be first page");
+
+ // move to next page
+ await testUtils.dom.click(list.$('.o_group_header .o_pager_next'));
+ assert.strictEqual(list.$('th.o_group_name:eq(0) .o_pager_counter').text().trim(), '3-3 / 3',
+ "should be on second page");
+
+ // delete a record
+ await testUtils.dom.click(list.$('tbody .o_data_row:first td.o_list_record_selector:first input'));
+ checkSearchRead = true;
+ await testUtils.dom.click(list.$('.o_dropdown_toggler:contains(Action)'));
+ await testUtils.dom.click(list.$('a:contains(Delete)'));
+ await testUtils.dom.click($('body .modal button span:contains(Ok)'));
+
+ assert.strictEqual(list.$('th.o_group_name:eq(0) .o_pager_counter').text().trim(), '',
+ "should be on first page now");
+
+ list.destroy();
+ });
+
+ QUnit.test('list view move to previous page when all records from last page archive/unarchived', async function (assert) {
+ assert.expect(9);
+
+ // add active field on foo model and make all records active
+ this.data.foo.fields.active = { string: 'Active', type: 'boolean', default: true };
+
+ const list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '',
+ viewOptions: {
+ hasActionMenus: true,
+ },
+ mockRPC: function (route) {
+ if (route === '/web/dataset/call_kw/foo/action_archive') {
+ this.data.foo.records[3].active = false;
+ return Promise.resolve();
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(list.$('.o_pager_counter').text().trim(), '1-3 / 4',
+ "should have 2 pages and current page should be first page");
+ assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 3,
+ "should have 3 records");
+
+ // move to next page
+ await testUtils.dom.click(list.$('.o_pager_next'));
+ assert.strictEqual(list.$('.o_pager_counter').text().trim(), '4-4 / 4',
+ "should be on second page");
+ assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 1,
+ "should have 1 records");
+ assert.containsNone(list, '.o_cp_action_menus', 'sidebar should not be available');
+
+ await testUtils.dom.click(list.$('tbody .o_data_row:first td.o_list_record_selector:first input'));
+ assert.containsOnce(list, '.o_cp_action_menus', 'sidebar should be available');
+
+ // archive all records of current page
+ await testUtils.dom.click(list.$('.o_cp_action_menus .o_dropdown_toggler:contains(Action)'));
+ await testUtils.dom.click(list.$('a:contains(Archive)'));
+ assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed');
+
+ await testUtils.dom.click($('body .modal button span:contains(Ok)'));
+ assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 3,
+ "should have 3 records");
+ assert.strictEqual(list.$('.o_pager_counter').text().trim(), '1-3 / 3',
+ "should have 1 page only");
+
+ list.destroy();
+ });
+
QUnit.test('list should ask to scroll to top on page changes', async function (assert) {
assert.expect(10);