Files
odoo_source/addons/web/static/tests/views/kanban_tests.js
T
Prakash PrajapatiandAaron Bohy b9c5ae27bb [FIX] web: kanban: reload kanban image
Before this commit, if the image changed from the form view, it
wasn't updated when coming back to the kanban view (one must
refresh the page).

This commit makes this work by automatically adding the field
'__last_update' to the list of fields to read when there is an
image with kanban_image src in the template.

closes odoo/odoo#58313

Taskid: 2341493
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Co-authored-by: Aaron Bohy <aab@odoo.com>
2020-12-21 12:45:29 +00:00

7455 lines
310 KiB
JavaScript

odoo.define('web.kanban_tests', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
var fieldRegistry = require('web.field_registry');
const FormRenderer = require("web.FormRenderer");
var KanbanColumnProgressBar = require('web.KanbanColumnProgressBar');
var kanbanExamplesRegistry = require('web.kanban_examples_registry');
var KanbanRenderer = require('web.KanbanRenderer');
var KanbanView = require('web.KanbanView');
var mixins = require('web.mixins');
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
var widgetRegistry = require('web.widget_registry');
var makeTestPromise = testUtils.makeTestPromise;
var nextTick = testUtils.nextTick;
const cpHelpers = testUtils.controlPanel;
var createView = testUtils.createView;
QUnit.module('Views', {
before: function () {
this._initialKanbanProgressBarAnimate = KanbanColumnProgressBar.prototype.ANIMATE;
KanbanColumnProgressBar.prototype.ANIMATE = false;
},
after: function () {
KanbanColumnProgressBar.prototype.ANIMATE = this._initialKanbanProgressBarAnimate;
},
beforeEach: function () {
this.data = {
partner: {
fields: {
foo: {string: "Foo", type: "char"},
bar: {string: "Bar", type: "boolean"},
int_field: {string: "int_field", type: "integer", sortable: true},
qux: {string: "my float", type: "float"},
product_id: {string: "something_id", type: "many2one", relation: "product"},
category_ids: { string: "categories", type: "many2many", relation: 'category'},
state: { string: "State", type: "selection", selection: [["abc", "ABC"], ["def", "DEF"], ["ghi", "GHI"]]},
date: {string: "Date Field", type: 'date'},
datetime: {string: "Datetime Field", type: 'datetime'},
image: {string: "Image", type: "binary"},
displayed_image_id: {string: "cover", type: "many2one", relation: "ir.attachment"},
currency_id: {string: "Currency", type: "many2one", relation: "currency", default: 1},
salary: {string: "Monetary field", type: "monetary"},
},
records: [
{id: 1, bar: true, foo: "yop", int_field: 10, qux: 0.4, product_id: 3, state: "abc", category_ids: [], 'image': 'R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==', salary: 1750, currency_id: 1},
{id: 2, bar: true, foo: "blip", int_field: 9, qux: 13, product_id: 5, state: "def", category_ids: [6], salary: 1500, currency_id: 1},
{id: 3, bar: true, foo: "gnap", int_field: 17, qux: -3, product_id: 3, state: "ghi", category_ids: [7], salary: 2000, currency_id: 2},
{id: 4, bar: false, foo: "blip", int_field: -4, qux: 9, product_id: 5, state: "ghi", category_ids: [], salary: 2222, currency_id: 1},
]
},
product: {
fields: {
id: {string: "ID", type: "integer"},
name: {string: "Display Name", type: "char"},
},
records: [
{id: 3, name: "hello"},
{id: 5, name: "xmo"},
]
},
category: {
fields: {
name: {string: "Category Name", type: "char"},
color: {string: "Color index", type: "integer"},
},
records: [
{id: 6, name: "gold", color: 2},
{id: 7, name: "silver", color: 5},
]
},
'ir.attachment': {
fields: {
mimetype: {type: "char"},
name: {type: "char"},
res_model: {type: "char"},
res_id: {type: "integer"},
},
records: [
{id: 1, name: "1.png", mimetype: 'image/png', res_model: 'partner', res_id: 1},
{id: 2, name: "2.png", mimetype: 'image/png', res_model: 'partner', res_id: 2},
]
},
'currency': {
fields: {
symbol: {string: "Symbol", type: "char"},
position: {
string: "Position",
type: "selection",
selection: [['after', 'A'], ['before', 'B']],
},
},
records: [
{id: 1, display_name: "USD", symbol: '$', position: 'before'},
{id: 2, display_name: "EUR", symbol: '€', position: 'after'},
],
},
};
},
}, function () {
QUnit.module('KanbanView');
QUnit.test('basic ungrouped rendering', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div>' +
'<t t-esc="record.foo.value"/>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates></kanban>',
mockRPC: function (route, args) {
assert.ok(args.context.bin_size,
"should not request direct binary payload");
return this._super(route, args);
},
});
assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_ungrouped');
assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_test');
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4);
assert.containsN(kanban,'.o_kanban_ghost', 6);
assert.containsOnce(kanban, '.o_kanban_record:contains(gnap)');
kanban.destroy();
});
QUnit.test('basic grouped rendering', async function (assert) {
assert.expect(13);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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_kanban_view'), 'o_kanban_grouped');
assert.hasClass(kanban.$('.o_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: '<kanban class="o_kanban_test">' +
'<field name="active"/>' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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 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: '<kanban class="o_kanban_test" archivable="true">' +
'<field name="active"/>' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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");
envIDs = [4];
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 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: '<kanban class="o_kanban_test" archivable="false">' +
'<field name="active"/>' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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('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: '<kanban>' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div>' +
'<t t-if="context.some_key">' +
'<field name="foo"/>' +
'</t>' +
'</div>' +
'</t>' +
'</templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
groupBy: ['bar'],
});
assert.containsNone(kanban, '.o_pager');
kanban.destroy();
});
QUnit.test('pager, ungrouped, with default limit', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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(cpHelpers.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: '<kanban class="o_kanban_test">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
mockRPC: function (route, args) {
assert.strictEqual(args.limit, 2, "limit should be 2");
return this._super.apply(this, arguments);
},
viewOptions: {
limit: 2,
},
});
assert.strictEqual(cpHelpers.getPagerValue(kanban), "1-2", "pager's limit should be 2");
assert.strictEqual(cpHelpers.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: '<kanban class="o_kanban_test" limit="3">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
mockRPC: function (route, args) {
assert.strictEqual(args.limit, 3, "limit should be 3");
return this._super.apply(this, arguments);
},
viewOptions: {
limit: 2,
},
});
assert.strictEqual(cpHelpers.getPagerValue(kanban), "1-3", "pager's limit should be 3");
assert.strictEqual(cpHelpers.getPagerSize(kanban), "4", "pager's size should be 4");
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
});
assert.hasClass(kanban.$('.o_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_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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['foo'],
});
assert.doesNotHaveClass(kanban.$('.o_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_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('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: '<kanban on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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_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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'<field name="state" widget="priority"/>' +
'</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_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)
'load_views', // form view in quick create
'onchange', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'load_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: '<kanban on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'<field name="state" widget="priority"/>' +
'</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)
'load_views', // form view in quick create
'onchange', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'load_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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</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)
'load_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: '<kanban quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo" required="1"/>' +
'<field name="int_field" attrs=\'{"invisible": [["foo", "=", false]]}\'/>' +
'</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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'<div class="oe_kanban_bottom_right">' +
'<field name="kanban_state" widget="state_selection"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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_form_view'), "o_xxs_form_view");
// trigger window resize explicitly to call _applyFormSizeClass
window.dispatchEvent(new Event('resize'));
assert.hasClass(quickCreate.querySelector('.o_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(8);
var nbRecords = 4;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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 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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/><field name="int_field"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</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: '<kanban on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban>' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="product_id"/>' +
'</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: '<kanban>' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,false,form': '<form string="Partner">' +
'<field name="product_id"/>' +
'<field name="foo"/>' +
'</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_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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,false,form': '<form string="Partner">' +
'<field name="product_id"/>' +
'<field name="foo"/>' +
'</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_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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,false,form': '<form>' +
'<field name="foo"/>' +
'</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_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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="state"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,false,form': '<form>' +
'<field name="state"/>' +
'</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_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: '<kanban on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
'</t></templates>' +
'</kanban>',
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 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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'</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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="bar"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="bar"/>' +
'</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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="state"/></div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="state"/>' +
'</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: '<kanban on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: `
<kanban on_create="quick_create" quick_create_view="some_view_ref">
<templates><t t-name="kanban-box">
<div><field name="foo"/></div>
</t></templates>
</kanban>`,
archs: {
'partner,some_view_ref,form': '<form><field name="int_field"/></form>',
},
groupBy: ['product_id'],
async mockRPC(route, args) {
const result = this._super(...arguments);
if (args.method === 'load_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_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_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: '<kanban on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test">' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
'<field name="category_ids" widget="many2many_tags" options="{\'color_field\': \'color\'}"/>' +
'<field name="foo"/>' +
'<field name="state" widget="priority"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test">' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
'<field name="foo"/>' +
'<div>' +
'<a class="o_test_link" href="#">test link</a>' +
'</div>' +
'</div>' +
'</t>' +
'</templates>' +
'</kanban>',
intercepts: {
// when clicking on a record in kanban view,
// it switches to form view.
switch_view: function () {
throw new Error("should not switch view");
},
},
doNotDisableAHref: true,
});
var $record = kanban.$('.o_kanban_record:not(.o_kanban_ghost)');
assert.strictEqual($record.length, 1,
"should display a kanban record");
var $testLink = $record.find('a');
assert.strictEqual($testLink.length, 1,
"should contain a link in the kanban record");
assert.ok(!!$testLink[0].href,
"link inside kanban record should have non-empty href");
// Prevent the browser default behaviour when clicking on anything.
// This includes clicking on a `<a>` with `href`, so that it does not
// change the URL in the address bar.
// Note that we should not specify a click listener on 'a', otherwise
// it may influence the kanban record global click handler to not open
// the record.
$(document.body).on('click.o_test', function (ev) {
assert.notOk(ev.isDefaultPrevented(),
"should not prevented browser default behaviour beforehand");
assert.strictEqual(ev.target, $testLink[0],
"should have clicked on the test link in the kanban record");
ev.preventDefault();
});
await testUtils.dom.click($testLink);
$(document.body).off('click.o_test');
kanban.destroy();
});
QUnit.test('o2m loaded in only one batch', async function (assert) {
assert.expect(9);
this.data.subtask = {
fields: {
name: {string: 'Name', type: 'char'}
},
records: [
{id: 1, name: "subtask #1"},
{id: 2, name: "subtask #2"},
]
};
this.data.partner.fields.subtask_ids = {
string: 'Subtasks',
type: 'one2many',
relation: 'subtask'
};
this.data.partner.records[0].subtask_ids = [1];
this.data.partner.records[1].subtask_ids = [2];
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="subtask_ids" widget="many2many_tags"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
mockRPC: function (route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
});
await kanban.reload();
assert.verifySteps([
'web_read_group',
'/web/dataset/search_read',
'/web/dataset/search_read',
'read',
'web_read_group',
'/web/dataset/search_read',
'/web/dataset/search_read',
'read',
]);
kanban.destroy();
});
QUnit.test('m2m loaded in only one batch', async function (assert) {
assert.expect(9);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="category_ids" widget="many2many_tags"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
mockRPC: function (route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
});
await kanban.reload(kanban);
assert.verifySteps([
'web_read_group',
'/web/dataset/search_read',
'/web/dataset/search_read',
'read',
'web_read_group',
'/web/dataset/search_read',
'/web/dataset/search_read',
'read',
]);
kanban.destroy();
});
QUnit.test('fetch reference in only one batch', async function (assert) {
assert.expect(9);
this.data.partner.records[0].ref_product = 'product,3';
this.data.partner.records[1].ref_product = 'product,5';
this.data.partner.fields.ref_product = {
string: "Reference Field",
type: 'reference',
};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
'<field name="ref_product"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
mockRPC: function (route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
});
await kanban.reload();
assert.verifySteps([
'web_read_group',
'/web/dataset/search_read',
'/web/dataset/search_read',
'name_get',
'web_read_group',
'/web/dataset/search_read',
'/web/dataset/search_read',
'name_get',
]);
kanban.destroy();
});
QUnit.test('wait x2manys batch fetches to re-render', async function (assert) {
assert.expect(7);
var done = assert.async();
var def = Promise.resolve();
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="category_ids" widget="many2many_tags"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
mockRPC: function (route, args) {
var result = this._super(route, args);
if (args.method === 'read') {
return def.then(function() {
return result;
});
}
return result;
},
});
def = testUtils.makeTestPromise();
assert.containsN(kanban, '.o_tag', 2);
assert.containsN(kanban, '.o_kanban_group', 2);
kanban.update({groupBy: ['state']});
def.then(async function () {
assert.containsN(kanban, '.o_kanban_group', 2);
await testUtils.nextTick();
assert.containsN(kanban, '.o_kanban_group', 3);
assert.containsN(kanban, '.o_tag', 2,
'Should display 2 tags after update');
assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_tag').text(),
'gold', 'First category should be \'gold\'');
assert.strictEqual(kanban.$('.o_kanban_group:eq(2) .o_tag').text(),
'silver', 'Second category should be \'silver\'');
kanban.destroy();
done();
});
await testUtils.nextTick();
def.resolve();
});
QUnit.test('can drag and drop a record from one column to the next', async function (assert) {
assert.expect(9);
// @todo: remove this resequenceDef whenever the jquery upgrade branch
// is merged. This is currently necessary to simulate the reality: we
// need the click handlers to be executed after the end of the drag and
// drop operation, not before.
var resequenceDef = testUtils.makeTestPromise();
var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test
this.data.partner.fields.sequence = {type: 'number', string: "Sequence"};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click"><field name="foo"/>' +
'<t t-if="widget.editable"><span class="thisiseditable">edit</span></t>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
assert.ok(true, "should call resequence");
return resequenceDef.then(_.constant(true));
}
return this._super(route, args);
},
});
assert.containsN(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record', 2);
assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2);
assert.containsN(kanban, '.thisiseditable', 4);
assert.deepEqual(kanban.exportState().resIds, envIDs);
var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
var $group = kanban.$('.o_kanban_group:nth-child(2)');
envIDs = [3, 2, 4, 1]; // first record of first column moved to the bottom of second column
await testUtils.dom.dragAndDrop($record, $group, {withTrailingClick: true});
resequenceDef.resolve();
await testUtils.nextTick();
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);
assert.containsN(kanban, '.thisiseditable', 4);
assert.deepEqual(kanban.exportState().resIds, envIDs);
resequenceDef.resolve();
kanban.destroy();
});
QUnit.test('drag and drop a record, grouped by selection', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div><field name="state"/></div>' +
'</t>' +
'</templates>' +
'</kanban>',
groupBy: ['state'],
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
assert.ok(true, "should call resequence");
return Promise.resolve(true);
}
if (args.model === 'partner' && args.method === 'write') {
assert.deepEqual(args.args[1], {state: 'def'});
}
return this._super(route, args);
},
});
assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record');
assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record');
var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
var $group = kanban.$('.o_kanban_group:nth-child(2)');
await testUtils.dom.dragAndDrop($record, $group);
await nextTick(); // wait for resequence after drag and drop
assert.containsNone(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record');
assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2);
kanban.destroy();
});
QUnit.test('prevent drag and drop of record if grouped by readonly', async function (assert) {
assert.expect(12);
this.data.partner.fields.foo.readonly = true;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban>' +
'<templates>' +
'<t t-name="kanban-box"><div>' +
'<field name="foo"/>' +
'<field name="state" readonly="1"/>' +
'</div></t>' +
'</templates>' +
'</kanban>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
return Promise.resolve();
}
if (args.model === 'partner' && args.method === 'write') {
throw new Error('should not be draggable');
}
return this._super(route, args);
},
});
// simulate an update coming from the searchview, with another groupby given
await kanban.update({groupBy: ['state']});
assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record');
assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record');
// drag&drop a record in another column
var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
var $group = kanban.$('.o_kanban_group:nth-child(2)');
await testUtils.dom.dragAndDrop($record, $group);
await nextTick(); // wait for resequence after drag and drop
// should not be draggable
assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record');
assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record');
// simulate an update coming from the searchview, with another groupby given
await kanban.update({groupBy: ['foo']});
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);
// drag&drop a record in another column
$record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
$group = kanban.$('.o_kanban_group:nth-child(2)');
await testUtils.dom.dragAndDrop($record, $group);
await nextTick(); // wait for resequence after drag and drop
// should not be draggable
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);
// drag&drop a record in the same column
var $record1 = kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(0)');
var $record2 = kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(1)');
assert.strictEqual($record1.text(), "blipDEF", "first record should be DEF");
assert.strictEqual($record2.text(), "blipGHI", "second record should be GHI");
await testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'});
// should still be able to resequence
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(0)').text(), "blipGHI",
"records should have been resequenced");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(1)').text(), "blipDEF",
"records should have been resequenced");
kanban.destroy();
});
QUnit.test('prevent drag and drop if grouped by date/datetime field', async function (assert) {
assert.expect(5);
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-02-08';
this.data.partner.records[3].date = '2017-02-10';
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
groupBy: ['date:month'],
});
assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have 2 columns");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 2,
"1st column should contain 2 records of January month");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length , 2,
"2nd column should contain 2 records of February month");
// drag&drop a record in another column
var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
var $group = kanban.$('.o_kanban_group:nth-child(2)');
await testUtils.dragAndDrop($record, $group);
// should not drag&drop record
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length , 2,
"Should remain same records in first column(2 records)");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length , 2,
"Should remain same records in 2nd column(2 record)");
kanban.destroy();
});
QUnit.test('completely prevent drag and drop if records_draggable set to false', async function (assert) {
assert.expect(6);
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: '<kanban class="o_kanban_test" records_draggable="false">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
groupBy: ['product_id'],
});
// testing initial state
assert.containsN(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record', 2);
assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2);
assert.deepEqual(kanban.exportState().resIds, envIDs);
// attempt to drag&drop a record in another column
var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
var $group = kanban.$('.o_kanban_group:nth-child(2)');
await testUtils.dom.dragAndDrop($record, $group, {withTrailingClick: true});
// should not drag&drop record
assert.containsN(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record', 2,
"First column should still contain 2 records");
assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2,
"Second column should still contain 2 records");
assert.deepEqual(kanban.exportState().resIds, envIDs, "Records should not have moved");
kanban.destroy();
});
QUnit.test('prevent drag and drop of record if onchange fails', async function (assert) {
assert.expect(4);
this.data.partner.onchanges = {
product_id: function (obj) {}
};
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban>' +
'<field name="product_id"/>' +
'<templates>' +
'<t t-name="kanban-box"><div>' +
'<field name="foo"/>' +
'<field name="product_id"/>' +
'</div></t>' +
'</templates>' +
'</kanban>',
groupBy: ['product_id'],
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/onchange') {
return Promise.reject({});
}
return this._super(route, args);
},
});
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 2,
"column should contain 2 records");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2,
"column should contain 2 records");
// drag&drop a record in another column
var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
var $group = kanban.$('.o_kanban_group:nth-child(2)');
await testUtils.dom.dragAndDrop($record, $group);
// should not be dropped, card should reset back to first column
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 2,
"column should now contain 2 records");
assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2,
"column should contain 2 records");
kanban.destroy();
});
QUnit.test('kanban view with default_group_by', async function (assert) {
assert.expect(7);
this.data.partner.records.product_id = 1;
this.data.product.records.push({id: 1, display_name: "third product"});
var readGroupCount = 0;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test" default_group_by="bar">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/web_read_group') {
readGroupCount++;
var correctGroupBy;
if (readGroupCount === 2) {
correctGroupBy = ['product_id'];
} else {
correctGroupBy = ['bar'];
}
// this is done three times
assert.ok(_.isEqual(args.kwargs.groupby, correctGroupBy),
"groupby args should be correct");
}
return this._super.apply(this, arguments);
},
});
assert.hasClass(kanban.$('.o_kanban_view'), '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', 2, "should now have " + 3 + " columns");
// simulate an update coming from the searchview, removing the previously set groupby
await kanban.update({groupBy: []});
assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns again");
kanban.destroy();
});
QUnit.test('kanban view not groupable', async function (assert) {
assert.expect(3);
const searchMenuTypesOriginal = KanbanView.prototype.searchMenuTypes;
KanbanView.prototype.searchMenuTypes = ['filter', 'favorite'];
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban class="o_kanban_test" default_group_by="bar">
<field name="bar"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>
`,
archs: {
'partner,false,search': `
<search>
<filter string="candle" name="itsName" context="{'group_by': 'foo'}"/>
</search>
`,
},
mockRPC: function (route, args) {
if (args.method === 'read_group') {
throw new Error("Should not do a read_group RPC");
}
return this._super.apply(this, arguments);
},
context: { search_default_itsName: 1, },
});
assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped');
assert.containsNone(kanban, '.o_control_panel div.o_search_options div.o_group_by_menu');
assert.deepEqual(cpHelpers.getFacetTexts(kanban), []);
kanban.destroy();
KanbanView.prototype.searchMenuTypes = searchMenuTypesOriginal;
});
QUnit.test('kanban view with create=False', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test" create="0">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
});
assert.ok(!kanban.$buttons || !kanban.$buttons.find('.o-kanban-button-new').length,
"Create button shouldn't be there");
kanban.destroy();
});
QUnit.test('clicking on a link triggers correct event', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><a type="edit">Edit</a></div>' +
'</t></templates></kanban>',
});
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: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
archs: {
'product,false,form': '<form string="Product"><field name="display_name"/></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 .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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
archs: {
'product,false,form': '<form string="Product"><field name="display_name"/></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: '<kanban class="o_kanban_test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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 several columns in a row', async function (assert) {
assert.expect(10);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: "Some description",
}, {
name: "A second example",
columns: ["Col 1", "Col 2"],
}],
});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban examples="test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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_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");
var $firstPane = $('.modal .o_kanban_examples_dialog_content .tab-pane:first');
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').text().trim(),
"Some description", "the correct description should be displayed");
var $secondPane = $('.modal .o_kanban_examples_dialog_content .tab-pane:nth(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').text().trim(),
"", "there should be no description for the second example");
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"],
description: "Some description",
}, {
name: "A second example",
columns: ["Col 1", "Col 2"],
}],
});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban examples="test">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: `<kanban >
<field name="product_id"/>
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}' sum_field="int_field"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
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: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
});
assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped');
await kanban.update({groupBy: ['product_id']});
assert.hasClass(kanban.$('.o_kanban_view'),'o_kanban_grouped');
await kanban.update({groupBy: []});
assert.doesNotHaveClass(kanban.$('.o_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: `<kanban class="o_kanban_test">
<field name="active"/>
<field name="bar"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
viewOptions: {
action: {
help: '<p class="hello">click to add a partner</p>'
}
},
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: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div>' +
'<t t-esc="record.foo.value"/>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates></kanban>',
viewOptions: {
action: {
help: '<p class="hello">click to add a partner</p>'
}
},
});
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: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="name"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="name"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="name"/></div>' +
'</t></templates>' +
'</kanban>',
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_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: '<kanban group_create="false">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
data: this.data,
groupBy: ['product_id'],
model: 'partner',
View: KanbanView,
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsNone(kanban, '.o_view_nocontent');
assert.containsOnce(kanban, '.o_quick_create_unfolded');
assert.containsOnce(kanban, '.o_kanban_example_background_container');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data and click quick create', async function (assert) {
assert.expect(11);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
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;
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsN(kanban, '.o_kanban_group', 2,
"there should be two columns");
assert.hasClass(kanban.$el, 'o_view_sample_data');
assert.containsOnce(kanban, '.o_view_nocontent');
assert.containsN(kanban, '.o_kanban_record', 16,
"there should be 8 sample records by column");
await testUtils.dom.click(kanban.$('.o_kanban_quick_add:first'));
assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
assert.containsNone(kanban, '.o_kanban_record');
assert.containsNone(kanban, '.o_view_nocontent');
assert.containsOnce(kanban.$('.o_kanban_group:first'), '.o_kanban_quick_create');
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.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
assert.containsOnce(kanban.$('.o_kanban_group:first'), '.o_kanban_record');
assert.containsNone(kanban, '.o_view_nocontent');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data and cancel quick create', async function (assert) {
assert.expect(12);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
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;
},
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.containsN(kanban, '.o_kanban_group', 2,
"there should be two columns");
assert.hasClass(kanban.$el, 'o_view_sample_data');
assert.containsOnce(kanban, '.o_view_nocontent');
assert.containsN(kanban, '.o_kanban_record', 16,
"there should be 8 sample records by column");
await testUtils.dom.click(kanban.$('.o_kanban_quick_add:first'));
assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
assert.containsNone(kanban, '.o_kanban_record');
assert.containsNone(kanban, '.o_view_nocontent');
assert.containsOnce(kanban.$('.o_kanban_group:first'), '.o_kanban_quick_create');
await testUtils.dom.click(kanban.$('.o_kanban_view'));
assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
assert.containsNone(kanban, '.o_kanban_quick_create');
assert.containsNone(kanban, '.o_kanban_record');
assert.containsOnce(kanban, '.o_view_nocontent');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data: keyboard navigation', async function (assert) {
assert.expect(5);
const kanban = await createView({
arch: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
<field name="state" widget="priority"/>
</div>
</templates>
</kanban>`,
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') {
result.groups.forEach(g => g.product_id_count = 0);
}
return result;
},
});
// Check keynav is disabled
assert.hasClass(
kanban.el.querySelector('.o_kanban_record'),
'o_sample_data_disabled'
);
assert.hasClass(
kanban.el.querySelector('.o_kanban_toggle_fold'),
'o_sample_data_disabled'
);
assert.containsNone(kanban.renderer, '[tabindex]:not([tabindex="-1"])');
assert.hasClass(document.activeElement, 'o_searchview_input');
await testUtils.fields.triggerKeydown(document.activeElement, 'down');
assert.hasClass(document.activeElement, 'o_searchview_input');
kanban.destroy();
});
QUnit.test('empty kanban with sample data', async function (assert) {
assert.expect(6);
this.data.partner.records = [];
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.hasClass(kanban.$el, 'o_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_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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<t t-name="kanban-box">
<div>
<field name="int_field"/>
<field name="category_ids" widget="many2many_tags"/>
</div>
</t>
</templates>
</kanban>`,
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_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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<t t-name="kanban-box">
<div><field name="int_field"/></div>
</t>
</templates>
</kanban>`,
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_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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
viewOptions: {
action: {
help: "No content helper",
},
},
});
assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4);
assert.containsNone(kanban, '.o_view_nocontent');
await kanban.reload({ domain: [['id', '<', 0]]});
assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');
kanban.destroy();
});
QUnit.test('empty grouped kanban with sample data: add a column', async function (assert) {
assert.expect(6);
const kanban = await createView({
arch: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
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],
};
});
result.length = result.groups.length;
}
return result;
},
});
assert.hasClass(kanban, 'o_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_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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
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_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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
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_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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
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_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: `
<kanban sample="1">
<field name="product_id"/>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
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],
};
});
result.length = result.groups.length;
}
return result;
},
});
assert.hasClass(kanban, 'o_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_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_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: `<kanban class="o_kanban_test"><templates><t t-name="kanban-box">
<div>
<t t-esc="record.foo.value"/>
<field name="foo"/>
</div>
</t></templates></kanban>`,
viewOptions: {
action: {
help: '<p class="hello">click to add a partner</p>'
}
},
});
await testUtils.dom.click(kanban.$('.o_kanban_view'));
assert.doesNotHaveClass(kanban.$('.o-kanban-button-new'), 'o_catch_attention');
await kanban.reload({ domain: [['id', '<', 0]] });
await testUtils.dom.click(kanban.$('.o_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:
'<kanban>' +
'<field name="foo"/>' +
'<field name="bar"/>' +
'<field name="state"/>' +
'<templates><div t-name="kanban-box">' +
'<button class="o_btn_test_1" type="object" name="a1" ' +
'attrs="{\'invisible\': [[\'foo\', \'!=\', \'yop\']]}"/>' +
'<button class="o_btn_test_2" type="object" name="a2" ' +
'attrs="{\'invisible\': [[\'bar\', \'=\', True]]}" ' +
'states="abc,def"/>' +
'</div></templates>' +
'</kanban>',
});
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:
'<kanban>' +
'<templates><div t-name="kanban-box">' +
'<field name="foo"/>' +
'<button type="object" name="a1" />' +
'</div></templates>' +
'</kanban>',
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();
});
await testUtils.dom.click($('button[data-name="a1"]').first());
assert.strictEqual(count, 1, "should have triggered a execute action");
await testUtils.dom.click($('button[data-name="a1"]').first());
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:
'<kanban>' +
'<templates><div t-name="kanban-box">' +
'<field name="foo"/>' +
'<field name="active"/>' +
'<button type="object" name="a1" />' +
'<button type="object" name="toggle_active" />' +
'</div></templates>' +
'</kanban>',
});
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:
'<kanban>' +
'<templates><div t-name="kanban-box">' +
'<field name="foo"/>' +
'<button type="object" name="a1" />' +
'<button type="object" name="toggle_action" />' +
'</div></templates>' +
'</kanban>',
});
testUtils.mock.intercept(kanban, 'execute_action', function (event) {
event.data.on_closed();
});
try {
await testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_action"]'));
assert.strictEqual(true, true, 'Everything went fine');
} catch (e) {
assert.strictEqual(true, false, 'Error triggered at action execution');
}
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: '<kanban class="o_kanban_test">' +
'<field name="date"/>' +
'<field name="datetime"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<t t-esc="record.date.raw_value"/>' +
'<t t-esc="record.datetime.raw_value"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
});
// 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: '<kanban class="o_kanban_test">' +
'<field name="product_id"/>' +
'<field name="category_ids"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<button t-if="!record.product_id.raw_value" class="btn_a">A</button>' +
'<button t-if="!record.category_ids.raw_value.length" class="btn_b">B</button>' +
'</div>' +
'</t></templates>' +
'</kanban>',
});
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: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
});
assert.hasClass(kanban.$('.o_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: '<kanban>' +
'<field name="foo"/>' +
'<field name="bar"/>' +
'<field name="category_ids"/>' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div>' +
'<field name="foo"/>' +
'<button type="object" attrs="{\'invisible\':[\'|\', (\'bar\',\'=\',True), (\'category_ids\', \'!=\', [])]}" class="btn btn-primary float-right" name="channel_join_and_get_info">Join</button>' +
'</div>' +
'</t>' +
'</templates>' +
'</kanban>',
});
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: '<kanban>' +
'<field name="color"/>' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div color="color">' +
'<div class="o_dropdown_kanban dropdown">' +
'<a class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#">' +
'<span class="fa fa-bars fa-lg"/>' +
'</a>' +
'<ul class="dropdown-menu" role="menu">' +
'<li>' +
'<ul class="oe_kanban_colorpicker"/>' +
'</li>' +
'</ul>' +
'</div>' +
'<field name="name"/>' +
'</div>' +
'</t>' +
'</templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="category_ids"/>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban default_group_by="bar">' +
'<field name="bar"/>' +
'<field name="product_id" '+
'options=\'{"group_by_tooltip": {"name": "Kikou"}}\'/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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_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(Undefined)').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('original-title'),
"<div>Kikou</br>hello</div>",
"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: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
'</t></templates></kanban>',
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: '<kanban>' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
'</t></templates></kanban>',
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) {
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: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div>' +
'<t t-esc="record.foo.value"/>' +
'<field name="foo" blip="1"/>' +
'<widget name="test"/>' +
'</div>' +
'</t></templates></kanban>',
});
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('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: '<kanban class="o_kanban_test">' +
'<field name="color"/>' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div color="color">' +
'<div class="o_dropdown_kanban dropdown">' +
'<a class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#">' +
'<span class="fa fa-bars fa-lg"/>' +
'</a>' +
'<ul class="dropdown-menu" role="menu">' +
'<li>' +
'<ul class="oe_kanban_colorpicker"/>' +
'</li>' +
'</ul>' +
'</div>' +
'<field name="name" widget="test_widget"/>' +
'</div>' +
'</t>' +
'</templates>' +
'</kanban>',
});
// 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:
'<kanban>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['bar'],
});
assert.containsN(kanban, '.o_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:
'<kanban>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\'/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['bar'],
});
assert.containsN(kanban, '.o_kanban_group', 2);
assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "4");
assert.containsN(kanban, '.o_kanban_counter_progress:last .progress-bar', 4);
assert.containsOnce(kanban, '.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]',
"should have false kanban color");
assert.hasClass(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'), 'bg-muted-full');
await testUtils.dom.click(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'));
assert.hasClass(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'), 'progress-bar-animated');
assert.hasClass(kanban.$('.o_kanban_group:last'), 'o_kanban_group_show_muted');
assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "1");
kanban.destroy();
});
QUnit.test('column progressbars: "false" bar with sum_field', async function (assert) {
assert.expect(4);
this.data.partner.records.push({id: 5, bar: true, foo: false, int_field: 15, product_id: 5, state: "ghi"});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'<kanban>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<field name="foo"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['bar'],
});
assert.containsN(kanban, '.o_kanban_group', 2);
assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "51");
await testUtils.dom.click(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'));
assert.hasClass(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'), 'progress-bar-animated');
assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "15");
kanban.destroy();
});
QUnit.test('column progressbars should not crash in non grouped views', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch:
'<kanban>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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:
'<kanban>' +
'<field name="product_id"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\'/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
});
var nbProgressBars = kanban.$('.o_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_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:
'<kanban>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\'/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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:
'<kanban limit="1">' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\'/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="id"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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:
`<kanban limit="1">
<progressbar field="foo" colors='{"blork": "success"}'/>
<field name="foo"/>
<templates><t t-name="kanban-box">
<div><field name="id"/></div>
</t></templates>
</kanban>`,
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:
'<kanban>' +
'<field name="active"/>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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('originalTitle'), "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('originalTitle'), "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:
'<kanban>' +
'<field name="active"/>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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:
'<kanban>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['bar'],
mockRPC: function (route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
});
await kanban.reload();
assert.verifySteps([
// initial load
'web_read_group',
'read_progress_bar',
'/web/dataset/search_read',
'/web/dataset/search_read',
// reload
'web_read_group',
'read_progress_bar',
'/web/dataset/search_read',
'/web/dataset/search_read',
]);
kanban.destroy();
});
QUnit.test('RPCs when (de)activating kanban view progressbar filters', async function (assert) {
assert.expect(8);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban>
<field name="bar"/>
<field name="int_field"/>
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}' sum_field="int_field"/>
<templates><t t-name="kanban-box">
<div><field name="name"/></div>
</t></templates>
</kanban>
`,
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:
'<kanban>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\'/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="int_field"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
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:
`<kanban>
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}'/>
<templates><t t-name="kanban-box">
<div>
<field name="foo"/>
</div>
</t></templates>
</kanban>`,
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-full'));
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:
`<kanban>
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}'/>
<templates><t t-name="kanban-box">
<div>
<field name="foo"/>
</div>
</t></templates>
</kanban>`,
groupBy: ['bar'],
});
assert.deepEqual(
[...view.el.querySelectorAll('.progress-bar')].map(el => el.getAttribute('data-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-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:
`<kanban limit="1">
<field name="id"/>
<field name="foo"/>
<field name="sequence"/>
<templates><t t-name="kanban-box">
<div>
<field name="id"/>
</div>
</t></templates>
</kanban>`,
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: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="int_field"/>' +
'</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: `
<kanban on_create="quick_create" quick_create_view="some_view_ref">
<field name="int_field"/>
<field name="foo"/>
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}' sum_field="int_field"/>
<templates><t t-name="kanban-box">
<div><field name="name"/></div>
</t></templates>
</kanban>
`,
archs: {
'partner,some_view_ref,form': `
<form>
<field name="int_field"/>
<field name="foo"/>
</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:
'<kanban on_create="quick_create">' +
'<field name="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
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: '<kanban class="o_kanban_test">' +
'<field name="id"/>' +
'<templates><t t-name="kanban-box"><div>' +
'<img t-att-src="kanban_image(\'partner\', \'image\', record.id.raw_value)"/>' +
'</div></t></templates>' +
'</kanban>',
});
// 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: `
<kanban class="o_kanban_test">
<field name="id"/>
<templates><t t-name="kanban-box"><div>
<img t-att-src="kanban_image('partner', 'image', record.id.raw_value)"/>
</div></t></templates>
</kanban>`,
mockRPC(route, args) {
if (route === '/web/dataset/search_read') {
assert.deepEqual(args.fields, ['id', '__last_update']);
}
return this._super(...arguments);
},
});
kanban.destroy();
});
QUnit.test('test displaying image (binary & placeholder)', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<field name="id"/>' +
'<field name="image"/>' +
'<templates><t t-name="kanban-box"><div>' +
'<img t-att-src="kanban_image(\'partner\', \'image\', record.id.raw_value)"/>' +
'</div></t></templates>' +
'</kanban>',
mockRPC: function (route, args) {
if (route === 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==') {
assert.ok("The view's image should have been fetched.");
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
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");
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: '<kanban class="o_kanban_test">' +
'<field name="id"/>' +
'<field name="image"/>' +
'<templates><t t-name="kanban-box"><div>' +
'<img t-att-src="kanban_image(\'partner\', \'image\', 1)"/>' +
'</div></t></templates>' +
'</kanban>',
mockRPC: function (route, args) {
if (route === 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==') {
assert.ok("The view's image should have been fetched.");
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
// 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");
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: '<kanban string="Partners">' +
'<field name="foo"/>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<field name="qux"/>' +
'<field name="product_id"/>' +
'<field name="category_ids"/>' +
'<field name="state"/>' +
'<field name="date"/>' +
'<field name="datetime"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
};
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: '<kanban class="o_kanban_test">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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_kanban_view'),'o_kanban_grouped',
"the kanban view should be grouped");
assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_ungrouped',
"the kanban view should not be ungrouped");
kanban.update({domain: []}); // 1st update on kanban view
kanban.update({groupBy: false}); // 2n update on kanban view
prom.resolve(); // simulate slow 1st update of kanban view
await nextTick();
assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped',
"the kanban view should not longer be grouped");
assert.hasClass(kanban.$('.o_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: '<kanban class="o_kanban_test" group_create="0" on_create="quick_create"><templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates></kanban>',
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: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div>' +
'<t t-esc="record.foo.value"/>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates></kanban>',
});
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: '<kanban class="o_kanban_test">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban class="o_kanban_test">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
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: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><a type="edit">Edit</a></div>' +
'</t></templates></kanban>',
});
testUtils.mock.intercept(kanban, 'switch_view', function (event) {
assert.deepEqual(event.data, {
view_type: 'form',
res_id: 1,
mode: 'edit',
model: 'partner',
}, 'When selecting focusing a card and hitting ENTER, the first link or button is clicked');
});
kanban.$('.o_kanban_record').first().focus().trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ENTER,
which: $.ui.keyCode.ENTER,
}));
await testUtils.nextTick();
kanban.destroy();
});
QUnit.test('asynchronous rendering of a field widget (ungrouped)', async function (assert) {
assert.expect(4);
var fooFieldProm = makeTestPromise();
var FieldChar = fieldRegistry.get('char');
fieldRegistry.add('asyncwidget', FieldChar.extend({
willStart: function () {
return fooFieldProm;
},
start: function () {
this.$el.html('LOADED');
},
}));
var kanbanController;
testUtils.createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><field name="foo" widget="asyncwidget"/></div>' +
'</t></templates></kanban>',
}).then(function (kanban) {
kanbanController = kanban;
});
assert.strictEqual($('.o_kanban_record').length, 0, "kanban view is not ready yet");
fooFieldProm.resolve();
await nextTick();
assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED");
// reload with a domain
fooFieldProm = makeTestPromise();
kanbanController.reload({domain: [['id', '=', 1]]});
await nextTick();
assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED");
fooFieldProm.resolve();
await nextTick();
assert.strictEqual($('.o_kanban_record').text(), "LOADED");
kanbanController.destroy();
delete fieldRegistry.map.asyncWidget;
});
QUnit.test('asynchronous rendering of a field widget (grouped)', async function (assert) {
assert.expect(4);
var fooFieldProm = makeTestPromise();
var FieldChar = fieldRegistry.get('char');
fieldRegistry.add('asyncwidget', FieldChar.extend({
willStart: function () {
return fooFieldProm;
},
start: function () {
this.$el.html('LOADED');
},
}));
var kanbanController;
testUtils.createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><field name="foo" widget="asyncwidget"/></div>' +
'</t></templates></kanban>',
groupBy: ['foo'],
}).then(function (kanban) {
kanbanController = kanban;
});
assert.strictEqual($('.o_kanban_record').length, 0, "kanban view is not ready yet");
fooFieldProm.resolve();
await nextTick();
assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED");
// reload with a domain
fooFieldProm = makeTestPromise();
kanbanController.reload({domain: [['id', '=', 1]]});
await nextTick();
assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED");
fooFieldProm.resolve();
await nextTick();
assert.strictEqual($('.o_kanban_record').text(), "LOADED");
kanbanController.destroy();
delete fieldRegistry.map.asyncWidget;
});
QUnit.test('asynchronous rendering of a field widget with display attr', async function (assert) {
assert.expect(3);
var fooFieldDef = makeTestPromise();
var FieldChar = fieldRegistry.get('char');
fieldRegistry.add('asyncwidget', FieldChar.extend({
willStart: function () {
return fooFieldDef;
},
start: function () {
this.$el.html('LOADED');
},
}));
var kanbanController;
testUtils.createAsyncView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><field name="foo" display="right" widget="asyncwidget"/></div>' +
'</t></templates></kanban>',
}).then(function (kanban) {
kanbanController = kanban;
});
assert.containsNone(document.body, '.o_kanban_record');
fooFieldDef.resolve();
await nextTick();
assert.strictEqual(kanbanController.$('.o_kanban_record').text(),
"LOADEDLOADEDLOADEDLOADED");
assert.hasClass(kanbanController.$('.o_kanban_record:first .o_field_char'), 'float-right');
kanbanController.destroy();
delete fieldRegistry.map.asyncWidget;
});
QUnit.test('asynchronous rendering of a widget', async function (assert) {
assert.expect(2);
var widgetDef = makeTestPromise();
widgetRegistry.add('asyncwidget', Widget.extend({
willStart: function () {
return widgetDef;
},
start: function () {
this.$el.html('LOADED');
},
}));
var kanbanController;
testUtils.createAsyncView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><widget name="asyncwidget"/></div>' +
'</t></templates></kanban>',
}).then(function (kanban) {
kanbanController = kanban;
});
assert.containsNone(document.body, '.o_kanban_record');
widgetDef.resolve();
await nextTick();
assert.strictEqual(kanbanController.$('.o_kanban_record .o_widget').text(),
"LOADEDLOADEDLOADEDLOADED");
kanbanController.destroy();
delete widgetRegistry.map.asyncWidget;
});
QUnit.test('update kanban with asynchronous field widget', async function (assert) {
assert.expect(3);
var fooFieldDef = makeTestPromise();
var FieldChar = fieldRegistry.get('char');
fieldRegistry.add('asyncwidget', FieldChar.extend({
willStart: function () {
return fooFieldDef;
},
start: function () {
this.$el.html('LOADED');
},
}));
var kanban = await testUtils.createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><field name="foo" widget="asyncwidget"/></div>' +
'</t></templates></kanban>',
domain: [['id', '=', '0']], // no record matches this domain
});
assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');
kanban.update({domain: []}); // this rendering will be async
assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');
fooFieldDef.resolve();
await nextTick();
assert.strictEqual(kanban.$('.o_kanban_record').text(),
"LOADEDLOADEDLOADEDLOADED");
kanban.destroy();
delete widgetRegistry.map.asyncWidget;
});
QUnit.test('set cover image', async function (assert) {
assert.expect(6);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'<div class="o_dropdown_kanban dropdown">' +
'<a class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#">' +
'<span class="fa fa-bars fa-lg"/>' +
'</a>' +
'<div class="dropdown-menu" role="menu">' +
'<a type="set_cover" data-field="displayed_image_id" class="dropdown-item">Set Cover Image</a>'+
'</div>' +
'</div>' +
'<div>'+
'<field name="displayed_image_id" widget="attachment_image"/>'+
'</div>'+
'</div>' +
'</t>' +
'</templates>' +
'</kanban>',
mockRPC: function (route, args) {
if (args.model === 'partner' && args.method === 'write') {
assert.step(String(args.args[0][0]));
return this._super(route, args);
}
return this._super(route, args);
},
});
var $firstRecord = kanban.$('.o_kanban_record:first');
testUtils.kanban.toggleRecordDropdown($firstRecord);
await testUtils.dom.click($firstRecord.find('[data-type=set_cover]'));
assert.containsNone($firstRecord, 'img', "Initially there is no image.");
await testUtils.dom.click($('.modal').find("img[data-id='1']"));
await testUtils.modal.clickButton('Select');
assert.containsOnce(kanban, 'img[data-src*="/web/image/1"]');
var $secondRecord = kanban.$('.o_kanban_record:nth(1)');
testUtils.kanban.toggleRecordDropdown($secondRecord);
await testUtils.dom.click($secondRecord.find('[data-type=set_cover]'));
$('.modal').find("img[data-id='2']").dblclick();
await testUtils.nextTick();
assert.containsOnce(kanban, 'img[data-src*="/web/image/2"]');
assert.verifySteps(["1", "2"], "should writes on both kanban records");
kanban.destroy();
});
QUnit.test('ungrouped kanban with handle field', async function (assert) {
assert.expect(4);
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: '<kanban>' +
'<field name="int_field" widget="handle" />' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates></kanban>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
assert.deepEqual(args.ids, envIDs,
"should write the sequence in correct order");
return Promise.resolve(true);
}
return this._super(route, args);
},
});
assert.hasClass(kanban.$('.o_kanban_view'), 'ui-sortable');
assert.strictEqual(kanban.$('.o_kanban_record:not(.o_kanban_ghost)').text(),
'yopblipgnapblip');
var $record = kanban.$('.o_kanban_view .o_kanban_record:first');
var $to = kanban.$('.o_kanban_view .o_kanban_record:nth-child(4)');
envIDs = [2, 3, 4, 1]; // first record of moved after last one
await testUtils.dom.dragAndDrop($record, $to, {position: "bottom"});
assert.strictEqual(kanban.$('.o_kanban_record:not(.o_kanban_ghost)').text(),
'blipgnapblipyop');
kanban.destroy();
});
QUnit.test('ungrouped kanban without handle field', async function (assert) {
assert.expect(3);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates></kanban>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
assert.ok(false, "should not trigger a resequencing");
}
return this._super(route, args);
},
});
assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'ui-sortable');
assert.strictEqual(kanban.$('.o_kanban_record:not(.o_kanban_ghost)').text(),
'yopblipgnapblip');
var $draggedRecord = kanban.$('.o_kanban_view .o_kanban_record:first');
var $to = kanban.$('.o_kanban_view .o_kanban_record:nth-child(4)');
await testUtils.dom.dragAndDrop($draggedRecord, $to, {position: "bottom"});
assert.strictEqual(kanban.$('.o_kanban_record:not(.o_kanban_ghost)').text(),
'yopblipgnapblip');
kanban.destroy();
});
QUnit.test('click on image field in kanban with oe_kanban_global_click', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
'<field name="image" widget="image"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
mockRPC: function (route) {
if (route.startsWith('data:image')) {
return Promise.resolve();
}
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");
},
},
});
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4);
await testUtils.dom.click(kanban.$('.o_field_image').first());
kanban.destroy();
});
QUnit.test('kanban view with boolean field', async function (assert) {
assert.expect(2);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div><field name="bar"/></div>
</t>
</templates>
</kanban>`,
});
assert.containsN(kanban, '.o_kanban_record:contains(True)', 3);
assert.containsOnce(kanban, '.o_kanban_record:contains(False)');
kanban.destroy();
});
QUnit.test('kanban view with boolean widget', async function (assert) {
assert.expect(1);
const kanban = await testUtils.createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div><field name="bar" widget="boolean"/></div>
</t>
</templates>
</kanban>
`,
});
assert.containsOnce(kanban.el.querySelector('.o_kanban_record'),
'div.custom-checkbox.o_field_boolean');
kanban.destroy();
});
QUnit.test('kanban view with monetary and currency fields without widget', async function (assert) {
assert.expect(1);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban>
<field name="currency_id"/>
<templates><t t-name="kanban-box">
<div><field name="salary"/></div>
</t></templates>
</kanban>`,
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
const kanbanRecords = kanban.el.querySelectorAll('.o_kanban_record:not(.o_kanban_ghost)');
assert.deepEqual([...kanbanRecords].map(r => r.innerText),
['$ 1750.00', '$ 1500.00', '2000.00 €', '$ 2222.00']);
kanban.destroy();
});
QUnit.test("quick create: keyboard navigation to buttons", async function (assert) {
assert.expect(2);
const kanban = await createView({
arch: `
<kanban on_create="quick_create">
<field name="bar"/>
<templates>
<div t-name="kanban-box">
<field name="display_name"/>
</div>
</templates>
</kanban>`,
data: this.data,
groupBy: ["bar"],
model: "partner",
View: KanbanView,
});
// Open quick create
await testUtils.kanban.clickCreate(kanban);
assert.containsOnce(kanban, ".o_kanban_group:first .o_kanban_quick_create");
const $displayName = kanban.$(".o_kanban_quick_create .o_field_widget[name=display_name]");
// Fill in mandatory field
await testUtils.fields.editInput($displayName, "aaa");
// Tab -> goes to first primary button
await testUtils.dom.triggerEvent($displayName, "keydown", {
keyCode: $.ui.keyCode.TAB,
which: $.ui.keyCode.TAB,
});
assert.hasClass(document.activeElement, "btn btn-primary o_kanban_add");
kanban.destroy();
});
});
});