summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/views/kanban_tests.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/tests/views/kanban_tests.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/tests/views/kanban_tests.js')
-rw-r--r--addons/web/static/tests/views/kanban_tests.js7248
1 files changed, 7248 insertions, 0 deletions
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
new file mode 100644
index 00000000..1197fc9a
--- /dev/null
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -0,0 +1,7248 @@
+odoo.define('web.kanban_tests', function (require) {
+"use strict";
+
+var AbstractField = require('web.AbstractField');
+const Domain = require('web.Domain');
+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('pager, ungrouped, deleting all records from last page should move to previous page', async function (assert) {
+ assert.expect(5);
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ `<kanban class="o_kanban_test" limit="3">
+ <templates>
+ <t t-name="kanban-box">
+ <div>
+ <div><a role="menuitem" type="delete" class="dropdown-item">Delete</a></div>
+ <field name="foo"/>
+ </div>
+ </t>
+ </templates>
+ </kanban>`,
+ });
+
+ assert.strictEqual(cpHelpers.getPagerValue(kanban), "1-3",
+ "should have 3 records on current page");
+ assert.strictEqual(cpHelpers.getPagerSize(kanban), "4",
+ "should have 4 records");
+
+ // move to next page
+ await cpHelpers.pagerNext(kanban);
+ assert.strictEqual(cpHelpers.getPagerValue(kanban), "4-4",
+ "should be on second page");
+
+ // delete a record
+ await testUtils.dom.click(kanban.$('.o_kanban_record:first a:first'));
+ await testUtils.dom.click($('.modal-footer button:first'));
+ assert.strictEqual(cpHelpers.getPagerValue(kanban), "1-3",
+ "should have 1 page only");
+ assert.strictEqual(cpHelpers.getPagerSize(kanban), "3",
+ "should have 4 records");
+
+ kanban.destroy();
+ });
+
+ QUnit.test('create in grouped on m2o', async function (assert) {
+ assert.expect(5);
+
+ 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(9);
+
+ 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 input and drag the mouse outside, should not cancel the quick creation
+ await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
+ $quickCreate = kanban.$('.o_kanban_quick_create');
+ await testUtils.dom.triggerMouseEvent($quickCreate.find('input'), 'mousedown');
+ await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first').first());
+ assert.containsOnce(kanban, '.o_kanban_quick_create',
+ "the quick create should not have been destroyed after clicking outside");
+
+ // click to really add an element
+ await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first());
+ $quickCreate = kanban.$('.o_kanban_quick_create');
+ await testUtils.fields.editInput($quickCreate.find('input'), 'new partner');
+
+ // clicking outside should no longer destroy the quick create as it is dirty
+ await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first'));
+ assert.containsOnce(kanban, '.o_kanban_quick_create',
+ "the quick create should not have been destroyed");
+
+ // confirm by pressing ENTER
+ nbRecords = 5;
+ $quickCreate.find('input').trigger($.Event('keydown', {
+ keyCode: $.ui.keyCode.ENTER,
+ which: $.ui.keyCode.ENTER,
+ }));
+
+ await nextTick();
+ assert.strictEqual(this.data.partner.records.length, 5,
+ "should have created a partner");
+ assert.strictEqual(_.last(this.data.partner.records).name, "new partner",
+ "should have correct name");
+ assert.strictEqual(kanban.exportState().resIds.length, nbRecords);
+
+ kanban.destroy();
+ });
+
+ QUnit.test('quick create record: validate with ENTER', async function (assert) {
+ // in this test, we accurately mock the behavior of the webclient by specifying a
+ // fieldDebounce > 0, meaning that the changes in an InputField aren't notified to the model
+ // on 'input' events, but they wait for the 'change' event (or a call to 'commitChanges',
+ // e.g. triggered by a navigation event)
+ // in this scenario, the call to 'commitChanges' actually does something (i.e. it notifies
+ // the new value of the char field), whereas it does nothing if the changes are notified
+ // directly
+ assert.expect(3);
+
+ var kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<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 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('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);
+
+ 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="foo"/>' +
+ '</div>' +
+ '</t></templates>' +
+ '</kanban>',
+ groupBy: ['bar'],
+ });
+
+ var $secondGroup = kanban.$('.o_kanban_group:eq(1)');
+ var initialCount = parseInt($secondGroup.find('.o_kanban_counter_side').text());
+ assert.strictEqual(initialCount, 3,
+ "Initial count should be Three");
+ await testUtils.dom.click($secondGroup.find('.bg-success-full'));
+ var lastCount = parseInt($secondGroup.find('.o_kanban_counter_side').text());
+ assert.strictEqual(lastCount, 1,
+ "kanban counters should vary according to what subgroup is selected");
+
+ kanban.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('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 (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("test displaying image from m2o field (m2o field not set)", async function (assert) {
+ assert.expect(2);
+ this.data.foo_partner = {
+ fields: {
+ name: {string: "Foo Name", type: "char"},
+ partner_id: {string: "Partner", type: "many2one", relation: "partner"},
+ },
+ records: [
+ {id: 1, name: 'foo_with_partner_image', partner_id: 1},
+ {id: 2, name: 'foo_no_partner'},
+ ]
+ };
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: "foo_partner",
+ data: this.data,
+ arch: `
+ <kanban>
+ <templates>
+ <div t-name="kanban-box">
+ <field name="name"/>
+ <field name="partner_id"/>
+ <img t-att-src="kanban_image('partner', 'image', record.partner_id.raw_value)"/>
+ </div>
+ </templates>
+ </kanban>`,
+ });
+
+ assert.containsOnce(kanban, 'img[data-src*="/web/image"][data-src$="&id=1"]', "image url should contain id of set partner_id");
+ assert.containsOnce(kanban, 'img[data-src*="/web/image"][data-src$="&id="]', "image url should contain an empty id if partner_id is not set");
+
+ kanban.destroy();
+ });
+
+ QUnit.test('check if the view destroys all widgets and instances', async function (assert) {
+ assert.expect(2);
+
+ var instanceNumber = 0;
+ testUtils.mock.patch(mixins.ParentedMixin, {
+ init: function () {
+ instanceNumber++;
+ return this._super.apply(this, arguments);
+ },
+ destroy: function () {
+ if (!this.isDestroyed()) {
+ instanceNumber--;
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ var params = {
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<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();
+ });
+});
+
+});