Skip to content

Commit

Permalink
[FIX] web: prevent no content helper on delete/archive list records
Browse files Browse the repository at this point in the history
before this commit: when deleting or archiving or unarchive all records of last
page in listview user is displayed with NoContentHelper while we still have
other records in previous page, user should be moved to previous page if all
records of last page are deleted.

after this commit: if user deletes or archives or unarchives all records of
last page in list view then user is moved to previous page.

task-2446911

closes odoo#73540

X-original-commit: 1aeebc9
Signed-off-by: Simon Genin (ges@odoo) <[email protected]>
  • Loading branch information
msh-odoo committed Jul 13, 2021
1 parent 831fb8f commit 868833e
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 8 deletions.
47 changes: 39 additions & 8 deletions addons/web/static/src/js/views/basic/basic_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,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);
Expand Down Expand Up @@ -1302,14 +1307,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;
});
},
/**
Expand All @@ -1331,14 +1349,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;
});
},
/**
Expand Down
41 changes: 41 additions & 0 deletions addons/web/static/tests/views/kanban_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,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:
`<kanban class="o_kanban_test" limit="3">
<templates>
<t t-name="kanban-box">
<div>
<div><a role="menuitem" type="delete" class="dropdown-item">Delete</a></div>
<field name="foo"/>
</div>
</t>
</templates>
</kanban>`,
});

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);

Expand Down
211 changes: 211 additions & 0 deletions addons/web/static/tests/views/list_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3628,6 +3628,70 @@ 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
this.data.foo.fields.active = { string: 'Active', type: 'boolean', default: true };

const actionManager = await createActionManager({
data: this.data,
actions: [{
id: 11,
name: 'Action 11',
res_model: 'foo',
type: 'ir.actions.act_window',
views: [[3, 'list']],
search_view_id: [9, 'search'],
}],
archs: {
'foo,3,list': '<tree><field name="foo"/></tree>',
'foo,9,search': `
<search>
<filter string="Not Bar" name="not bar" domain="[['bar','=',False]]"/>
</search>`,
'bar,false,form': '<form><field name="display_name"/></form>',
},
mockRPC: function (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']]
});
}
return this._super.apply(this, arguments);
},
intercepts: {
do_action: function (ev) {
actionManager.doAction(ev.data.action, {});
},
},
});

await actionManager.doAction(11);

assert.containsNone(actionManager, '.o_cp_action_menus', 'sidebar should be invisible');
assert.containsN(actionManager, 'tbody td.o_list_record_selector', 4, "should have 4 records");

await testUtils.dom.click(actionManager.$('tbody td.o_list_record_selector:first input'));

assert.containsOnce(actionManager, '.o_cp_action_menus', 'sidebar should be visible');

await testUtils.dom.click(actionManager.$('.o_cp_action_menus .o_dropdown_toggler_btn:contains(Action)'));
await testUtils.dom.click(actionManager.$('.o_cp_action_menus a:contains(Archive)'));
assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed');
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
assert.strictEqual($('.modal').length, 2, 'a confirm modal should be displayed');
assert.strictEqual($('.modal:eq(1) .modal-title').text().trim(), 'Archive Action',
"action wizard should have been opened");

actionManager.destroy();
});

QUnit.test('pager (ungrouped and grouped mode), default limit', async function (assert) {
assert.expect(4);

Expand Down Expand Up @@ -8765,6 +8829,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: '<tree limit="3">' +
'<field name="display_name"/>' +
'</tree>',
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_btn: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: '<tree limit="2">' +
'<field name="display_name"/>' +
'</tree>',
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_btn: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: '<tree limit="3"><field name="display_name"/></tree>',
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_btn: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);

Expand Down

0 comments on commit 868833e

Please sign in to comment.