' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
// the lazy option is important, so the server can fill in
// the empty groups
assert.ok(args.kwargs.lazy, "should use lazy read_group");
}
return this._super(route, args);
},
});
assert.hasClass(kanban.$('.o_legacy_kanban_view'), 'o_kanban_grouped');
assert.hasClass(kanban.$('.o_legacy_kanban_view'), 'o_kanban_test');
assert.containsN(kanban, '.o_kanban_group', 2);
assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record');
assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3);
// check available actions in kanban header's config dropdown
assert.containsOnce(kanban, '.o_kanban_header:first .o_kanban_config .o_kanban_toggle_fold');
assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_edit');
assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_delete');
assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_archive_records');
assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_unarchive_records');
// the next line makes sure that reload works properly. It looks useless,
// but it actually test that a grouped local record can be reloaded without
// changing its result.
await kanban.reload(kanban);
assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3);
kanban.destroy();
});
QUnit.test('basic grouped rendering with active field (archivable by default)', async function (assert) {
// var done = assert.async();
assert.expect(9);
// add active field on partner model and make all records active
this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
var envIDs = [1, 2, 3, 4]; // the ids that should be in the environment during this test
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/action_archive') {
var partnerIDS = args.args[0];
var records = this.data.partner.records
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
return record.id === partnerID;
}).active = false;
})
this.data.partner.records[0].active;
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
// check archive/restore all actions in kanban header's config dropdown
assert.containsOnce(kanban, '.o_kanban_header:first .o_kanban_config .o_column_archive_records');
assert.containsOnce(kanban, '.o_kanban_header:first .o_kanban_config .o_column_unarchive_records');
assert.deepEqual(kanban.exportState().resIds, envIDs);
// archive the records of the first column
assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3);
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records'));
assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed");
await testUtils.modal.clickButton('Cancel');
assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3, "still last column should contain 3 records");
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records'));
assert.ok($('.modal').length, 'a confirm modal should be displayed');
await testUtils.modal.clickButton('Ok');
assert.containsNone(kanban, '.o_kanban_group:last .o_kanban_record', "last column should not contain any records");
envIDs = [4];
assert.deepEqual(kanban.exportState().resIds, envIDs);
kanban.destroy();
});
QUnit.test('basic grouped rendering with active field and archive enabled (archivable true)', async function (assert) {
assert.expect(7);
// add active field on partner model and make all records active
this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/action_archive') {
var partnerIDS = args.args[0];
var records = this.data.partner.records
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
return record.id === partnerID;
}).active = false;
})
this.data.partner.records[0].active;
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
// check archive/restore all actions in kanban header's config dropdown
assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_column_archive_records').length, "should be able to archive all the records");
assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_column_unarchive_records').length, "should be able to restore all the records");
// archive the records of the first column
assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3,
"last column should contain 3 records");
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records'));
assert.ok($('.modal').length, 'a confirm modal should be displayed');
await testUtils.modal.clickButton('Cancel'); // Click on 'Cancel'
assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3, "still last column should contain 3 records");
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records'));
assert.ok($('.modal').length, 'a confirm modal should be displayed');
await testUtils.modal.clickButton('Ok'); // Click on 'Ok'
assert.containsNone(kanban, '.o_kanban_group:last .o_kanban_record', "last column should not contain any records");
kanban.destroy();
});
QUnit.test('basic grouped rendering with active field and hidden archive buttons (archivable false)', async function (assert) {
assert.expect(2);
// add active field on partner model and make all records active
this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
// check archive/restore all actions in kanban header's config dropdown
assert.strictEqual(
kanban.$('.o_kanban_header:first .o_kanban_config .o_column_archive_records').length, 0,
"should not be able to archive all the records");
assert.strictEqual(
kanban.$('.o_kanban_header:first .o_kanban_config .o_column_unarchive_records').length, 0,
"should not be able to restore all the records");
kanban.destroy();
});
QUnit.test("m2m grouped rendering with active field and archive enabled (archivable true)", async function (assert) {
assert.expect(7);
// add active field on partner model and make all records active
this.data.partner.fields.active = { string: 'Active', type: 'char', default: true };
// more many2many data
this.data.partner.records[0].category_ids = [6, 7];
this.data.partner.records[3].foo = "blork";
this.data.partner.records[3].category_ids = [];
const kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch: `
`,
groupBy: ["category_ids"],
});
assert.containsN(kanban, ".o_kanban_group", 3);
assert.containsOnce(kanban, ".o_kanban_group:nth-child(1) .o_kanban_record");
assert.containsN(kanban, ".o_kanban_group:nth-child(2) .o_kanban_record", 2);
assert.containsN(kanban, ".o_kanban_group:nth-child(3) .o_kanban_record", 2);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group")].map(el => el.innerText.replace(/\s/g, " ")),
["None blork", "gold yopblip", "silver yopgnap"],
);
// check archive/restore all actions in kanban header's config dropdown
// despite the fact that the kanban view is configured to be archivable,
// the actions should not be there as it is grouped by an m2m field.
assert.containsNone(kanban, ".o_kanban_header .o_kanban_config .o_column_archive_records", "should not be able to archive all the records");
assert.containsNone(kanban, ".o_kanban_header .o_kanban_config .o_column_unarchive_records", "should not be able to unarchive all the records");
kanban.destroy();
});
QUnit.test('context can be used in kanban template', async function (assert) {
assert.expect(2);
var form = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'' +
'' +
'
' +
'' +
'' +
'',
context: {some_key: 1},
domain: [['id', '=', 1]],
});
assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1,
"there should be one record");
assert.strictEqual(form.$('.o_kanban_record span:contains(yop)').length, 1,
"condition in the kanban template should have been correctly evaluated");
form.destroy();
});
QUnit.test('pager should be hidden in grouped mode', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
mockRPC: function (route, args) {
assert.strictEqual(args.limit, 40, "default limit should be 40 in Kanban");
return this._super.apply(this, arguments);
},
});
assert.containsOnce(kanban, '.o_pager');
assert.strictEqual(testUtils.controlPanel.getPagerSize(kanban), "4", "pager's size should be 4");
kanban.destroy();
});
QUnit.test('pager, ungrouped, with limit given in options', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'',
mockRPC: function (route, args) {
assert.strictEqual(args.limit, 2, "limit should be 2");
return this._super.apply(this, arguments);
},
viewOptions: {
limit: 2,
},
});
assert.strictEqual(testUtils.controlPanel.getPagerValue(kanban), "1-2", "pager's limit should be 2");
assert.strictEqual(testUtils.controlPanel.getPagerSize(kanban), "4", "pager's size should be 4");
kanban.destroy();
});
QUnit.test('pager, ungrouped, with limit set on arch and given in options', async function (assert) {
assert.expect(3);
// the limit given in the arch should take the priority over the one given in options
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'',
mockRPC: function (route, args) {
assert.strictEqual(args.limit, 3, "limit should be 3");
return this._super.apply(this, arguments);
},
viewOptions: {
limit: 2,
},
});
assert.strictEqual(testUtils.controlPanel.getPagerValue(kanban), "1-3", "pager's limit should be 3");
assert.strictEqual(testUtils.controlPanel.getPagerSize(kanban), "4", "pager's size should be 4");
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(testUtils.controlPanel.getPagerValue(kanban), "1-3",
"should have 3 records on current page");
assert.strictEqual(testUtils.controlPanel.getPagerSize(kanban), "4",
"should have 4 records");
// move to next page
await testUtils.controlPanel.pagerNext(kanban);
assert.strictEqual(testUtils.controlPanel.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(testUtils.controlPanel.getPagerValue(kanban), "1-3",
"should have 1 page only");
assert.strictEqual(testUtils.controlPanel.getPagerSize(kanban), "3",
"should have 4 records");
kanban.destroy();
});
QUnit.test('create in grouped on m2o', async function (assert) {
assert.expect(5);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.hasClass(kanban.$('.o_legacy_kanban_view'),'ui-sortable',
"columns are sortable when grouped by a m2o field");
assert.hasClass(kanban.$buttons.find('.o-kanban-button-new'),'btn-primary',
"'create' button should be btn-primary for grouped kanban with at least one column");
assert.hasClass(kanban.$('.o_legacy_kanban_view > div:last'),'o_column_quick_create',
"column quick create should be enabled when grouped by a many2one field)");
await testUtils.kanban.clickCreate(kanban); // Click on 'Create'
assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create',
"clicking on create should open the quick_create in the first column");
assert.ok(kanban.$('span.o_column_title:contains(hello)').length,
"should have a column title with a value from the many2one");
kanban.destroy();
});
QUnit.test('create in grouped on char', async function (assert) {
assert.expect(4);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['foo'],
});
assert.doesNotHaveClass(kanban.$('.o_legacy_kanban_view'), 'ui-sortable',
"columns aren't sortable when not grouped by a m2o field");
assert.containsN(kanban, '.o_kanban_group', 3, "should have " + 3 + " columns");
assert.strictEqual(kanban.$('.o_kanban_group:first() .o_column_title').text(), "yop",
"'yop' column should be the first column");
assert.doesNotHaveClass(kanban.$('.o_legacy_kanban_view > div:last'), 'o_column_quick_create',
"column quick create should be disabled when not grouped by a many2one field)");
kanban.destroy();
});
QUnit.test('prevent deletion when grouped by many2many field', async function (assert) {
assert.expect(2);
this.data.partner.records[0].category_ids = [6, 7];
this.data.partner.records[3].category_ids = [7];
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
delete
`,
groupBy: ['category_ids'],
});
assert.containsNone(kanban, '.thisisdeletable', "records should not be deletable");
await kanban.reload({ groupBy: ['foo'] });
assert.containsN(kanban, '.thisisdeletable', 4, "records should be deletable");
kanban.destroy();
});
QUnit.test('quick create record without quick_create_view', async function (assert) {
assert.expect(16);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'name_create') {
assert.strictEqual(args.args[0], 'new partner',
"should send the correct value");
}
return this._super.apply(this, arguments);
},
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain one record");
// click on 'Create' -> should open the quick create in the first column
await testUtils.kanban.clickCreate(kanban);
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1,
"should have a quick create element in the first column");
assert.strictEqual($quickCreate.find('.o_legacy_form_view.o_xxs_form_view').length, 1,
"should have rendered an XXS form view");
assert.strictEqual($quickCreate.find('input').length, 1,
"should have only one input");
assert.hasClass($quickCreate.find('input'), 'o_required_modifier',
"the field should be required");
assert.strictEqual($quickCreate.find('input[placeholder=Title]').length, 1,
"input placeholder should be 'Title'");
// fill the quick create and validate
await testUtils.fields.editInput($quickCreate.find('input'), 'new partner');
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should contain two records");
assert.verifySteps([
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'onchange', // quick create
'name_create', // should perform a name_create to create the record
'read', // read the created record
'onchange', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record with quick_create_view', async function (assert) {
assert.expect(19);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '
',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'create') {
assert.deepEqual(args.args[0], {
foo: 'new partner',
int_field: 4,
state: 'def',
}, "should send the correct values");
}
return this._super.apply(this, arguments);
},
});
assert.containsOnce(kanban, '.o_control_panel', 'should have one control panel');
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain one record");
// click on 'Create' -> should open the quick create in the first column
await testUtils.kanban.clickCreate(kanban);
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1,
"should have a quick create element in the first column");
assert.strictEqual($quickCreate.find('.o_legacy_form_view.o_xxs_form_view').length, 1,
"should have rendered an XXS form view");
assert.containsOnce(kanban, '.o_control_panel', 'should not have instantiated an extra control panel');
assert.strictEqual($quickCreate.find('input').length, 2,
"should have two inputs");
assert.strictEqual($quickCreate.find('.o_field_widget').length, 3,
"should have rendered three widgets");
// fill the quick create and validate
await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner');
await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4');
await testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first'));
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should contain two records");
assert.verifySteps([
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'get_views', // form view in quick create
'onchange', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'get_views', // form view in quick create (is actually in cache)
'onchange', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record in grouped on m2o (no quick_create_view)', async function (assert) {
assert.expect(12);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'name_create') {
assert.strictEqual(args.args[0], 'new partner',
"should send the correct value");
assert.deepEqual(args.kwargs.context, {
default_product_id: 3,
default_qux: 2.5,
}, "should send the correct context");
}
return this._super.apply(this, arguments);
},
viewOptions: {
context: {default_qux: 2.5},
},
});
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should contain two records");
// click on 'Create', fill the quick create and validate
await testUtils.kanban.clickCreate(kanban);
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'new partner');
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3,
"first column should contain three records");
assert.verifySteps([
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'onchange', // quick create
'name_create', // should perform a name_create to create the record
'read', // read the created record
'onchange', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record in grouped on m2o (with quick_create_view)', async function (assert) {
assert.expect(14);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['product_id'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'create') {
assert.deepEqual(args.args[0], {
foo: 'new partner',
int_field: 4,
state: 'def',
}, "should send the correct values");
assert.deepEqual(args.kwargs.context, {
default_product_id: 3,
default_qux: 2.5,
}, "should send the correct context");
}
return this._super.apply(this, arguments);
},
viewOptions: {
context: {default_qux: 2.5},
},
});
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should contain two records");
// click on 'Create', fill the quick create and validate
await testUtils.kanban.clickCreate(kanban);
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner');
await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4');
await testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first'));
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3,
"first column should contain three records");
assert.verifySteps([
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'get_views', // form view in quick create
'onchange', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'get_views', // form view in quick create (is actually in cache)
'onchange', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record with default values and onchanges', async function (assert) {
assert.expect(10);
this.data.partner.fields.int_field.default = 4;
this.data.partner.onchanges = {
foo: function (obj) {
if (obj.foo) {
obj.int_field = 8;
}
},
};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
});
// click on 'Create' -> should open the quick create in the first column
await testUtils.kanban.clickCreate(kanban);
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1,
"should have a quick create element in the first column");
assert.strictEqual($quickCreate.find('.o_field_widget[name=int_field]').val(), '4',
"default value should be set");
// fill the 'foo' field -> should trigger the onchange
await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner');
assert.strictEqual($quickCreate.find('.o_field_widget[name=int_field]').val(), '8',
"onchange should have been triggered");
assert.verifySteps([
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'get_views', // form view in quick create
'onchange', // quick create
'onchange', // onchange due to 'foo' field change
]);
kanban.destroy();
});
QUnit.test('quick create record with quick_create_view: modifiers', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
});
// create a new record
await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_quick_add'));
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.hasClass($quickCreate.find('.o_field_widget[name=foo]'),'o_required_modifier',
"foo field should be required");
assert.hasClass($quickCreate.find('.o_field_widget[name=int_field]'),'o_invisible_modifier',
"int_field should be invisible");
// fill 'foo' field
await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner');
assert.doesNotHaveClass($quickCreate.find('.o_field_widget[name=int_field]'), 'o_invisible_modifier',
"int_field should now be visible");
kanban.destroy();
});
QUnit.test('quick create record and change state in grouped mode', async function (assert) {
assert.expect(1);
this.data.partner.fields.kanban_state = {
string: "Kanban State",
type: "selection",
selection: [["normal", "Grey"], ["done", "Green"], ["blocked", "Red"]],
};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['foo'],
});
// Quick create kanban record
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
var $quickAdd = kanban.$('.o_kanban_quick_create');
$quickAdd.find('.o_input').val('Test');
await testUtils.dom.click($quickAdd.find('.o_kanban_add'));
// Select state in kanban
await testUtils.dom.click(kanban.$('.o_status').first());
await testUtils.dom.click(kanban.$('.o_selection .dropdown-item:first'));
assert.hasClass(kanban.$('.o_status').first(),'o_status_green',
"Kanban state should be done (Green)");
kanban.destroy();
});
QUnit.test('window resize should not change quick create form size', async function (assert) {
assert.expect(2);
testUtils.mock.patch(FormRenderer, {
start: function () {
this._super.apply(this, arguments);
window.addEventListener("resize", this._applyFormSizeClass.bind(this));
},
});
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
// click to add an element and cancel the quick creation by pressing ESC
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_header .o_kanban_quick_add i'));
const quickCreate = kanban.el.querySelector('.o_kanban_quick_create');
assert.hasClass(quickCreate.querySelector('.o_legacy_form_view'), "o_xxs_form_view");
// trigger window resize explicitly to call _applyFormSizeClass
window.dispatchEvent(new Event('resize'));
assert.hasClass(quickCreate.querySelector('.o_legacy_form_view'), 'o_xxs_form_view');
kanban.destroy();
testUtils.mock.unpatch(FormRenderer);
});
QUnit.test('quick create record: cancel and validate without using the buttons', async function (assert) {
assert.expect(9);
var nbRecords = 4;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
assert.strictEqual(kanban.exportState().resIds.length, nbRecords);
// click to add an element and cancel the quick creation by pressing ESC
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
var $quickCreate = kanban.$('.o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1, "should have a quick create element");
$quickCreate.find('input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.containsNone(kanban, '.o_kanban_quick_create',
"should have destroyed the quick create element");
// click to add and element and click outside, should cancel the quick creation
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first'));
assert.containsNone(kanban, '.o_kanban_quick_create',
"the quick create should be destroyed when the user clicks outside");
// click to input and drag the mouse outside, should not cancel the quick creation
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
$quickCreate = kanban.$('.o_kanban_quick_create');
await testUtils.dom.triggerMouseEvent($quickCreate.find('input'), 'mousedown');
await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should not have been destroyed after clicking outside");
// click to really add an element
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
$quickCreate = kanban.$('.o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'new partner');
// clicking outside should no longer destroy the quick create as it is dirty
await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first'));
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should not have been destroyed");
// confirm by pressing ENTER
nbRecords = 5;
$quickCreate.find('input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ENTER,
which: $.ui.keyCode.ENTER,
}));
await nextTick();
assert.strictEqual(this.data.partner.records.length, 5,
"should have created a partner");
assert.strictEqual(_.last(this.data.partner.records).name, "new partner",
"should have correct name");
assert.strictEqual(kanban.exportState().resIds.length, nbRecords);
kanban.destroy();
});
QUnit.test('quick create record: validate with ENTER', async function (assert) {
// in this test, we accurately mock the behavior of the webclient by specifying a
// fieldDebounce > 0, meaning that the changes in an InputField aren't notified to the model
// on 'input' events, but they wait for the 'change' event (or a call to 'commitChanges',
// e.g. triggered by a navigation event)
// in this scenario, the call to 'commitChanges' actually does something (i.e. it notifies
// the new value of the char field), whereas it does nothing if the changes are notified
// directly
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
fieldDebounce: 5000,
});
assert.containsN(kanban, '.o_kanban_record', 4,
"should have 4 records at the beginning");
// add an element and confirm by pressing ENTER
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
await testUtils.kanban.quickCreate(kanban, 'new partner', 'foo');
// triggers a navigation event, leading to the 'commitChanges' and record creation
assert.containsN(kanban, '.o_kanban_record', 5,
"should have created a new record");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '',
"quick create should now be empty");
kanban.destroy();
});
QUnit.test('quick create record: prevent multiple adds with ENTER', async function (assert) {
assert.expect(9);
var prom = makeTestPromise();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
// add a fieldDebounce to accurately simulate what happens in the webclient: the field
// doesn't notify the BasicModel that it has changed directly, as it waits for the user
// to focusout or navigate (e.g. by pressing ENTER)
fieldDebounce: 5000,
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'create') {
assert.step('create');
return prom.then(function () {
return result;
});
}
return result;
},
});
assert.containsN(kanban, '.o_kanban_record', 4,
"should have 4 records at the beginning");
// add an element and press ENTER twice
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
var enterEvent = {
keyCode: $.ui.keyCode.ENTER,
which: $.ui.keyCode.ENTER,
};
await testUtils.fields.editAndTrigger(
kanban.$('.o_kanban_quick_create').find('input[name=foo]'),
'new partner',
['input', $.Event('keydown', enterEvent), $.Event('keydown', enterEvent)]
);
assert.containsN(kanban, '.o_kanban_record', 4,
"should not have created the record yet");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner',
"quick create should not be empty yet");
assert.hasClass(kanban.$('.o_kanban_quick_create'), 'o_disabled',
"quick create should be disabled");
prom.resolve();
await nextTick();
assert.containsN(kanban, '.o_kanban_record', 5,
"should have created a new record");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '',
"quick create should now be empty");
assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled',
"quick create should be enabled");
assert.verifySteps(['create']);
kanban.destroy();
});
QUnit.test('quick create record: prevent multiple adds with Add clicked', async function (assert) {
assert.expect(9);
var prom = makeTestPromise();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'create') {
assert.step('create');
return prom.then(function () {
return result;
});
}
return result;
},
});
assert.containsN(kanban, '.o_kanban_record', 4,
"should have 4 records at the beginning");
// add an element and click 'Add' twice
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner');
await testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add'));
await testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_record', 4,
"should not have created the record yet");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner',
"quick create should not be empty yet");
assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled',
"quick create should be disabled");
prom.resolve();
await nextTick();
assert.containsN(kanban, '.o_kanban_record', 5,
"should have created a new record");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '',
"quick create should now be empty");
assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled',
"quick create should be enabled");
assert.verifySteps(['create']);
kanban.destroy();
});
QUnit.test('quick create record: prevent multiple adds with ENTER, with onchange', async function (assert) {
assert.expect(13);
this.data.partner.onchanges = {
foo: function (obj) {
obj.int_field += (obj.foo ? 3 : 0);
},
};
var shouldDelayOnchange = false;
var prom = makeTestPromise();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'onchange') {
assert.step('onchange');
if (shouldDelayOnchange) {
return Promise.resolve(prom).then(function () {
return result;
});
}
}
if (args.method === 'create') {
assert.step('create');
assert.deepEqual(_.pick(args.args[0], 'foo', 'int_field'), {
foo: 'new partner',
int_field: 3,
});
}
return result;
},
// add a fieldDebounce to accurately simulate what happens in the webclient: the field
// doesn't notify the BasicModel that it has changed directly, as it waits for the user
// to focusout or navigate (e.g. by pressing ENTER)
fieldDebounce: 5000,
});
assert.containsN(kanban, '.o_kanban_record', 4,
"should have 4 records at the beginning");
// add an element and press ENTER twice
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
shouldDelayOnchange = true;
var enterEvent = {
keyCode: $.ui.keyCode.ENTER,
which: $.ui.keyCode.ENTER,
};
await testUtils.fields.editAndTrigger(
kanban.$('.o_kanban_quick_create').find('input[name=foo]'),
'new partner',
['input', $.Event('keydown', enterEvent), $.Event('keydown', enterEvent)]
);
assert.containsN(kanban, '.o_kanban_record', 4,
"should not have created the record yet");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner',
"quick create should not be empty yet");
assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled',
"quick create should be disabled");
prom.resolve();
await nextTick();
assert.containsN(kanban, '.o_kanban_record', 5,
"should have created a new record");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '',
"quick create should now be empty");
assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled',
"quick create should be enabled");
assert.verifySteps([
'onchange', // default_get
'onchange', // new partner
'create',
'onchange', // default_get
]);
kanban.destroy();
});
QUnit.test('quick create record: click Add to create, with delayed onchange', async function (assert) {
assert.expect(13);
this.data.partner.onchanges = {
foo: function (obj) {
obj.int_field += (obj.foo ? 3 : 0);
},
};
var shouldDelayOnchange = false;
var prom = makeTestPromise();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'onchange') {
assert.step('onchange');
if (shouldDelayOnchange) {
return Promise.resolve(prom).then(function () {
return result;
});
}
}
if (args.method === 'create') {
assert.step('create');
assert.deepEqual(_.pick(args.args[0], 'foo', 'int_field'), {
foo: 'new partner',
int_field: 3,
});
}
return result;
},
});
assert.containsN(kanban, '.o_kanban_record', 4,
"should have 4 records at the beginning");
// add an element and click 'add'
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
shouldDelayOnchange = true;
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner');
await testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_record', 4,
"should not have created the record yet");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner',
"quick create should not be empty yet");
assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled',
"quick create should be disabled");
prom.resolve(); // the onchange returns
await nextTick();
assert.containsN(kanban, '.o_kanban_record', 5,
"should have created a new record");
assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '',
"quick create should now be empty");
assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled',
"quick create should be enabled");
assert.verifySteps([
'onchange', // default_get
'onchange', // new partner
'create',
'onchange', // default_get
]);
kanban.destroy();
});
QUnit.test('quick create when first column is folded', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
assert.doesNotHaveClass(kanban.$('.o_kanban_group:first'), 'o_column_folded',
"first column should not be folded");
// fold the first column
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first'));
await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold'));
assert.hasClass(kanban.$('.o_kanban_group:first'),'o_column_folded',
"first column should be folded");
// click on 'Create' to open the quick create in the first column
await testUtils.kanban.clickCreate(kanban);
assert.doesNotHaveClass(kanban.$('.o_kanban_group:first'), 'o_column_folded',
"first column should no longer be folded");
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1,
"should have added a quick create element in first column");
// fold again the first column
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first'));
await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold'));
assert.hasClass(kanban.$('.o_kanban_group:first'),'o_column_folded',
"first column should be folded");
assert.containsNone(kanban, '.o_kanban_quick_create',
"there should be no more quick create");
kanban.destroy();
});
QUnit.test('quick create record: cancel when not dirty', async function (assert) {
assert.expect(11);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain one record");
// click to add an element
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have open the quick create widget");
// click again to add an element -> should have kept the quick create open
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have kept the quick create open");
// click outside: should remove the quick create
await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first'));
assert.containsNone(kanban, '.o_kanban_quick_create',
"the quick create should not have been destroyed");
// click to reopen the quick create
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have open the quick create widget");
// press ESC: should remove the quick create
kanban.$('.o_kanban_quick_create input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.containsNone(kanban, '.o_kanban_quick_create',
"quick create widget should have been removed");
// click to reopen the quick create
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have open the quick create widget");
// click on 'Discard': should remove the quick create
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first'));
assert.containsNone(kanban, '.o_kanban_quick_create',
"the quick create should be destroyed when the user clicks outside");
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should still contain one record");
// click to reopen the quick create
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have open the quick create widget");
// clicking on the quick create itself should keep it open
await testUtils.dom.click(kanban.$('.o_kanban_quick_create'));
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should not have been destroyed when clicked on itself");
kanban.destroy();
});
QUnit.test('quick create record: cancel when modal is opened', async function (assert) {
assert.expect(3);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
});
// click to add an element
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have open the quick create widget");
kanban.$('.o_kanban_quick_create input')
.val('test')
.trigger('keyup')
.trigger('focusout');
await nextTick();
// When focusing out of the many2one, a modal to add a 'product' will appear.
// The following assertions ensures that a click on the body element that has 'modal-open'
// will NOT close the quick create.
// This can happen when the user clicks out of the input because of a race condition between
// the focusout of the m2o and the global 'click' handler of the quick create.
// Check odoo/odoo#61981 for more details.
const $body = kanban.$el.closest('body');
assert.hasClass($body, 'modal-open',
"modal should be opening after m2o focusout");
await testUtils.dom.click($body);
assert.containsOnce(kanban, '.o_kanban_quick_create',
"quick create should stay open while modal is opening");
kanban.destroy();
});
QUnit.test('quick create record: cancel when dirty', async function (assert) {
assert.expect(7);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain one record");
// click to add an element and edit it
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have open the quick create widget");
var $quickCreate = kanban.$('.o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'some value');
// click outside: should not remove the quick create
await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first'));
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should not have been destroyed");
// press ESC: should remove the quick create
$quickCreate.find('input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.containsNone(kanban, '.o_kanban_quick_create',
"quick create widget should have been removed");
// click to reopen quick create and edit it
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have open the quick create widget");
$quickCreate = kanban.$('.o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'some value');
// click on 'Discard': should remove the quick create
await testUtils.dom.click(kanban.$('.o_kanban_quick_create .o_kanban_cancel'));
assert.containsNone(kanban, '.o_kanban_quick_create',
"the quick create should be destroyed when the user clicks outside");
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should still contain one record");
kanban.destroy();
});
QUnit.test('quick create record and edit in grouped mode', async function (assert) {
assert.expect(6);
var newRecordID;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
mockRPC: function (route, args) {
var def = this._super.apply(this, arguments);
if (args.method === 'name_create') {
def.then(function (result) {
newRecordID = result[0];
});
}
return def;
},
groupBy: ['bar'],
intercepts: {
switch_view: function (event) {
assert.strictEqual(event.data.mode, "edit",
"should trigger 'open_record' event in edit mode");
assert.strictEqual(event.data.res_id, newRecordID,
"should open the correct record");
},
},
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain one record");
// click to add and edit an element
var $quickCreate = kanban.$('.o_kanban_quick_create');
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
$quickCreate = kanban.$('.o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'new partner');
await testUtils.dom.click($quickCreate.find('button.o_kanban_edit'));
assert.strictEqual(this.data.partner.records.length, 5,
"should have created a partner");
assert.strictEqual(_.last(this.data.partner.records).name, "new partner",
"should have correct name");
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should now contain two records");
kanban.destroy();
});
QUnit.test('quick create several records in a row', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain one record");
// click to add an element, fill the input and press ENTER
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should be open");
await testUtils.kanban.quickCreate(kanban, 'new partner 1');
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should now contain two records");
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should still be open");
// create a second element in a row
await testUtils.kanban.quickCreate(kanban, 'new partner 2');
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3,
"first column should now contain three records");
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should still be open");
kanban.destroy();
});
QUnit.test('quick create is disabled until record is created and read', async function (assert) {
assert.expect(6);
var prom = makeTestPromise();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'read') {
return prom.then(_.constant(result));
}
return result;
},
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain one record");
// click to add a record, and add two in a row (first one will be delayed)
await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
assert.containsOnce(kanban, '.o_kanban_quick_create',
"the quick create should be open");
await testUtils.kanban.quickCreate(kanban, 'new partner 1');
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should still contain one record");
assert.containsOnce(kanban, '.o_kanban_quick_create.o_disabled',
"quick create should be disabled");
prom.resolve();
await nextTick();
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should now contain two records");
assert.strictEqual(kanban.$('.o_kanban_quick_create:not(.o_disabled)').length, 1,
"quick create should be enabled");
kanban.destroy();
});
QUnit.test('quick create record fail in grouped by many2one', async function (assert) {
assert.expect(8);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
archs: {
'partner,false,form': '',
},
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'name_create') {
return Promise.reject({
message: {
code: 200,
data: {},
message: "Odoo server error",
},
event: $.Event()
});
}
return this._super.apply(this, arguments);
},
});
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"there should be 2 records in first column");
await testUtils.kanban.clickCreate(kanban); // Click on 'Create'
assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create',
"clicking on create should open the quick_create in the first column");
await testUtils.kanban.quickCreate(kanban, 'test');
assert.strictEqual($('.modal .o_legacy_form_view.o_form_editable').length, 1,
"a form view dialog should have been opened (in edit)");
assert.strictEqual($('.modal .o_field_many2one input').val(), 'hello',
"the correct product_id should already be set");
// specify a name and save
await testUtils.fields.editInput($('.modal input[name=foo]'), 'test');
await testUtils.modal.clickButton('Save');
assert.strictEqual($('.modal').length, 0, "the modal should be closed");
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3,
"there should be 3 records in first column");
var $firstRecord = kanban.$('.o_kanban_group:first .o_kanban_record:first');
assert.strictEqual($firstRecord.text(), 'test',
"the first record of the first column should be the new one");
assert.strictEqual(kanban.$('.o_kanban_quick_create:not(.o_disabled)').length, 1,
"quick create should be enabled");
kanban.destroy();
});
QUnit.test('quick create record is re-enabled after discard on failure', async function (assert) {
assert.expect(4);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
archs: {
'partner,false,form': '',
},
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'name_create') {
return Promise.reject({
message: {
code: 200,
data: {},
message: "Odoo server error",
},
event: $.Event()
});
}
return this._super.apply(this, arguments);
}
});
await testUtils.kanban.clickCreate(kanban);
assert.containsOnce(kanban, '.o_kanban_quick_create',
"should have a quick create widget");
await testUtils.kanban.quickCreate(kanban, 'test');
assert.strictEqual($('.modal .o_legacy_form_view.o_form_editable').length, 1,
"a form view dialog should have been opened (in edit)");
await testUtils.modal.clickButton('Discard');
assert.strictEqual($('.modal').length, 0, "the modal should be closed");
assert.strictEqual(kanban.$('.o_kanban_quick_create:not(.o_disabled)').length, 1,
"quick create widget should have been re-enabled");
kanban.destroy();
});
QUnit.test('quick create record fails in grouped by char', async function (assert) {
assert.expect(7);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
archs: {
'partner,false,form': '',
},
mockRPC: function (route, args) {
if (args.method === 'name_create') {
return Promise.reject({
message: {
code: 200,
data: {},
message: "Odoo server error",
},
event: $.Event()
});
}
if (args.method === 'create') {
assert.deepEqual(args.args[0], {foo: 'yop'},
"should write the correct value for foo");
assert.deepEqual(args.kwargs.context, {default_foo: 'yop', default_name: 'test'},
"should send the correct default value for foo");
}
return this._super.apply(this, arguments);
},
groupBy: ['foo'],
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"there should be 1 record in first column");
await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test');
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.strictEqual($('.modal .o_legacy_form_view.o_form_editable').length, 1,
"a form view dialog should have been opened (in edit)");
assert.strictEqual($('.modal .o_field_widget[name=foo]').val(), 'yop',
"the correct default value for foo should already be set");
await testUtils.modal.clickButton('Save');
assert.strictEqual($('.modal').length, 0, "the modal should be closed");
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"there should be 2 records in first column");
kanban.destroy();
});
QUnit.test('quick create record fails in grouped by selection', async function (assert) {
assert.expect(7);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
archs: {
'partner,false,form': '',
},
mockRPC: function (route, args) {
if (args.method === 'name_create') {
return Promise.reject({
message: {
code: 200,
data: {},
message: "Odoo server error",
},
event: $.Event()
});
}
if (args.method === 'create') {
assert.deepEqual(args.args[0], {state: 'abc'},
"should write the correct value for state");
assert.deepEqual(args.kwargs.context, {default_state: 'abc', default_name: 'test'},
"should send the correct default value for state");
}
return this._super.apply(this, arguments);
},
groupBy: ['state'],
});
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"there should be 1 record in first column");
await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test');
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.strictEqual($('.modal .o_legacy_form_view.o_form_editable').length, 1,
"a form view dialog should have been opened (in edit)");
assert.strictEqual($('.modal .o_field_widget[name=state]').val(), '"abc"',
"the correct default value for state should already be set");
await testUtils.modal.clickButton('Save');
assert.strictEqual($('.modal').length, 0, "the modal should be closed");
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"there should be 2 records in first column");
kanban.destroy();
});
QUnit.test('quick create record in empty grouped kanban', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
// override read_group to return empty groups, as this is
// the case for several models (e.g. project.task grouped
// by stage_id)
var result = {
groups: [
{__domain: [['product_id', '=', 3]], product_id_count: 0},
{__domain: [['product_id', '=', 5]], product_id_count: 0},
],
length: 2,
};
return Promise.resolve(result);
}
return this._super.apply(this, arguments);
},
});
assert.containsN(kanban, '.o_kanban_group', 2,
"there should be 2 columns");
assert.containsNone(kanban, '.o_kanban_record',
"both columns should be empty");
await testUtils.kanban.clickCreate(kanban);
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_quick_create',
"should have opened the quick create in the first column");
kanban.destroy();
});
QUnit.test('quick create record in grouped on date(time) field', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['date'],
intercepts: {
switch_view: function (ev) {
assert.deepEqual(_.pick(ev.data, 'res_id', 'view_type'), {
res_id: undefined,
view_type: 'form',
}, "should trigger an event to open the form view (twice)");
},
},
});
assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i',
"quick create should be disabled when grouped on a date field");
// clicking on CREATE in control panel should not open a quick create
await testUtils.kanban.clickCreate(kanban);
assert.containsNone(kanban, '.o_kanban_quick_create',
"should not have opened the quick create widget");
await kanban.reload({groupBy: ['datetime']});
assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i',
"quick create should be disabled when grouped on a datetime field");
// clicking on CREATE in control panel should not open a quick create
await testUtils.kanban.clickCreate(kanban);
assert.containsNone(kanban, '.o_kanban_quick_create',
"should not have opened the quick create widget");
kanban.destroy();
});
QUnit.test('quick create record if grouped on date(time) field with attribute allow_group_range_value: true',
async function (assert) {
assert.expect(6);
this.data.partner.records[0].date = '2017-01-08';
this.data.partner.records[1].date = '2017-01-09';
this.data.partner.records[2].date = '2017-01-08';
this.data.partner.records[3].date = '2017-01-10';
this.data.partner.records[0].datetime = '2017-01-08 10:55:05';
this.data.partner.records[1].datetime = '2017-01-09 11:31:10';
this.data.partner.records[2].datetime = '2017-01-08 09:20:25';
this.data.partner.records[3].datetime = '2017-01-10 08:05:51';
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'' +
'',
archs: {
'partner,quick_form,form': '',
},
groupBy: ['date'],
});
assert.containsOnce(kanban, '.o_kanban_header .o_kanban_quick_add i',
"quick create should be enabled when grouped on a non-readonly date field");
// clicking on CREATE in control panel should open a quick create
await testUtils.kanban.clickCreate(kanban);
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_quick_create',
"should have opened the quick create in the first column");
assert.strictEqual(kanban.$(
".o_kanban_group:first .o_kanban_quick_create .o_datepicker_input[name=date]"
).val(), "01/31/2017");
await kanban.reload({groupBy: ['datetime']});
assert.containsOnce(kanban, '.o_kanban_header .o_kanban_quick_add i',
"quick create should be enabled when grouped on a non-readonly datetime field");
// clicking on CREATE in control panel should open a quick create
await testUtils.kanban.clickCreate(kanban);
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_quick_create',
"should have opened the quick create in the first column");
assert.strictEqual(kanban.$(
".o_kanban_group:first .o_kanban_quick_create .o_datepicker_input[name=datetime]"
).val(), "01/31/2017 23:59:59");
kanban.destroy();
});
QUnit.test('quick create record feature is properly enabled/disabled at reload', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['foo'],
});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3,
"quick create should be enabled when grouped on a char field");
await kanban.reload({groupBy: ['date']});
assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i',
"quick create should now be disabled (grouped on date field)");
await kanban.reload({groupBy: ['bar']});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 2,
"quick create should be enabled again (grouped on boolean field)");
kanban.destroy();
});
QUnit.test('quick create record in grouped by char field', async function (assert) {
assert.expect(4);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['foo'],
mockRPC: function (route, args) {
if (args.method === 'name_create') {
assert.deepEqual(args.kwargs.context, {default_foo: 'yop'},
"should send the correct default value for foo");
}
return this._super.apply(this, arguments);
},
});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3,
"quick create should be enabled when grouped on a char field");
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain 1 record");
await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record');
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should now contain 2 records");
kanban.destroy();
});
QUnit.test('quick create record in grouped by boolean field', async function (assert) {
assert.expect(4);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
if (args.method === 'name_create') {
assert.deepEqual(args.kwargs.context, {default_bar: true},
"should send the correct default value for bar");
}
return this._super.apply(this, arguments);
},
});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 2,
"quick create should be enabled when grouped on a boolean field");
assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 3,
"second column (true) should contain 3 records");
await testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record');
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 4,
"second column (true) should now contain 4 records");
kanban.destroy();
});
QUnit.test('quick create record in grouped on selection field', async function (assert) {
assert.expect(4);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
mockRPC: function (route, args) {
if (args.method === 'name_create') {
assert.deepEqual(args.kwargs.context, {default_state: 'abc'},
"should send the correct default value for bar");
}
return this._super.apply(this, arguments);
},
groupBy: ['state'],
});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3,
"quick create should be enabled when grouped on a selection field");
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column (abc) should contain 1 record");
await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record');
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column (abc) should contain 2 records");
kanban.destroy();
});
QUnit.test('quick create record in grouped by char field (within quick_create_view)', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['foo'],
mockRPC: function (route, args) {
if (args.method === 'create') {
assert.deepEqual(args.args[0], {foo: 'yop'},
"should write the correct value for foo");
assert.deepEqual(args.kwargs.context, {default_foo: 'yop'},
"should send the correct default value for foo");
}
return this._super.apply(this, arguments);
},
});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3,
"quick create should be enabled when grouped on a char field");
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column should contain 1 record");
await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i'));
assert.strictEqual(kanban.$('.o_kanban_quick_create input').val(), 'yop',
"should have set the correct foo value by default");
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column should now contain 2 records");
kanban.destroy();
});
QUnit.test('quick create record in grouped by boolean field (within quick_create_view)', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
if (args.method === 'create') {
assert.deepEqual(args.args[0], {bar: true},
"should write the correct value for bar");
assert.deepEqual(args.kwargs.context, {default_bar: true},
"should send the correct default value for bar");
}
return this._super.apply(this, arguments);
},
});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 2,
"quick create should be enabled when grouped on a boolean field");
assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 3,
"second column (true) should contain 3 records");
await testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i'));
assert.ok(kanban.$('.o_kanban_quick_create .o_field_boolean input').is(':checked'),
"should have set the correct bar value by default");
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 4,
"second column (true) should now contain 4 records");
kanban.destroy();
});
QUnit.test('quick create record in grouped by selection field (within quick_create_view)', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['state'],
mockRPC: function (route, args) {
if (args.method === 'create') {
assert.deepEqual(args.args[0], {state: 'abc'},
"should write the correct value for state");
assert.deepEqual(args.kwargs.context, {default_state: 'abc'},
"should send the correct default value for state");
}
return this._super.apply(this, arguments);
},
});
assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3,
"quick create should be enabled when grouped on a selection field");
assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record',
"first column (abc) should contain 1 record");
await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i'));
assert.strictEqual(kanban.$('.o_kanban_quick_create select').val(), '"abc"',
"should have set the correct state value by default");
await testUtils.dom.click(kanban.$('.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2,
"first column (abc) should now contain 2 records");
kanban.destroy();
});
QUnit.test('quick create record while adding a new column', async function (assert) {
assert.expect(10);
var def = testUtils.makeTestPromise();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === 'name_create' && args.model === 'product') {
return def.then(_.constant(result));
}
return result;
},
});
assert.containsN(kanban, '.o_kanban_group', 2);
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2);
// add a new column
assert.containsOnce(kanban, '.o_column_quick_create');
assert.isNotVisible(kanban.$('.o_column_quick_create input'));
await testUtils.dom.click(kanban.$('.o_quick_create_folded'));
assert.isVisible(kanban.$('.o_column_quick_create input'));
await testUtils.fields.editInput(kanban.$('.o_column_quick_create input'), 'new column');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group', 2);
// click to add a new record
await testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new'));
// should wait for the column to be created (and view to be re-rendered
// before opening the quick create
assert.containsNone(kanban, '.o_kanban_quick_create');
// unlock column creation
def.resolve();
await testUtils.nextTick();
assert.containsN(kanban, '.o_kanban_group', 3);
assert.containsOnce(kanban, '.o_kanban_quick_create');
// quick create record in first column
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record');
await testUtils.dom.click(kanban.$('.o_kanban_quick_create .o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3);
kanban.destroy();
});
QUnit.test('close a column while quick creating a record', async function (assert) {
assert.expect(6);
const def = testUtils.makeTestPromise();
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['product_id'],
async mockRPC(route, args) {
const result = this._super(...arguments);
if (args.method === 'get_views') {
await def;
}
return result;
},
});
assert.containsN(kanban, '.o_kanban_group', 2);
assert.containsNone(kanban, '.o_column_folded');
// click to quick create a new record in the first column (this operation is delayed)
await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_quick_add'));
assert.containsNone(kanban, '.o_legacy_form_view');
// click to fold the first column
await testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first'));
await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold'));
assert.containsOnce(kanban, '.o_column_folded');
def.resolve();
await testUtils.nextTick();
assert.containsNone(kanban, '.o_legacy_form_view');
assert.containsOnce(kanban, '.o_column_folded');
kanban.destroy();
});
QUnit.test('quick create record: open on a column while another column has already one', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
// Click on quick create in first column
await testUtils.dom.click(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_quick_add'));
assert.containsOnce(kanban, '.o_kanban_quick_create');
assert.containsOnce(kanban.$('.o_kanban_group:nth-child(1)'), '.o_kanban_quick_create');
// Click on quick create in second column
await testUtils.dom.click(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_quick_add'));
assert.containsOnce(kanban, '.o_kanban_quick_create');
assert.containsOnce(kanban.$('.o_kanban_group:nth-child(2)'), '.o_kanban_quick_create');
// Click on quick create in first column once again
await testUtils.dom.click(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_quick_add'));
assert.containsOnce(kanban, '.o_kanban_quick_create');
assert.containsOnce(kanban.$('.o_kanban_group:nth-child(1)'), '.o_kanban_quick_create');
kanban.destroy();
});
QUnit.test('many2many_tags in kanban views', async function (assert) {
assert.expect(12);
this.data.partner.records[0].category_ids = [6, 7];
this.data.partner.records[1].category_ids = [7, 8];
this.data.category.records.push({
id: 8,
name: "hello",
color: 0,
});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'' +
'' +
'
' +
'' +
'',
mockRPC: function (route) {
assert.step(route);
return this._super.apply(this, arguments);
},
intercepts: {
switch_view: function (event) {
assert.deepEqual(_.pick(event.data, 'mode', 'model', 'res_id', 'view_type'), {
mode: 'readonly',
model: 'partner',
res_id: 1,
view_type: 'form',
}, "should trigger an event to open the clicked record in a form view");
},
},
});
var $first_record = kanban.$('.o_kanban_record:first()');
assert.strictEqual($first_record.find('.o_field_many2manytags .o_tag').length, 2,
'first record should contain 2 tags');
assert.hasClass($first_record.find('.o_tag:first()'),'o_tag_color_2',
'first tag should have color 2');
assert.verifySteps(['/web/dataset/search_read', '/web/dataset/call_kw/category/read'],
'two RPC should have been done (one search read and one read for the m2m)');
// Checks that second records has only one tag as one should be hidden (color 0)
assert.strictEqual(kanban.$('.o_kanban_record').eq(1).find('.o_tag').length, 1,
'there should be only one tag in second record');
// Write on the record using the priority widget to trigger a re-render in readonly
await testUtils.dom.click(kanban.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').first());
assert.verifySteps([
'/web/dataset/call_kw/partner/write',
'/web/dataset/call_kw/partner/read',
'/web/dataset/call_kw/category/read'
], 'five RPCs should have been done (previous 2, 1 write (triggers a re-render), same 2 at re-render');
assert.strictEqual(kanban.$('.o_kanban_record:first()').find('.o_field_many2manytags .o_tag').length, 2,
'first record should still contain only 2 tags');
// click on a tag (should trigger switch_view)
await testUtils.dom.click(kanban.$('.o_tag:contains(gold):first'));
kanban.destroy();
});
QUnit.test('Do not open record when clicking on `a` with `href`', async function (assert) {
assert.expect(5);
this.data.partner.records = [
{ id: 1, foo: 'yop' },
];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
});
testUtils.mock.intercept(kanban, 'switch_view', function (event) {
assert.deepEqual(event.data, {
view_type: 'form',
res_id: 1,
mode: 'edit',
model: 'partner',
});
});
await testUtils.dom.click(kanban.$('a').first());
kanban.destroy();
});
QUnit.test('environment is updated when (un)folding groups', async function (assert) {
assert.expect(3);
var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.deepEqual(kanban.exportState().resIds, envIDs);
// fold the second group and check that the res_ids it contains are no
// longer in the environment
envIDs = [1, 3];
await testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold'));
assert.deepEqual(kanban.exportState().resIds, envIDs);
// re-open the second group and check that the res_ids it contains are
// back in the environment
envIDs = [1, 3, 2, 4];
await testUtils.dom.click(kanban.$('.o_kanban_group:last'));
assert.deepEqual(kanban.exportState().resIds, envIDs);
kanban.destroy();
});
QUnit.test('create a column in grouped on m2o', async function (assert) {
assert.expect(14);
var nbRPCs = 0;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
nbRPCs++;
if (args.method === 'name_create') {
assert.ok(true, "should call name_create");
}
//Create column will call resequence to set column order
if (route === '/web/dataset/resequence') {
assert.ok(true, "should call resequence");
return Promise.resolve(true);
}
return this._super(route, args);
},
});
assert.containsOnce(kanban, '.o_column_quick_create', "should have a quick create column");
assert.notOk(kanban.$('.o_column_quick_create input').is(':visible'),
"the input should not be visible");
await testUtils.dom.click(kanban.$('.o_quick_create_folded'));
assert.ok(kanban.$('.o_column_quick_create input').is(':visible'),
"the input should be visible");
// discard the column creation and click it again
await kanban.$('.o_column_quick_create input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.notOk(kanban.$('.o_column_quick_create input').is(':visible'),
"the input should not be visible after discard");
await testUtils.dom.click(kanban.$('.o_quick_create_folded'));
assert.ok(kanban.$('.o_column_quick_create input').is(':visible'),
"the input should be visible");
await kanban.$('.o_column_quick_create input').val('new value').trigger('input');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new value)').length, 1,
"the last column should be the newly created one");
assert.ok(_.isNumber(kanban.$('.o_kanban_group:last').data('id')),
'the created column should have the correct id');
assert.doesNotHaveClass(kanban.$('.o_kanban_group:last'), 'o_column_folded',
'the created column should not be folded');
// fold and unfold the created column, and check that no RPC is done (as there is no record)
nbRPCs = 0;
await testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold'));
assert.hasClass(kanban.$('.o_kanban_group:last'),'o_column_folded',
'the created column should now be folded');
await testUtils.dom.click(kanban.$('.o_kanban_group:last'));
assert.doesNotHaveClass(kanban.$('.o_kanban_group:last'), 'o_column_folded');
assert.strictEqual(nbRPCs, 0, 'no rpc should have been done when folding/unfolding');
// quick create a record
await testUtils.kanban.clickCreate(kanban);
assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create',
"clicking on create should open the quick_create in the first column");
kanban.destroy();
});
QUnit.test('auto fold group when reach the limit', async function (assert) {
assert.expect(9);
var data = this.data;
for (var i = 0; i < 12; i++) {
data.product.records.push({
id: (8 + i),
name: ("column"),
});
data.partner.records.push({
id: (20 + i),
foo: ("dumb entry"),
product_id: (8 + i),
});
}
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
return this._super.apply(this, arguments).then(function (result) {
result.groups[2].__fold = true;
result.groups[8].__fold = true;
return result;
});
}
return this._super(route, args);
},
});
// we look if column are fold/unfold according what is expected
assert.doesNotHaveClass(kanban.$('.o_kanban_group:nth-child(2)'), 'o_column_folded');
assert.doesNotHaveClass(kanban.$('.o_kanban_group:nth-child(4)'), 'o_column_folded');
assert.doesNotHaveClass(kanban.$('.o_kanban_group:nth-child(10)'), 'o_column_folded');
assert.hasClass(kanban.$('.o_kanban_group:nth-child(3)'), 'o_column_folded');
assert.hasClass(kanban.$('.o_kanban_group:nth-child(9)'), 'o_column_folded');
// we look if columns are actually fold after we reached the limit
assert.hasClass(kanban.$('.o_kanban_group:nth-child(13)'), 'o_column_folded');
assert.hasClass(kanban.$('.o_kanban_group:nth-child(14)'), 'o_column_folded');
// we look if we have the right count of folded/unfolded column
assert.containsN(kanban, '.o_kanban_group:not(.o_column_folded)', 10);
assert.containsN(kanban, '.o_kanban_group.o_column_folded', 4);
kanban.destroy();
});
QUnit.test('hide and display help message (ESC) in kanban quick create', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
await testUtils.dom.click(kanban.$('.o_quick_create_folded'));
assert.ok(kanban.$('.o_discard_msg').is(':visible'),
'the ESC to discard message is visible');
// click outside the column (to lose focus)
await testUtils.dom.clickFirst(kanban.$('.o_kanban_header'));
assert.notOk(kanban.$('.o_discard_msg').is(':visible'),
'the ESC to discard message is no longer visible');
kanban.destroy();
});
QUnit.test('delete a column in grouped on m2o', async function (assert) {
assert.expect(37);
testUtils.mock.patch(KanbanRenderer, {
_renderGrouped: function () {
this._super.apply(this, arguments);
// set delay and revert animation time to 0 so dummy drag and drop works
if (this.$el.sortable('instance')) {
this.$el.sortable('option', {delay: 0, revert: 0});
}
},
});
var resequencedIDs;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
resequencedIDs = args.ids;
assert.strictEqual(_.reject(args.ids, _.isNumber).length, 0,
"column resequenced should be existing records with IDs");
return Promise.resolve(true);
}
if (args.method) {
assert.step(args.method);
}
return this._super(route, args);
},
});
// check the initial rendering
assert.containsN(kanban, '.o_kanban_group', 2, "should have two columns");
assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 3,
'first column should be [3, "hello"]');
assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 5,
'second column should be [5, "xmo"]');
assert.strictEqual(kanban.$('.o_kanban_group:last .o_column_title').text(), 'xmo',
'second column should have correct title');
assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 2,
"second column should have two records");
// check available actions in kanban header's config dropdown
assert.ok(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold').length,
"should be able to fold the column");
assert.ok(kanban.$('.o_kanban_group:first .o_column_edit').length,
"should be able to edit the column");
assert.ok(kanban.$('.o_kanban_group:first .o_column_delete').length,
"should be able to delete the column");
assert.ok(!kanban.$('.o_kanban_group:first .o_column_archive_records').length, "should not be able to archive all the records");
assert.ok(!kanban.$('.o_kanban_group:first .o_column_unarchive_records').length, "should not be able to restore all the records");
// delete second column (first cancel the confirm request, then confirm)
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete'));
assert.ok($('.modal').length, 'a confirm modal should be displayed');
await testUtils.modal.clickButton('Cancel'); // click on cancel
assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 5,
'column [5, "xmo"] should still be there');
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete'));
assert.ok($('.modal').length, 'a confirm modal should be displayed');
await testUtils.modal.clickButton('Ok'); // click on confirm
assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 3,
'last column should now be [3, "hello"]');
assert.containsN(kanban, '.o_kanban_group', 2, "should still have two columns");
assert.ok(!_.isNumber(kanban.$('.o_kanban_group:first').data('id')),
'first column should have no id (Undefined column)');
// check available actions on 'Undefined' column
assert.ok(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold').length,
"should be able to fold the column");
assert.ok(!kanban.$('.o_kanban_group:first .o_column_delete').length,
'Undefined column could not be deleted');
assert.ok(!kanban.$('.o_kanban_group:first .o_column_edit').length,
'Undefined column could not be edited');
assert.ok(!kanban.$('.o_kanban_group:first .o_column_archive_records').length, "Records of undefined column could not be archived");
assert.ok(!kanban.$('.o_kanban_group:first .o_column_unarchive_records').length, "Records of undefined column could not be restored");
assert.verifySteps(['web_read_group', 'unlink', 'web_read_group']);
assert.strictEqual(kanban.renderer.widgets.length, 2,
"the old widgets should have been correctly deleted");
// test column drag and drop having an 'Undefined' column
await testUtils.dom.dragAndDrop(
kanban.$('.o_column_title:first'),
kanban.$('.o_column_title:last'), {position: 'right'}
);
assert.strictEqual(resequencedIDs, undefined,
"resequencing require at least 2 not Undefined columns");
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded'));
kanban.$('.o_column_quick_create input').val('once third column');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
var newColumnID = kanban.$('.o_kanban_group:last').data('id');
await testUtils.dom.dragAndDrop(
kanban.$('.o_column_title:first'),
kanban.$('.o_column_title:last'), {position: 'right'}
);
assert.deepEqual([3, newColumnID], resequencedIDs,
"moving the Undefined column should not affect order of other columns");
await testUtils.dom.dragAndDrop(
kanban.$('.o_column_title:first'),
kanban.$('.o_column_title:nth(1)'), {position: 'right'}
);
await nextTick(); // wait for resequence after drag and drop
assert.deepEqual([newColumnID, 3], resequencedIDs,
"moved column should be resequenced accordingly");
assert.verifySteps(['name_create', 'read', 'read', 'read']);
kanban.destroy();
testUtils.mock.unpatch(KanbanRenderer);
});
QUnit.test('create a column, delete it and create another one', async function (assert) {
assert.expect(5);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.containsN(kanban, '.o_kanban_group', 2, "should have two columns");
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded'));
kanban.$('.o_column_quick_create input').val('new column 1');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group', 3, "should have two columns");
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last'));
await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete'));
await testUtils.modal.clickButton('Ok');
assert.containsN(kanban, '.o_kanban_group', 2, "should have twos columns");
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded'));
kanban.$('.o_column_quick_create input').val('new column 2');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
assert.containsN(kanban, '.o_kanban_group', 3, "should have three columns");
assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new column 2)').length, 1,
"the last column should be the newly created one");
kanban.destroy();
});
QUnit.test('edit a column in grouped on m2o', async function (assert) {
assert.expect(12);
var nbRPCs = 0;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
archs: {
'product,false,form': '',
},
mockRPC: function (route, args) {
nbRPCs++;
return this._super(route, args);
},
});
assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'xmo',
'title of the column should be "xmo"');
// edit the title of column [5, 'xmo'] and close without saving
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]'));
await testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit'));
assert.containsOnce(document.body, '.modal .o_form_editable',
"a form view should be open in a modal");
assert.strictEqual($('.modal .o_form_editable input').val(), 'xmo',
'the name should be "xmo"');
await testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value
nbRPCs = 0;
await testUtils.dom.click($('.modal-header .btn-close'));
assert.containsNone(document.body, '.modal');
assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'xmo',
'title of the column should still be "xmo"');
assert.strictEqual(nbRPCs, 0, 'no RPC should have been done');
// edit the title of column [5, 'xmo'] and discard
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]'));
await testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit'));
await testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value
nbRPCs = 0;
await testUtils.modal.clickButton('Discard');
assert.containsNone(document.body, '.modal');
assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'xmo',
'title of the column should still be "xmo"');
assert.strictEqual(nbRPCs, 0, 'no RPC should have been done');
// edit the title of column [5, 'xmo'] and save
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]'));
await testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit'));
await testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value
nbRPCs = 0;
await testUtils.modal.clickButton('Save'); // click on save
assert.ok(!$('.modal').length, 'the modal should be closed');
assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'ged',
'title of the column should be "ged"');
assert.strictEqual(nbRPCs, 4, 'should have done 1 write, 1 read_group and 2 search_read');
kanban.destroy();
});
QUnit.test('edit a column propagates right context', async function (assert) {
assert.expect(4);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
archs: {
'product,false,form': '',
},
session: {user_context: {lang: 'brol'}},
mockRPC: function (route, args) {
let context;
if (route === '/web/dataset/search_read' && args.model === 'partner') {
context = args.context;
assert.strictEqual(context.lang, 'brol',
'lang is present in context for partner operations');
}
if (args.model === 'product') {
context = args.kwargs.context;
assert.strictEqual(context.lang, 'brol',
'lang is present in context for product operations');
}
return this._super.apply(this, arguments);
},
});
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]'));
await testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit'));
kanban.destroy();
});
QUnit.test('quick create column should be opened if there is no column', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
domain: [['foo', '=', 'norecord']],
});
assert.containsNone(kanban, '.o_kanban_group');
assert.containsOnce(kanban, '.o_column_quick_create');
assert.ok(kanban.$('.o_column_quick_create input').is(':visible'),
"the quick create should be opened");
kanban.destroy();
});
QUnit.test('quick create column should not be closed on widnow click if there is no column', async function (assert) {
assert.expect(4);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
groupBy: ['product_id'],
domain: [['foo', '=', 'norecord']],
});
assert.containsNone(kanban, '.o_kanban_group');
assert.containsOnce(kanban, '.o_column_quick_create');
assert.ok(kanban.$('.o_column_quick_create input').is(':visible'),
"the quick create should be opened");
// click outside should not discard quick create column
await testUtils.dom.click(kanban.$('.o_kanban_example_background_container'));
assert.ok(kanban.$('.o_column_quick_create input').is(':visible'),
"the quick create should still be opened");
kanban.destroy();
});
QUnit.test('quick create several columns in a row', async function (assert) {
assert.expect(10);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.containsN(kanban, '.o_kanban_group', 2,
"should have two columns");
assert.containsOnce(kanban, '.o_column_quick_create',
"should have a ColumnQuickCreate widget");
assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_folded:visible',
"the ColumnQuickCreate should be folded");
assert.containsNone(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible',
"the ColumnQuickCreate should be folded");
// add a new column
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded'));
assert.containsNone(kanban, '.o_column_quick_create .o_quick_create_folded:visible',
"the ColumnQuickCreate should be unfolded");
assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible',
"the ColumnQuickCreate should be unfolded");
kanban.$('.o_column_quick_create input').val('New Column 1');
await testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary'));
assert.containsN(kanban, '.o_kanban_group', 3,
"should now have three columns");
// add another column
assert.containsNone(kanban, '.o_column_quick_create .o_quick_create_folded:visible',
"the ColumnQuickCreate should still be unfolded");
assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible',
"the ColumnQuickCreate should still be unfolded");
kanban.$('.o_column_quick_create input').val('New Column 2');
await testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary'));
assert.containsN(kanban, '.o_kanban_group', 4,
"should now have four columns");
kanban.destroy();
});
QUnit.test('quick create column and examples', async function (assert) {
assert.expect(12);
kanbanExamplesRegistry.add('test', {
examples:[{
name: "A first example",
columns: ["Column 1", "Column 2", "Column 3"],
description: "A weak description.",
}, {
name: "A second example",
columns: ["Col 1", "Col 2"],
description: Markup`A fantastic description.`
}],
});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.containsOnce(kanban, '.o_column_quick_create',
"should have a ColumnQuickCreate widget");
// open the quick create
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded'));
assert.containsOnce(kanban, '.o_column_quick_create .o_kanban_examples:visible',
"should have a link to see examples");
// click to see the examples
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_examples'));
assert.strictEqual($('.modal .o_legacy_kanban_examples_dialog').length, 1,
"should have open the examples dialog");
assert.strictEqual($('.modal .o_kanban_examples_dialog_nav li').length, 2,
"should have two examples (in the menu)");
assert.strictEqual($('.modal .o_kanban_examples_dialog_nav a').text(),
' A first example A second example ', "example names should be correct");
assert.strictEqual($('.modal .o_kanban_examples_dialog_content .tab-pane').length, 2,
"should have two examples");
const $panes = $('.modal .o_kanban_examples_dialog_content .tab-pane');
var $firstPane = $panes.eq(0);
assert.strictEqual($firstPane.find('.o_kanban_examples_group').length, 3,
"there should be 3 stages in the first example");
assert.strictEqual($firstPane.find('h6').text(), 'Column 1Column 2Column 3',
"column titles should be correct");
assert.strictEqual($firstPane.find('.o_kanban_examples_description').html().trim(),
"A <b>weak</b> description.",
"An escaped description should be displayed");
var $secondPane = $panes.eq(1);
assert.strictEqual($secondPane.find('.o_kanban_examples_group').length, 2,
"there should be 2 stages in the second example");
assert.strictEqual($secondPane.find('h6').text(), 'Col 1Col 2',
"column titles should be correct");
assert.strictEqual($secondPane.find('.o_kanban_examples_description').html().trim(),
"A fantastic description.",
"A formatted description should be displayed.");
kanban.destroy();
delete kanbanExamplesRegistry.map['test'];
});
QUnit.test("quick create column's apply button's display text", async function (assert) {
assert.expect(1);
const applyExamplesText = 'Use This For My Test';
kanbanExamplesRegistry.add('test', {
applyExamplesText: applyExamplesText,
examples:[{
name: "A first example",
columns: ["Column 1", "Column 2", "Column 3"],
}, {
name: "A second example",
columns: ["Col 1", "Col 2"],
}],
});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
// open the quick create
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded'));
// click to see the examples
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_examples'));
const $primaryActionButton = $('.modal footer.modal-footer button.btn-primary > span');
assert.strictEqual($primaryActionButton.text(), applyExamplesText, 'the primary button should display the value of applyExamplesText');
kanban.destroy();
});
QUnit.test('quick create column and examples background with ghostColumns titles', async function (assert) {
assert.expect(4);
this.data.partner.records = [];
kanbanExamplesRegistry.add('test', {
ghostColumns: ["Ghost 1", "Ghost 2", "Ghost 3", "Ghost 4"],
examples:[{
name: "A first example",
columns: ["Column 1", "Column 2", "Column 3"],
}, {
name: "A second example",
columns: ["Col 1", "Col 2"],
}],
});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.containsOnce(kanban, '.o_kanban_example_background',
"should have ExamplesBackground when no data");
assert.strictEqual(kanban.$('.o_kanban_examples_group h6').text(), 'Ghost 1Ghost 2Ghost 3Ghost 4',
"ghost title should be correct");
assert.containsOnce(kanban, '.o_column_quick_create',
"should have a ColumnQuickCreate widget");
assert.containsOnce(kanban, '.o_column_quick_create .o_kanban_examples:visible',
"should not have a link to see examples as there is no examples registered");
kanban.destroy();
});
QUnit.test('quick create column and examples background without ghostColumns titles', async function (assert) {
assert.expect(4);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.containsOnce(kanban, '.o_kanban_example_background',
"should have ExamplesBackground when no data");
assert.strictEqual(kanban.$('.o_kanban_examples_group h6').text(), 'Column 1Column 2Column 3Column 4',
"ghost title should be correct");
assert.containsOnce(kanban, '.o_column_quick_create',
"should have a ColumnQuickCreate widget");
assert.containsNone(kanban, '.o_column_quick_create .o_kanban_examples:visible',
"should not have a link to see examples as there is no examples registered");
kanban.destroy();
});
QUnit.test('nocontent helper after adding a record (kanban with progressbar)', async function (assert) {
assert.expect(3);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
groupBy: ['product_id'],
domain: [['foo', '=', 'abcd']],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
const result = {
groups: [
{ __domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello'] },
],
};
return Promise.resolve(result);
}
return this._super.apply(this, arguments);
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsOnce(kanban, '.o_view_nocontent', "the nocontent helper is displayed");
// add a record
await testUtils.dom.click(kanban.$('.o_kanban_quick_add'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle');
await testUtils.dom.click(kanban.$('button.o_kanban_add'));
assert.containsNone(kanban, '.o_view_nocontent',
"the nocontent helper is not displayed after quick create");
// cancel quick create
await testUtils.dom.click(kanban.$('button.o_kanban_cancel'));
assert.containsNone(kanban, '.o_view_nocontent',
"the nocontent helper is not displayed after cancelling the quick create");
kanban.destroy();
});
QUnit.test('if view was not grouped at start, it can be grouped and ungrouped', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
});
assert.doesNotHaveClass(kanban.$('.o_legacy_kanban_view'), 'o_kanban_grouped');
await kanban.update({groupBy: ['product_id']});
assert.hasClass(kanban.$('.o_legacy_kanban_view'),'o_kanban_grouped');
await kanban.update({groupBy: []});
assert.doesNotHaveClass(kanban.$('.o_legacy_kanban_view'), 'o_kanban_grouped');
kanban.destroy();
});
QUnit.test('no content helper when archive all records in kanban group', async function (assert) {
assert.expect(3);
// add active field on partner model to have archive option
this.data.partner.fields.active = {string: 'Active', type: 'boolean', default: true};
// remove last records to have only one column
this.data.partner.records = this.data.partner.records.slice(0, 3);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
viewOptions: {
action: {
help: "click to add a partner",
}
},
groupBy: ['bar'],
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/action_archive') {
const partnerIDS = args.args[0];
const records = this.data.partner.records;
_.each(partnerIDS, function (partnerID) {
_.find(records, function (record) {
return record.id === partnerID;
}).active = false;
});
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
// check that the (unique) column contains 3 records
assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3);
// archive the records of the last column
testUtils.kanban.toggleGroupSettings($(kanban.el.querySelector('.o_kanban_group'))); // we should change the helper
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group .o_column_archive_records'));
assert.containsOnce(document.body, '.modal');
await testUtils.modal.clickButton('Ok');
// check no content helper is exist
assert.containsOnce(kanban, '.o_view_nocontent');
kanban.destroy();
});
QUnit.test('no content helper when no data', async function (assert) {
assert.expect(3);
var records = this.data.partner.records;
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'
' +
'' +
'' +
'
' +
'',
viewOptions: {
action: {
help: markup('
click to add a partner
'),
}
},
});
assert.containsOnce(kanban, '.o_view_nocontent',
"should display the no content helper");
assert.strictEqual(kanban.$('.o_view_nocontent p.hello:contains(add a partner)').length, 1,
"should have rendered no content helper from action");
this.data.partner.records = records;
await kanban.reload();
assert.containsNone(kanban, '.o_view_nocontent',
"should not display the no content helper");
kanban.destroy();
});
QUnit.test('no nocontent helper for grouped kanban with empty groups', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
// override read_group to return empty groups, as this is
// the case for several models (e.g. project.task grouped
// by stage_id)
return this._super.apply(this, arguments).then(function (result) {
_.each(result.groups, function (group) {
group[args.kwargs.groupby[0] + '_count'] = 0;
});
return result;
});
}
return this._super.apply(this, arguments);
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsN(kanban, '.o_kanban_group', 2,
"there should be two columns");
assert.containsNone(kanban, '.o_kanban_record',
"there should be no records");
kanban.destroy();
});
QUnit.test('no nocontent helper for grouped kanban with no records', async function (assert) {
assert.expect(4);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsNone(kanban, '.o_kanban_group',
"there should be no columns");
assert.containsNone(kanban, '.o_kanban_record',
"there should be no records");
assert.containsNone(kanban, '.o_view_nocontent',
"there should be no nocontent helper (we are in 'column creation mode')");
assert.containsOnce(kanban, '.o_column_quick_create',
"there should be a column quick create");
kanban.destroy();
});
QUnit.test('no nocontent helper is shown when no longer creating column', async function (assert) {
assert.expect(3);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsNone(kanban, '.o_view_nocontent',
"there should be no nocontent helper (we are in 'column creation mode')");
// creating a new column
kanban.$('.o_column_quick_create .o_input').val('applejack');
await testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_add'));
assert.containsNone(kanban, '.o_view_nocontent',
"there should be no nocontent helper (still in 'column creation mode')");
// leaving column creation mode
kanban.$('.o_column_quick_create .o_input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.containsOnce(kanban, '.o_view_nocontent',
"there should be a nocontent helper");
kanban.destroy();
});
QUnit.test('no nocontent helper is hidden when quick creating a column', async function (assert) {
assert.expect(2);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
var result = {
groups: [
{__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']},
],
length: 1,
};
return Promise.resolve(result);
}
return this._super.apply(this, arguments);
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsOnce(kanban, '.o_view_nocontent',
"there should be a nocontent helper");
await testUtils.dom.click(kanban.$('.o_kanban_add_column'));
assert.containsNone(kanban, '.o_view_nocontent',
"there should be no nocontent helper (we are in 'column creation mode')");
kanban.destroy();
});
QUnit.test('remove nocontent helper after adding a record', async function (assert) {
assert.expect(2);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
var result = {
groups: [
{__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']},
],
length: 1,
};
return Promise.resolve(result);
}
return this._super.apply(this, arguments);
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsOnce(kanban, '.o_view_nocontent',
"there should be a nocontent helper");
// add a record
await testUtils.dom.click(kanban.$('.o_kanban_quick_add'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle');
await testUtils.dom.click(kanban.$('.o_kanban_quick_create button.o_kanban_add'));
assert.containsNone(kanban, '.o_view_nocontent',
"there should be no nocontent helper (there is now one record)");
kanban.destroy();
});
QUnit.test('remove nocontent helper when adding a record', async function (assert) {
assert.expect(2);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
var result = {
groups: [
{__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']},
],
length: 1,
};
return Promise.resolve(result);
}
return this._super.apply(this, arguments);
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsOnce(kanban, '.o_view_nocontent',
"there should be a nocontent helper");
// add a record
await testUtils.dom.click(kanban.$('.o_kanban_quick_add'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle');
assert.containsNone(kanban, '.o_view_nocontent',
"there should be no nocontent helper (there is now one record)");
kanban.destroy();
});
QUnit.test('nocontent helper is displayed again after canceling quick create', async function (assert) {
assert.expect(1);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
var result = {
groups: [
{__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']},
],
length: 1,
};
return Promise.resolve(result);
}
return this._super.apply(this, arguments);
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
// add a record
await testUtils.dom.click(kanban.$('.o_kanban_quick_add'));
await testUtils.dom.click(kanban.$('.o_legacy_kanban_view'));
assert.containsOnce(kanban, '.o_view_nocontent',
"there should be again a nocontent helper");
kanban.destroy();
});
QUnit.test('nocontent helper for grouped kanban with no records with no group_create', async function (assert) {
assert.expect(4);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsNone(kanban, '.o_kanban_group',
"there should be no columns");
assert.containsNone(kanban, '.o_kanban_record',
"there should be no records");
assert.containsNone(kanban, '.o_view_nocontent',
"there should not be a nocontent helper");
assert.containsNone(kanban, '.o_column_quick_create',
"there should not be a column quick create");
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data and no columns', async function (assert) {
assert.expect(3);
this.data.partner.records = [];
const kanban = await createView({
arch: `
`,
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.hasClass(kanban.$el, 'o_legacy_view_sample_data');
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 10,
"there should be 10 sample records");
assert.containsOnce(kanban, '.o_view_nocontent');
await kanban.reload({ domain: [['id', '<', 0]]});
assert.doesNotHaveClass(kanban.$el, 'o_legacy_view_sample_data');
assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');
assert.containsOnce(kanban, '.o_view_nocontent');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data and many2many_tags', async function (assert) {
assert.expect(6);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
groupBy: ['product_id'],
async mockRPC(route, { kwargs, method }) {
assert.step(method || route);
const result = await this._super(...arguments);
if (method === 'web_read_group') {
// override read_group to return empty groups, as this is
// the case for several models (e.g. project.task grouped
// by stage_id)
result.groups.forEach(group => {
group[`${kwargs.groupby[0]}_count`] = 0;
});
}
return result;
},
});
assert.containsN(kanban, '.o_kanban_group', 2, "there should be 2 'real' columns");
assert.hasClass(kanban.$el, 'o_legacy_view_sample_data');
assert.ok(kanban.$('.o_kanban_record').length >= 1, "there should be sample records");
assert.ok(kanban.$('.o_field_many2manytags .o_tag').length >= 1, "there should be tags");
assert.verifySteps(["web_read_group"], "should not read the tags");
kanban.destroy();
});
QUnit.test('sample data does not change after reload with sample data', async function (assert) {
assert.expect(4);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
groupBy: ['product_id'],
async mockRPC(route, { kwargs, method }) {
const result = await this._super(...arguments);
if (method === 'web_read_group') {
// override read_group to return empty groups, as this is
// the case for several models (e.g. project.task grouped
// by stage_id)
result.groups.forEach(group => {
group[`${kwargs.groupby[0]}_count`] = 0;
});
}
return result;
},
});
const columns = kanban.el.querySelectorAll('.o_kanban_group');
assert.ok(columns.length >= 1, "there should be at least 1 sample column");
assert.hasClass(kanban.$el, 'o_legacy_view_sample_data');
assert.containsN(kanban, '.o_kanban_record', 16);
const kanbanText = kanban.el.innerText;
await kanban.reload();
assert.strictEqual(kanbanText, kanban.el.innerText,
"the content should be the same after reloading the view");
kanban.destroy();
});
QUnit.test('non empty kanban with sample data', async function (assert) {
assert.expect(5);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
data: this.data,
groupBy: ['product_id'],
model: 'partner',
View: KanbanView,
async mockRPC(route, { method }) {
const result = await this._super(...arguments);
if (method === 'web_read_group') {
result.groups = this.data.product.records.map(r => {
return {
product_id: [r.id, r.display_name],
product_id_count: 0,
__domain: ['product_id', '=', r.id], // This domain shouldn't be valid.
};
});
result.length = result.groups.length;
}
return result;
},
});
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsN(kanban, '.o_kanban_group', 2);
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_add_column'));
await testUtils.fields.editInput(kanban.el.querySelector('.o_kanban_header input'), "Yoohoo");
await testUtils.dom.click(kanban.el.querySelector('.btn.o_kanban_add'));
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsN(kanban, '.o_kanban_group', 3);
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data: cannot fold a column', async function (assert) {
// folding a column in grouped kanban with sample data is disabled, for the sake of simplicity
assert.expect(5);
const kanban = await createView({
arch: `
`,
data: this.data,
groupBy: ['product_id'],
model: 'partner',
View: KanbanView,
async mockRPC(route, { kwargs, method }) {
const result = await this._super(...arguments);
if (method === 'web_read_group') {
// override read_group to return a single, empty group
result.groups = result.groups.slice(0, 1);
result.groups[0][`${kwargs.groupby[0]}_count`] = 0;
result.length = 1;
}
return result;
},
});
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsOnce(kanban, '.o_kanban_group');
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_config > a'));
assert.hasClass(kanban.el.querySelector('.o_kanban_config .o_kanban_toggle_fold'), 'o_sample_data_disabled');
assert.hasClass(kanban.el.querySelector('.o_kanban_config .o_kanban_toggle_fold'), 'disabled');
kanban.destroy();
});
QUnit.skip('empty grouped kanban with sample data: fold/unfold a column', async function (assert) {
// folding/unfolding of grouped kanban with sample data is currently disabled
assert.expect(8);
const kanban = await createView({
arch: `
`,
data: this.data,
groupBy: ['product_id'],
model: 'partner',
View: KanbanView,
async mockRPC(route, { kwargs, method }) {
const result = await this._super(...arguments);
if (method === 'web_read_group') {
// override read_group to return a single, empty group
result.groups = result.groups.slice(0, 1);
result.groups[0][`${kwargs.groupby[0]}_count`] = 0;
result.length = 1;
}
return result;
},
});
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsOnce(kanban, '.o_kanban_group');
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
// Fold the column
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_config > a'));
await testUtils.dom.click(kanban.el.querySelector('.dropdown-item.o_kanban_toggle_fold'));
assert.containsOnce(kanban, '.o_kanban_group');
assert.hasClass(kanban.$('.o_kanban_group'), 'o_column_folded');
// Unfold the column
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group.o_column_folded'));
assert.containsOnce(kanban, '.o_kanban_group');
assert.doesNotHaveClass(kanban.$('.o_kanban_group'), 'o_column_folded');
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data: delete a column', async function (assert) {
assert.expect(5);
this.data.partner.records = [];
let groups = [{
product_id: [1, 'New'],
product_id_count: 0,
__domain: [],
}];
const kanban = await createView({
arch: `
`,
data: this.data,
groupBy: ['product_id'],
model: 'partner',
View: KanbanView,
async mockRPC(route, { method }) {
let result = await this._super(...arguments);
if (method === 'web_read_group') {
// override read_group to return a single, empty group
return {
groups,
length: groups.length,
};
}
return result;
},
});
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsOnce(kanban, '.o_kanban_group');
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
// Delete the first column
groups = [];
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_config > a'));
await testUtils.dom.click(kanban.el.querySelector('.dropdown-item.o_column_delete'));
await testUtils.dom.click(document.querySelector('.modal .btn-primary'));
assert.containsNone(kanban, '.o_kanban_group');
assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data: add a column and delete it right away', async function (assert) {
assert.expect(9);
const kanban = await createView({
arch: `
`,
data: this.data,
groupBy: ['product_id'],
model: 'partner',
View: KanbanView,
async mockRPC(route, { method }) {
const result = await this._super(...arguments);
if (method === 'web_read_group') {
result.groups = this.data.product.records.map(r => {
return {
product_id: [r.id, r.display_name],
product_id_count: 0,
__domain: ['product_id', '=', r.id], // This domain shouldn't be valid.
};
});
result.length = result.groups.length;
}
return result;
},
});
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsN(kanban, '.o_kanban_group', 2);
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
// add a new column
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_add_column'));
await testUtils.fields.editInput(kanban.el.querySelector('.o_kanban_header input'), "Yoohoo");
await testUtils.dom.click(kanban.el.querySelector('.btn.o_kanban_add'));
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsN(kanban, '.o_kanban_group', 3);
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
// delete the column we just created
const newColumn = kanban.el.querySelectorAll('.o_kanban_group')[2];
await testUtils.dom.click(newColumn.querySelector('.o_kanban_config > a'));
await testUtils.dom.click(newColumn.querySelector('.dropdown-item.o_column_delete'));
await testUtils.dom.click(document.querySelector('.modal .btn-primary'));
assert.hasClass(kanban, 'o_legacy_view_sample_data');
assert.containsN(kanban, '.o_kanban_group', 2);
assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
kanban.destroy();
});
QUnit.test('bounce create button when no data and click on empty area', async function (assert) {
assert.expect(2);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
viewOptions: {
action: {
help: "click to add a partner",
}
},
});
await testUtils.dom.click(kanban.$('.o_legacy_kanban_view'));
assert.doesNotHaveClass(kanban.$('.o-kanban-button-new'), 'o_catch_attention');
await kanban.reload({ domain: [['id', '<', 0]] });
await testUtils.dom.click(kanban.$('.o_legacy_kanban_view'));
assert.hasClass(kanban.$('.o-kanban-button-new'), 'o_catch_attention');
kanban.destroy();
});
QUnit.test('buttons with modifiers', async function (assert) {
assert.expect(2);
this.data.partner.records[1].bar = false; // so that test is more complete
var kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch:
'' +
'' +
'' +
'' +
'
' +
'' +
'' +
'
' +
'',
});
assert.containsOnce(kanban, ".o_btn_test_1",
"kanban should have one buttons of type 1");
assert.containsN(kanban, ".o_btn_test_2", 3,
"kanban should have three buttons of type 2");
kanban.destroy();
});
QUnit.test('button executes action and reloads', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch:
'' +
'
' +
'' +
'' +
'
' +
'',
mockRPC: function (route) {
assert.step(route);
return this._super.apply(this, arguments);
},
});
assert.ok(kanban.$('button[data-name="a1"]').length,
"kanban should have at least one button a1");
var count = 0;
testUtils.mock.intercept(kanban, 'execute_action', function (event) {
count++;
event.data.on_closed();
});
testUtils.dom.click($('button[data-name="a1"]').first());
await new Promise(r => setTimeout(r));
assert.strictEqual(count, 1, "should have triggered a execute action");
testUtils.dom.click($('button[data-name="a1"]').first());
await new Promise(r => setTimeout(r));
assert.strictEqual(count, 1, "double-click on kanban actions should be debounced");
assert.verifySteps([
'/web/dataset/search_read',
'/web/dataset/call_kw/partner/read'
], 'a read should be done after the call button to reload the record');
kanban.destroy();
});
QUnit.test('button executes action and check domain', async function (assert) {
assert.expect(2);
var data = this.data;
data.partner.fields.active = {string: "Active", type: "boolean", default: true};
for (var k in this.data.partner.records) {
data.partner.records[k].active = true;
}
var kanban = await createView({
View: KanbanView,
model: "partner",
data: data,
arch:
'' +
'
' +
'' +
'' +
'' +
'' +
'
' +
'',
});
testUtils.mock.intercept(kanban, 'execute_action', function (event) {
data.partner.records[0].active = false;
event.data.on_closed();
});
assert.strictEqual(kanban.$('.o_kanban_record:contains(yop)').length, 1, "should display 'yop' record");
await testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_active"]'));
assert.strictEqual(kanban.$('.o_kanban_record:contains(yop)').length, 0, "should remove 'yop' record from the view");
kanban.destroy();
});
QUnit.test('button executes action with domain field not in view', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
domain: [['bar', '=', true]],
arch:
'' +
'
' +
'',
mockRPC: function (route) {
assert.step(route);
return this._super.apply(this, arguments);
},
});
var count = 0;
testUtils.mock.intercept(kanban, 'execute_action', function (event) {
count++;
assert.strictEqual(event.data.action_data.type, "object");
assert.strictEqual(event.data.action_data.name, "a1");
event.data.on_closed();
});
testUtils.dom.click(kanban.$('p').first());
await new Promise(r => setTimeout(r));
assert.strictEqual(count, 1, "should have triggered a execute action");
assert.verifySteps([
'/web/dataset/search_read',
'/web/dataset/call_kw/partner/read'
], 'a read should be done after the call button to reload the record');
kanban.destroy();
});
QUnit.test('action/type attributes on kanban arch, type="action"', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch:
'' +
'
' +
'
some value
' +
'
' +
'',
mockRPC: function (route) {
assert.step(route);
return this._super.apply(this, arguments);
},
});
var count = 0;
testUtils.mock.intercept(kanban, 'execute_action', function (event) {
count++;
assert.strictEqual(event.data.action_data.type, "action");
assert.strictEqual(event.data.action_data.name, "a1");
event.data.on_closed();
});
testUtils.dom.click(kanban.$('p').first());
await new Promise(r => setTimeout(r));
assert.strictEqual(count, 1, "should have triggered a execute action");
assert.verifySteps([
'/web/dataset/search_read',
'/web/dataset/call_kw/partner/read'
], 'a read should be done after the call button to reload the record');
kanban.destroy();
});
QUnit.test('rendering date and datetime', async function (assert) {
assert.expect(2);
this.data.partner.records[0].date = "2017-01-25";
this.data.partner.records[1].datetime= "2016-12-12 10:55:05";
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'' +
'' +
'
' +
'' +
'',
});
// FIXME: this test is locale dependant. we need to do it right.
assert.strictEqual(kanban.$('div.o_kanban_record:contains(Wed Jan 25)').length, 1,
"should have formatted the date");
assert.strictEqual(kanban.$('div.o_kanban_record:contains(Mon Dec 12)').length, 1,
"should have formatted the datetime");
kanban.destroy();
});
QUnit.test('evaluate conditions on relational fields', async function (assert) {
assert.expect(3);
this.data.partner.records[0].product_id = false;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'' +
'' +
'
' +
'' +
'',
});
assert.strictEqual($('.o_kanban_record:not(.o_kanban_ghost)').length, 4,
"there should be 4 records");
assert.strictEqual($('.o_kanban_record:not(.o_kanban_ghost) .btn_a').length, 1,
"only 1 of them should have the 'Action' button");
assert.strictEqual($('.o_kanban_record:not(.o_kanban_ghost) .btn_b').length, 2,
"only 2 of them should have the 'Action' button");
kanban.destroy();
});
QUnit.test('resequence columns in grouped by m2o', async function (assert) {
assert.expect(6);
this.data.product.fields.sequence = {string: "Sequence", type: "integer"};
var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
assert.hasClass(kanban.$('.o_legacy_kanban_view'),'ui-sortable',
"columns should be sortable");
assert.containsN(kanban, '.o_kanban_group', 2,
"should have two columns");
assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 3,
"first column should be id 3 before resequencing");
assert.deepEqual(kanban.exportState().resIds, envIDs);
// there is a 100ms delay on the d&d feature (jquery sortable) for
// kanban columns, making it hard to test. So we rather bypass the d&d
// for this test, and directly call the event handler
envIDs = [2, 4, 1, 3]; // the columns will be inverted
kanban._onResequenceColumn({data: {ids: [5, 3]}});
await nextTick(); // wait for resequencing before re-rendering
await kanban.update({}, {reload: false}); // re-render without reloading
assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 5,
"first column should be id 5 after resequencing");
assert.deepEqual(kanban.exportState().resIds, envIDs);
kanban.destroy();
});
QUnit.test('properly evaluate more complex domains', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'' +
'
' +
'' +
'' +
'',
});
assert.containsOnce(kanban, 'button.oe_kanban_action_button',
"only one button should be visible");
kanban.destroy();
});
QUnit.test('edit the kanban color with the colorpicker', async function (assert) {
assert.expect(5);
var writeOnColor;
this.data.category.records[0].color = 12;
var kanban = await createView({
View: KanbanView,
model: 'category',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'' +
'' +
'',
mockRPC: function (route, args) {
if (args.method === 'write' && 'color' in args.args[1]) {
writeOnColor = true;
}
return this._super.apply(this, arguments);
},
});
var $firstRecord = kanban.$('.o_kanban_record:first()');
assert.containsNone(kanban, '.o_kanban_record.oe_kanban_color_12',
"no record should have the color 12");
assert.strictEqual($firstRecord.find('.oe_kanban_colorpicker').length, 1,
"there should be a color picker");
assert.strictEqual($firstRecord.find('.oe_kanban_colorpicker').children().length, 12,
"the color picker should have 12 children (the colors)");
// Set a color
testUtils.kanban.toggleRecordDropdown($firstRecord);
await testUtils.dom.click($firstRecord.find('.oe_kanban_colorpicker a.oe_kanban_color_9'));
assert.ok(writeOnColor, "should write on the color field");
$firstRecord = kanban.$('.o_kanban_record:first()'); // First record is reloaded here
assert.ok($firstRecord.is('.oe_kanban_color_9'),
"the first record should have the color 9");
kanban.destroy();
});
QUnit.test('load more records in column', async function (assert) {
assert.expect(13);
var envIDs = [1, 2, 4]; // the ids that should be in the environment during this test
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
viewOptions: {
limit: 2,
},
mockRPC: function (route, args) {
if (route === '/web/dataset/search_read') {
assert.step(args.limit + ' - ' + args.offset);
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 2,
"there should be 2 records in the column");
assert.deepEqual(kanban.exportState().resIds, envIDs);
// load more
envIDs = [1, 2, 3, 4]; // id 3 will be loaded
await testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more'));
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3,
"there should now be 3 records in the column");
assert.verifySteps(['2 - undefined', '2 - undefined', '2 - 2'],
"the records should be correctly fetched");
assert.deepEqual(kanban.exportState().resIds, envIDs);
// reload
await kanban.reload();
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3,
"there should still be 3 records in the column after reload");
assert.deepEqual(kanban.exportState().resIds, envIDs);
assert.verifySteps(['4 - undefined', '2 - undefined']);
kanban.destroy();
});
QUnit.test('load more records in column with x2many', async function (assert) {
assert.expect(10);
this.data.partner.records[0].category_ids = [7];
this.data.partner.records[1].category_ids = [];
this.data.partner.records[2].category_ids = [6];
this.data.partner.records[3].category_ids = [];
// record [2] will be loaded after
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
viewOptions: {
limit: 2,
},
mockRPC: function (route, args) {
if (args.model === 'category' && args.method === 'read') {
assert.step(String(args.args[0]));
}
if (route === '/web/dataset/search_read') {
if (args.limit) {
assert.strictEqual(args.limit, 2,
"the limit should be correctly set");
}
if (args.offset) {
assert.strictEqual(args.offset, 2,
"the offset should be correctly set at load more");
}
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 2,
"there should be 2 records in the column");
assert.verifySteps(['7'], "only the appearing category should be fetched");
// load more
await testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more'));
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3,
"there should now be 3 records in the column");
assert.verifySteps(['6'], "the other categories should not be fetched");
kanban.destroy();
});
QUnit.test('update buttons after column creation', async function (assert) {
assert.expect(2);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'',
groupBy: ['product_id'],
});
assert.isNotVisible(kanban.$buttons.find('.o-kanban-button-new'),
"Create button should be hidden");
await testUtils.dom.click(kanban.$('.o_column_quick_create'));
kanban.$('.o_column_quick_create input').val('new column');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
assert.isVisible(kanban.$buttons.find('.o-kanban-button-new'),
"Create button should now be visible");
kanban.destroy();
});
QUnit.test('group_by_tooltip option when grouping on a many2one', async function (assert) {
assert.expect(12);
delete this.data.partner.records[3].product_id;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'',
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/product/read') {
assert.strictEqual(args.args[0].length, 2,
"read on two groups");
assert.deepEqual(args.args[1], ['display_name', 'name'],
"should read on specified fields on the group by relation");
}
return this._super.apply(this, arguments);
},
});
assert.hasClass(kanban.$('.o_legacy_kanban_view'),'o_kanban_grouped',
"should have classname 'o_kanban_grouped'");
assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns");
// simulate an update coming from the searchview, with another groupby given
await kanban.update({groupBy: ['product_id']});
assert.containsN(kanban, '.o_kanban_group', 3, "should have " + 3 + " columns");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1,
"column should contain 1 record(s)");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2,
"column should contain 2 record(s)");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(3) .o_kanban_record').length, 1,
"column should contain 1 record(s)");
assert.ok(kanban.$('.o_kanban_group:first span.o_column_title:contains(None)').length,
"first column should have a default title for when no value is provided");
assert.ok(!kanban.$('.o_kanban_group:first .o_kanban_header_title').data('original-title'),
"tooltip of first column should not defined, since group_by_tooltip title and the many2one field has no value");
assert.ok(kanban.$('.o_kanban_group:eq(1) span.o_column_title:contains(hello)').length,
"second column should have a title with a value from the many2one");
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_header_title').data('bs-original-title'),
"
Kikouhello
",
"second column should have a tooltip with the group_by_tooltip title and many2one field value");
kanban.destroy();
});
QUnit.test('move a record then put it again in the same column', async function (assert) {
assert.expect(6);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['product_id'],
});
await testUtils.dom.click(kanban.$('.o_column_quick_create'));
kanban.$('.o_column_quick_create input').val('column1');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
await testUtils.dom.click(kanban.$('.o_column_quick_create'));
kanban.$('.o_column_quick_create input').val('column2');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
await testUtils.dom.click(kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_add i'));
var $quickCreate = kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'new partner');
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 0,
"column should contain 0 record");
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 1,
"column should contain 1 records");
var $record = kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(0)');
var $group = kanban.$('.o_kanban_group:eq(0)');
await testUtils.dom.dragAndDrop($record, $group);
await nextTick(); // wait for resequencing after drag and drop
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 1,
"column should contain 1 records");
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 0,
"column should contain 0 records");
$record = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)');
$group = kanban.$('.o_kanban_group:eq(1)');
await testUtils.dom.dragAndDrop($record, $group);
await nextTick(); // wait for resequencing after drag and drop
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 0,
"column should contain 0 records");
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 1,
"column should contain 1 records");
kanban.destroy();
});
QUnit.test('resequence a record twice', async function (assert) {
assert.expect(10);
this.data.partner.records = [];
var nbResequence = 0;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['product_id'],
mockRPC: function (route) {
if (route === '/web/dataset/resequence') {
nbResequence++;
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
await testUtils.dom.click(kanban.$('.o_column_quick_create'));
kanban.$('.o_column_quick_create input').val('column1');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
await testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i'));
var $quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'record1');
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
await testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i'));
$quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('input'), 'record2');
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2,
"column should contain 2 records");
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)').text(), "record2",
"records should be correctly ordered");
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)').text(), "record1",
"records should be correctly ordered");
var $record1 = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)');
var $record2 = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)');
await testUtils.dom.dragAndDrop($record1, $record2, {position: 'top'});
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2,
"column should contain 2 records");
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)').text(), "record1",
"records should be correctly ordered");
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)').text(), "record2",
"records should be correctly ordered");
await testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'});
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2,
"column should contain 2 records");
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)').text(), "record2",
"records should be correctly ordered");
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)').text(), "record1",
"records should be correctly ordered");
assert.strictEqual(nbResequence, 2, "should have resequenced twice");
kanban.destroy();
});
QUnit.test('basic support for widgets', async function (assert) {
// This test could be removed as soon as we drop the support of legacy widgets (see test
// below, which is a duplicate of this one, but with an Owl Component instead).
assert.expect(1);
var MyWidget = Widget.extend({
init: function (parent, dataPoint) {
this.data = dataPoint.data;
},
start: function () {
this.$el.text(JSON.stringify(this.data));
},
});
widgetRegistry.add('test', MyWidget);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'
' +
'' +
'' +
'' +
'
' +
'',
});
assert.strictEqual(kanban.$('.o_widget:eq(2)').text(), '{"foo":"gnap","id":3}',
"widget should have been instantiated");
kanban.destroy();
delete widgetRegistry.map.test;
});
QUnit.test('basic support for widgets (being Owl Components)', async function (assert) {
assert.expect(1);
class MyComponent extends LegacyComponent {
get value() {
return JSON.stringify(this.props.record.data);
}
}
MyComponent.template = xml``;
widgetRegistryOwl.add('test', MyComponent);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
});
assert.strictEqual(kanban.$('.o_widget:eq(2)').text(), '{"foo":"gnap","id":3}');
kanban.destroy();
delete widgetRegistryOwl.map.test;
});
QUnit.test('subwidgets with on_attach_callback when changing record color', async function (assert) {
assert.expect(3);
var counter = 0;
var MyTestWidget = AbstractField.extend({
on_attach_callback: function () {
counter++;
},
});
fieldRegistry.add('test_widget', MyTestWidget);
var kanban = await createView({
View: KanbanView,
model: 'category',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'' +
'' +
'',
});
// counter should be 2 as there are 2 records
assert.strictEqual(counter, 2, "on_attach_callback should have been called twice");
// set a color to kanban record
var $firstRecord = kanban.$('.o_kanban_record:first()');
testUtils.kanban.toggleRecordDropdown($firstRecord);
await testUtils.dom.click($firstRecord.find('.oe_kanban_colorpicker a.oe_kanban_color_9'));
// first record has replaced its $el with a new one
$firstRecord = kanban.$('.o_kanban_record:first()');
assert.hasClass($firstRecord, 'oe_kanban_color_9');
assert.strictEqual(counter, 3, "on_attach_callback method should be called 3 times");
delete fieldRegistry.map.test_widget;
kanban.destroy();
});
QUnit.test('column progressbars properly work', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
});
assert.containsN(kanban, '.o_legacy_kanban_counter', this.data.product.records.length,
"kanban counters should have been created");
assert.strictEqual(parseInt(kanban.$('.o_kanban_counter_side').last().text()), 36,
"counter should display the sum of int_field values");
kanban.destroy();
});
QUnit.test('column progressbars: "false" bar is clickable', async function (assert) {
assert.expect(8);
this.data.partner.records.push({id: 5, bar: true, foo: false, product_id: 5, state: "ghi"});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'',
mockRPC: function (route, args) {
assert.step(route);
return this._super(route, args);
},
});
assert.strictEqual(kanban.$('.o_kanban_record').text(), 'namenamenamename',
"should have renderer 4 records");
assert.verifySteps(['/web/dataset/search_read'], "no read on progress bar data is done");
kanban.destroy();
});
QUnit.test('column progressbars: creating a new column should create a new progressbar', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
});
var nbProgressBars = kanban.$('.o_legacy_kanban_counter').length;
// Create a new column: this should create an empty progressbar
var $columnQuickCreate = kanban.$('.o_column_quick_create');
await testUtils.dom.click($columnQuickCreate.find('.o_quick_create_folded'));
$columnQuickCreate.find('input').val('test');
await testUtils.dom.click($columnQuickCreate.find('.btn-primary'));
assert.containsN(kanban, '.o_legacy_kanban_counter', nbProgressBars + 1,
"a new column with a new column progressbar should have been created");
kanban.destroy();
});
QUnit.test('column progressbars on quick create properly update counter', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
});
var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
await testUtils.dom.click(kanban.$('.o_kanban_quick_add:first'));
await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'Test');
await testUtils.dom.click(kanban.$('.o_kanban_add'));
var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
await nextTick(); // await update
await nextTick(); // await read
assert.strictEqual(lastCount, initialCount + 1,
"kanban counters should have updated on quick create");
kanban.destroy();
});
QUnit.test('column progressbars are working with load more', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
domain: [['bar', '=', true]],
arch:
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
});
// we have 1 record shown, load 2 more and check it worked
await testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more'));
await testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more'));
var shownIDs = _.map(kanban.$('.o_kanban_record'), function(record) {
return parseInt(record.innerText);
});
assert.deepEqual(shownIDs, [1, 2, 3], "intended records are loaded");
kanban.destroy();
});
QUnit.test('column progressbars with an active filter are working with load more', async function (assert) {
assert.expect(2);
this.data.partner.records.push(
{ id: 5, bar: true, foo: "blork" },
{ id: 6, bar: true, foo: "blork" },
{ id: 7, bar: true, foo: "blork" }
);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
domain: [['bar', '=', true]],
arch:
`
`,
groupBy: ['bar'],
});
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_counter_progress .progress-bar[data-filter="blork"]'));
// we should have 1 record shown
assert.deepEqual(
[...kanban.el.querySelectorAll('.o_kanban_record')].map(el => parseInt(el.innerText)),
[5]
);
// load 2 more and check it worked
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group .o_kanban_load_more'));
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group .o_kanban_load_more'));
assert.deepEqual(
[...kanban.el.querySelectorAll('.o_kanban_record')].map(el => parseInt(el.innerText)),
[5, 6, 7]
);
kanban.destroy();
});
QUnit.test('column progressbars on archiving records update counter', async function (assert) {
assert.expect(4);
// add active field on partner model and make all records active
this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/action_archive') {
var partnerIDS = args.args[0];
var records = this.data.partner.records;
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
return record.id === partnerID;
}).active = false;
});
this.data.partner.records[0].active;
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_side').text(), "36",
"counter should contain the correct value");
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_progress > .progress-bar:first').data('bsOriginalTitle'), "1 yop",
"the counter progressbars should be correctly displayed");
// archive all records of the second columns
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:eq(1)'));
await testUtils.dom.click(kanban.$('.o_column_archive_records:visible'));
await testUtils.dom.click($('.modal-footer button:first'));
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_side').text(), "0",
"counter should contain the correct value");
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_progress > .progress-bar:first').data('bsOriginalTitle'), "0 yop",
"the counter progressbars should have been correctly updated");
kanban.destroy();
});
QUnit.test('kanban with progressbars: correctly update env when archiving records', async function (assert) {
assert.expect(2);
// add active field on partner model and make all records active
this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['bar'],
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/action_archive') {
var partnerIDS = args.args[0];
var records = this.data.partner.records
_.each(partnerIDS, function(partnerID) {
_.find(records, function (record) {
return record.id === partnerID;
}).active = false;
})
this.data.partner.records[0].active;
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
assert.deepEqual(kanban.exportState().resIds, [1, 2, 3, 4]);
// archive all records of the first column
testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first'));
await testUtils.dom.click(kanban.$('.o_column_archive_records:visible'));
await testUtils.dom.click($('.modal-footer button:first'));
assert.deepEqual(kanban.exportState().resIds, [1, 2, 3]);
kanban.destroy();
});
QUnit.test('RPCs when (re)loading kanban view progressbars', async function (assert) {
assert.expect(9);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'' +
'' +
'
`,
groupBy: ['bar'],
mockRPC(route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
});
// Activate "yop" on second column
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .progress-bar[data-filter="yop"]'));
// Activate "gnap" on second column
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .progress-bar[data-filter="gnap"]'));
// Deactivate "gnap" on second column
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .progress-bar[data-filter="gnap"]'));
assert.verifySteps([
// initial load
'web_read_group',
'read_progress_bar',
'/web/dataset/search_read',
'/web/dataset/search_read',
// activate filter
'/web/dataset/search_read',
// activate another filter (switching)
'/web/dataset/search_read',
// deactivate active filter
'/web/dataset/search_read',
]);
kanban.destroy();
});
QUnit.test('drag & drop records grouped by m2o with progressbar', async function (assert) {
assert.expect(4);
this.data.partner.records[0].product_id = false;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
return Promise.resolve(true);
}
return this._super(route, args);
},
});
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "1",
"counter should contain the correct value");
await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)'));
await nextTick(); // wait for update resulting from drag and drop
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "0",
"counter should contain the correct value");
await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(2)'), kanban.$('.o_kanban_group:eq(0)'));
await nextTick(); // wait for update resulting from drag and drop
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "1",
"counter should contain the correct value");
await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)'));
await nextTick(); // wait for update resulting from drag and drop
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "0",
"counter should contain the correct value");
kanban.destroy();
});
QUnit.test('progress bar subgroup count recompute', async function (assert) {
assert.expect(2);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
`
`,
groupBy: ['bar'],
});
let secondCounter = kanban.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_counter_side');
assert.strictEqual(parseInt(secondCounter.innerText), 3,
"Initial count should be Three");
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .bg-success'));
secondCounter = kanban.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_counter_side');
assert.strictEqual(parseInt(secondCounter.innerText), 1,
"kanban counters should vary according to what subgroup is selected");
kanban.destroy();
});
QUnit.test('progress bar recompute after drag&drop to and from other column', async function (assert) {
assert.expect(4);
const view = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
`
`,
groupBy: ['bar'],
});
assert.deepEqual(
[...view.el.querySelectorAll('.progress-bar')].map(el => el.getAttribute('data-bs-original-title')),
['0 yop', '0 gnap', '1 blip', '0 __false', '1 yop', '1 gnap', '1 blip', '0 __false']
);
assert.deepEqual(
[...view.el.querySelectorAll('.o_kanban_counter_side')].map(el => parseInt(el.innerText)),
[1, 3]
);
// Drag the last kanban record to the first column
await testUtils.dom.dragAndDrop(
[...view.el.querySelectorAll('.o_kanban_record')].pop(),
[...view.el.querySelectorAll('.o_kanban_group')].shift()
);
assert.deepEqual(
[...view.el.querySelectorAll('.progress-bar')].map(el => el.getAttribute('data-bs-original-title')),
['0 yop', '1 gnap', '1 blip', '0 __false', '1 yop', '0 gnap', '1 blip', '0 __false']
);
assert.deepEqual(
[...view.el.querySelectorAll('.o_kanban_counter_side')].map(el => parseInt(el.innerText)),
[2, 2]
);
view.destroy();
});
QUnit.test('load more should load correct records after drag&drop event', async function (assert) {
assert.expect(3);
// Add a sequence number and initialize
this.data.partner.fields = Object.assign(this.data.partner.fields, {
sequence: { type: 'integer' }
});
this.data.partner.records.forEach((el, i) => {
el.sequence = i;
});
const view = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
`
`,
groupBy: ['bar'],
favoriteFilters: [
{
domain: '[]',
is_default: true,
sort: '["sequence asc"]',
},
],
});
assert.deepEqual(
[...view.el.querySelectorAll('.o_kanban_group:nth-child(1) .o_kanban_record span')]
.map(el => parseInt(el.innerText))[0],
4,
"first column's first record must be id 4"
);
assert.deepEqual(
[...view.el.querySelectorAll('.o_kanban_group:nth-child(2) .o_kanban_record span')]
.map(el => parseInt(el.innerText)),
[1],
"second column's records should be only the id 1"
);
// Drag the first kanban record on top of the last
await testUtils.dom.dragAndDrop(
[...view.el.querySelectorAll('.o_kanban_record')].shift(),
[...view.el.querySelectorAll('.o_kanban_record')].pop(),
{ position: 'top' }
);
// load more twice to load all records of second column
await testUtils.dom.click(view.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_load_more'));
await testUtils.dom.click(view.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_load_more'));
// Check records of the second column
assert.deepEqual(
[...view.el.querySelectorAll('.o_kanban_group:nth-child(2) .o_kanban_record span')].map(el => parseInt(el.innerText)),
[4, 1, 2, 3]
);
view.destroy();
});
QUnit.test('column progressbars on quick create with quick_create_view are updated', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'' +
'',
archs: {
'partner,some_view_ref,form': '',
},
groupBy: ['bar'],
});
var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
await testUtils.kanban.clickCreate(kanban);
// fill the quick create and validate
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '44');
await testUtils.dom.click($quickCreate.find('button.o_kanban_add'));
var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
assert.strictEqual(lastCount, initialCount + 44,
"kanban counters should have been updated on quick create");
kanban.destroy();
});
QUnit.test('column progressbars and active filter on quick create with quick_create_view are updated', async function (assert) {
assert.expect(2);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner,some_view_ref,form': `
`,
},
groupBy: ['bar'],
});
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(1) .progress-bar[data-filter="blip"]'));
const initialCount = parseInt(kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_counter_side').innerText);
assert.strictEqual(initialCount, -4, "Initial count should be -4");
// open the quick create
await testUtils.kanban.clickCreate(kanban);
// fill it with a record that satisfies the activated filter
let quickCreate = kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_quick_create');
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="int_field"]'), '44');
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="foo"]'), 'blip');
await testUtils.dom.click(quickCreate.querySelector('button.o_kanban_add'));
// fill it again with another record that DOES NOT satisfies the activated filter
quickCreate = kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_quick_create');
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="int_field"]'), '1000');
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="foo"]'), 'yop');
await testUtils.dom.click(quickCreate.querySelector('button.o_kanban_add'));
// check counter
const lastCount = parseInt(kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_counter_side').innerText);
assert.strictEqual(lastCount, initialCount + 44,
"kanban counters should have been updated on quick create, respecting the activated filter");
kanban.destroy();
});
QUnit.test('keep adding quickcreate in first column after a record from this column was moved', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'' +
'' +
'' +
'
' +
'' +
'',
groupBy: ['foo'],
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
return Promise.resolve(true);
}
return this._super(route, args);
},
});
var $quickCreateGroup;
var $groups;
await _quickCreateAndTest();
await testUtils.dom.dragAndDrop($groups.first().find('.o_kanban_record:first'), $groups.eq(1));
await _quickCreateAndTest();
kanban.destroy();
async function _quickCreateAndTest() {
await testUtils.kanban.clickCreate(kanban);
$quickCreateGroup = kanban.$('.o_kanban_quick_create').closest('.o_kanban_group');
$groups = kanban.$('.o_kanban_group');
assert.strictEqual($quickCreateGroup[0], $groups[0],
"quick create should have been added in the first column");
}
});
QUnit.test('test displaying image (URL, image field not set)', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'
' +
'' +
'
' +
'',
});
// since the field image is not set, kanban_image will generate an URL
var imageOnRecord = kanban.$('img[data-src*="/web/image"][data-src*="&id=1"]');
assert.strictEqual(imageOnRecord.length, 1, "partner with image display image by url");
kanban.destroy();
});
QUnit.test('test displaying image (__last_update field)', async function (assert) {
// the presence of __last_update field ensures that the image is reloaded when necessary
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
' +
'',
});
var images = kanban.el.querySelectorAll('img');
var placeholders = [];
for (var [index, img] of images.entries()) {
if (img.dataset.src.indexOf(this.data.partner.records[index].image) === -1) {
// Then we display a placeholder
placeholders.push(img);
}
}
assert.strictEqual(placeholders.length, this.data.partner.records.length - 1,
"partner with no image should display the placeholder");
assert.strictEqual(images[0].dataset.src, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==",
"The first partners non-placeholder image should be set");
kanban.destroy();
});
QUnit.test('test displaying image (for another record)', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'' +
'
' +
'',
});
// the field image is set, but we request the image for a specific id
// -> for the record matching the ID, the base64 should be returned
// -> for all the other records, the image should be displayed by url
var imageOnRecord = kanban.$('img[data-src*="/web/image"][data-src*="&id=1"]');
assert.strictEqual(imageOnRecord.length, this.data.partner.records.length - 1,
"display image by url when requested for another record");
assert.strictEqual(kanban.el.querySelector("img").dataset.src,
"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==",
"display image as value when requested for the record itself");
kanban.destroy();
});
QUnit.test("test displaying image from m2o field (m2o field not set)", async function (assert) {
assert.expect(2);
this.data.foo_partner = {
fields: {
name: {string: "Foo Name", type: "char"},
partner_id: {string: "Partner", type: "many2one", relation: "partner"},
},
records: [
{id: 1, name: 'foo_with_partner_image', partner_id: 1},
{id: 2, name: 'foo_no_partner'},
]
};
const kanban = await createView({
View: KanbanView,
model: "foo_partner",
data: this.data,
arch: `
`,
});
assert.containsOnce(kanban, 'img[data-src*="/web/image"][data-src$="&id=1"]', "image url should contain id of set partner_id");
assert.containsOnce(kanban, 'img[data-src*="/web/image"][data-src$="&id="]', "image url should contain an empty id if partner_id is not set");
kanban.destroy();
});
QUnit.test('check if the view destroys all widgets and instances', async function (assert) {
assert.expect(2);
var instanceNumber = 0;
testUtils.mock.patch(mixins.ParentedMixin, {
init: function () {
instanceNumber++;
return this._super.apply(this, arguments);
},
destroy: function () {
if (!this.isDestroyed()) {
instanceNumber--;
}
return this._super.apply(this, arguments);
}
});
var params = {
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'',
};
var kanban = await createView(params);
assert.ok(instanceNumber > 0);
kanban.destroy();
assert.strictEqual(instanceNumber, 0);
testUtils.mock.unpatch(mixins.ParentedMixin);
});
QUnit.test('grouped kanban becomes ungrouped when clearing domain then clearing groupby', async function (assert) {
// in this test, we simulate that clearing the domain is slow, so that
// clearing the groupby does not corrupt the data handled while
// reloading the kanban view.
assert.expect(4);
var prom = makeTestPromise();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
domain: [['foo', '=', 'norecord']],
groupBy: ['bar'],
mockRPC: function (route, args) {
var result = this._super(route, args);
if (args.method === 'web_read_group') {
var isFirstUpdate = _.isEmpty(args.kwargs.domain) &&
args.kwargs.groupby &&
args.kwargs.groupby[0] === 'bar';
if (isFirstUpdate) {
return prom.then(function () {
return result;
});
}
}
return result;
},
});
assert.hasClass(kanban.$('.o_legacy_kanban_view'),'o_kanban_grouped',
"the kanban view should be grouped");
assert.doesNotHaveClass(kanban.$('.o_legacy_kanban_view'), 'o_kanban_ungrouped',
"the kanban view should not be ungrouped");
kanban.update({domain: []}); // 1st update on kanban view
kanban.update({groupBy: []}); // 2n update on kanban view
prom.resolve(); // simulate slow 1st update of kanban view
await nextTick();
assert.doesNotHaveClass(kanban.$('.o_legacy_kanban_view'), 'o_kanban_grouped',
"the kanban view should not longer be grouped");
assert.hasClass(kanban.$('.o_legacy_kanban_view'),'o_kanban_ungrouped',
"the kanban view should have become ungrouped");
kanban.destroy();
});
QUnit.test('quick_create on grouped kanban without column', async function (assert) {
assert.expect(1);
this.data.partner.records = [];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
// force group_create to false, otherwise the CREATE button in control panel is hidden
arch: '' +
'
' +
'' +
'
' +
'',
groupBy: ['product_id'],
intercepts: {
switch_view: function (event) {
assert.ok(true, "switch_view was called instead of quick_create");
},
},
});
await testUtils.kanban.clickCreate(kanban);
kanban.destroy();
});
QUnit.test('keyboard navigation on kanban basic rendering', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'
' +
'' +
'' +
'
' +
'',
});
var $fisrtCard = kanban.$('.o_kanban_record:first');
var $secondCard = kanban.$('.o_kanban_record:eq(1)');
$fisrtCard.focus();
assert.strictEqual(document.activeElement, $fisrtCard[0], "the kanban cards are focussable");
$fisrtCard.trigger($.Event('keydown', { which: $.ui.keyCode.RIGHT, keyCode: $.ui.keyCode.RIGHT, }));
assert.strictEqual(document.activeElement, $secondCard[0], "the second card should be focussed");
$secondCard.trigger($.Event('keydown', { which: $.ui.keyCode.LEFT, keyCode: $.ui.keyCode.LEFT, }));
assert.strictEqual(document.activeElement, $fisrtCard[0], "the first card should be focussed");
kanban.destroy();
});
QUnit.test('keyboard navigation on kanban grouped rendering', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['bar'],
});
var $firstColumnFisrtCard = kanban.$('.o_kanban_record:first');
var $secondColumnFirstCard = kanban.$('.o_kanban_group:eq(1) .o_kanban_record:first');
var $secondColumnSecondCard = kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(1)');
$firstColumnFisrtCard.focus();
//RIGHT should select the next column
$firstColumnFisrtCard.trigger($.Event('keydown', { which: $.ui.keyCode.RIGHT, keyCode: $.ui.keyCode.RIGHT, }));
assert.strictEqual(document.activeElement, $secondColumnFirstCard[0], "RIGHT should select the first card of the next column");
//DOWN should move up one card
$secondColumnFirstCard.trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN, }));
assert.strictEqual(document.activeElement, $secondColumnSecondCard[0], "DOWN should select the second card of the current column");
//LEFT should go back to the first column
$secondColumnSecondCard.trigger($.Event('keydown', { which: $.ui.keyCode.LEFT, keyCode: $.ui.keyCode.LEFT, }));
assert.strictEqual(document.activeElement, $firstColumnFisrtCard[0], "LEFT should select the first card of the first column");
kanban.destroy();
});
QUnit.test('keyboard navigation on kanban grouped rendering with empty columns', async function (assert) {
assert.expect(2);
var data = this.data;
data.partner.records[1].state = "abc";
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: data,
arch: '' +
'' +
'' +
'
' +
'',
groupBy: ['state'],
mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
// override read_group to return empty groups, as this is
// the case for several models (e.g. project.task grouped
// by stage_id)
return this._super.apply(this, arguments).then(function (result) {
// add 2 empty columns in the middle
result.groups.splice(1, 0, {state_count: 0, state: 'def',
__domain: [["state", "=", "def"]]});
result.groups.splice(1, 0, {state_count: 0, state: 'def',
__domain: [["state", "=", "def"]]});
// add 1 empty column in the beginning and the end
result.groups.unshift({state_count: 0, state: 'def',
__domain: [["state", "=", "def"]]});
result.groups.push({state_count: 0, state: 'def',
__domain: [["state", "=", "def"]]});
return result;
});
}
return this._super.apply(this, arguments);
},
});
/**
* DEF columns are empty
*
* | DEF | ABC | DEF | DEF | GHI | DEF
* |-----|------|-----|-----|------|-----
* | | yop | | | gnap |
* | | blip | | | blip |
*/
var $yop = kanban.$('.o_kanban_record:first');
var $gnap = kanban.$('.o_kanban_group:eq(4) .o_kanban_record:first');
$yop.focus();
//RIGHT should select the next column that has a card
$yop.trigger($.Event('keydown', { which: $.ui.keyCode.RIGHT,
keyCode: $.ui.keyCode.RIGHT, }));
assert.strictEqual(document.activeElement, $gnap[0],
"RIGHT should select the first card of the next column that has a card");
//LEFT should go back to the first column that has a card
$gnap.trigger($.Event('keydown', { which: $.ui.keyCode.LEFT,
keyCode: $.ui.keyCode.LEFT, }));
assert.strictEqual(document.activeElement, $yop[0],
"LEFT should select the first card of the first column that has a card");
kanban.destroy();
});
QUnit.test('keyboard navigation on kanban when the focus is on a link that ' +
'has an action and the kanban has no oe_kanban_global_... class', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '' +
'
`,
domain: [['id', 'in', [11, 12]]],
});
assert.containsOnce(kanban, '.o_kanban_record:first div.test',
"the container is displayed if description have actual content");
assert.strictEqual(kanban.$('.o_kanban_record:first div.test span.text-info').html().trim(), 'hello',
"the inner html content is rendered properly");
assert.containsNone(kanban, '.o_kanban_record:last div.test',
"the container is not displayed if description just have formatting tags and no actual content");
kanban.destroy();
});
QUnit.test("progressbar filter state is kept unchanged when domain is updated (records still in group)", async function (assert) {
assert.expect(16);
const kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch: `
`,
});
// Check that we have 2 columns and check their progressbar's state
assert.containsN(kanban.el, ".o_kanban_group", 2);
assert.containsNone(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_column_title")].map(el => el.innerText),
["No", "Yes"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "1 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "1 blip", "1 __false"],
);
// Apply an active filter
await testUtils.dom.click(kanban.el.querySelector(".o_kanban_group:nth-child(2) .progress-bar[data-filter=yop]"));
assert.containsOnce(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.strictEqual(kanban.el.querySelector(".o_kanban_group.o_kanban_group_show .o_column_title").innerText, "Yes");
// Add searchdomain to something restricting progressbars' values (records still in filtered group)
await kanban.update({ domain: [["foo", "=", "yop"]] });
// Check that we have now 1 column only and check its progressbar's state
assert.containsOnce(kanban.el, ".o_kanban_group");
assert.containsOnce(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.strictEqual(kanban.el.querySelector(".o_column_title").innerText, "Yes");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "0 blip", "0 __false"],
);
// Undo searchdomain
await kanban.update({ domain: [] });
// Check that we have 2 columns back and check their progressbar's state
assert.containsN(kanban.el, ".o_kanban_group", 2);
assert.containsOnce(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_column_title")].map(el => el.innerText),
["No", "Yes"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "1 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "1 blip", "1 __false"],
);
kanban.destroy();
});
QUnit.test("progressbar filter state is kept unchanged when domain is updated (emptying group)", async function (assert) {
assert.expect(25);
const kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch: `
`,
});
// Check that we have 2 columns, check their progressbar's state and check records
assert.containsN(kanban.el, ".o_kanban_group", 2);
assert.containsNone(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_column_title")].map(el => el.innerText),
["No", "Yes"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "1 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_kanban_record")].map(el => el.innerText),
["4 blip"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "1 blip", "1 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_kanban_record")].map(el => el.innerText),
["1 yop", "2 blip", "3 gnap"],
);
// Apply an active filter
await testUtils.dom.click(kanban.el.querySelector(".o_kanban_group:nth-child(2) .progress-bar[data-filter=yop]"));
assert.containsOnce(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.strictEqual(kanban.el.querySelector(".o_kanban_group.o_kanban_group_show .o_column_title").innerText, "Yes");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group.o_kanban_group_show .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "1 blip", "1 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group.o_kanban_group_show .o_kanban_record")].map(el => el.innerText),
["1 yop"],
);
// Add searchdomain to something restricting progressbars' values + emptying the filtered group
await kanban.update({ domain: [["foo", "=", "blip"]] });
// Check that we still have 2 columns, check their progressbar's state and check records
assert.containsN(kanban.el, ".o_kanban_group", 2);
assert.containsNone(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_column_title")].map(el => el.innerText),
["No", "Yes"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "1 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_kanban_record")].map(el => el.innerText),
["4 blip"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "1 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_kanban_record")].map(el => el.innerText),
["2 blip"],
);
// Undo searchdomain
await kanban.update({ domain: [] });
// Check that we still have 2 columns and check their progressbar's state
assert.containsN(kanban.el, ".o_kanban_group", 2);
assert.containsNone(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_column_title")].map(el => el.innerText),
["No", "Yes"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "1 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_kanban_record")].map(el => el.innerText),
["4 blip"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "1 blip", "1 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_kanban_record")].map(el => el.innerText),
["1 yop", "2 blip", "3 gnap"],
);
kanban.destroy();
});
QUnit.test("filtered column keeps consistent counters when dropping in a non-matching record", async function (assert) {
assert.expect(19);
const kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch: `
`,
});
// Check that we have 2 columns, check their progressbar's state, and check records
assert.containsN(kanban.el, ".o_kanban_group", 2);
assert.containsNone(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_column_title")].map(el => el.innerText),
["No", "Yes"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "1 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_kanban_record")].map(el => el.innerText),
["4 blip"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "1 blip", "1 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_kanban_record")].map(el => el.innerText),
["1 yop", "2 blip", "3 gnap"],
);
// Apply an active filter
await testUtils.dom.click(kanban.el.querySelector(".o_kanban_group:nth-child(2) .progress-bar[data-filter=yop]"));
assert.hasClass(kanban.el.querySelector(".o_kanban_group:nth-child(2)"), "o_kanban_group_show");
assert.containsOnce(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.strictEqual(kanban.el.querySelector(".o_kanban_group.o_kanban_group_show .o_column_title").innerText, "Yes");
assert.containsOnce(kanban.el, ".o_kanban_group.o_kanban_group_show .o_kanban_record");
assert.strictEqual(kanban.el.querySelector(".o_kanban_group.o_kanban_group_show .o_kanban_record").innerText, "1 yop");
// Drop in the non-matching record from first column
await testUtils.dom.dragAndDrop(
kanban.el.querySelector(".o_kanban_group:nth-child(1) .o_kanban_record"),
kanban.el.querySelector(".o_kanban_group.o_kanban_group_show")
);
// Check that we have 2 columns, check their progressbar's state, and check records
assert.containsN(kanban.el, ".o_kanban_group", 2);
assert.containsOnce(kanban.el, ".o_kanban_group.o_kanban_group_show");
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_column_title")].map(el => el.innerText),
["No", "Yes"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["0 yop", "0 blip", "0 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(1) .o_kanban_record")].map(el => el.innerText),
[],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_legacy_kanban_counter .progress-bar")].map(el => el.dataset.bsOriginalTitle),
["1 yop", "2 blip", "1 __false"],
);
assert.deepEqual(
[...kanban.el.querySelectorAll(".o_kanban_group:nth-child(2) .o_kanban_record")].map(el => el.innerText),
["1 yop", "4 blip"],
);
kanban.destroy();
});
QUnit.test("filtered column is reloaded when dragging out its last record", async function (assert) {
assert.expect(33);
const kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch: `