odoo.define('web.field_many_to_many_tests', function (require) {
"use strict";
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
const cpHelpers = testUtils.controlPanel;
var createView = testUtils.createView;
QUnit.module('fields', {}, function () {
QUnit.module('relational_fields', {
beforeEach: function () {
this.data = {
partner: {
fields: {
display_name: { string: "Displayed name", type: "char" },
foo: { string: "Foo", type: "char", default: "My little Foo Value" },
int_field: { string: "int_field", type: "integer", sortable: true },
turtles: { string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu' },
timmy: { string: "pokemon", type: "many2many", relation: 'partner_type' },
color: {
type: "selection",
selection: [['red', "Red"], ['black', "Black"]],
default: 'red',
string: "Color",
},
user_id: { string: "User", type: 'many2one', relation: 'user' },
reference: {
string: "Reference Field", type: 'reference', selection: [
["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]]
},
},
records: [{
id: 1,
display_name: "first record",
foo: "yop",
int_field: 10,
turtles: [2],
timmy: [],
user_id: 17,
reference: 'product,37',
}, {
id: 2,
display_name: "second record",
foo: "blip",
int_field: 9,
timmy: [],
user_id: 17,
}, {
id: 4,
display_name: "aaa",
}],
onchanges: {},
},
product: {
fields: {
name: { string: "Product Name", type: "char" }
},
records: [{
id: 37,
display_name: "xphone",
}, {
id: 41,
display_name: "xpad",
}]
},
partner_type: {
fields: {
name: { string: "Partner Type", type: "char" },
color: { string: "Color index", type: "integer" },
},
records: [
{ id: 12, display_name: "gold", color: 2 },
{ id: 14, display_name: "silver", color: 5 },
]
},
turtle: {
fields: {
display_name: { string: "Displayed name", type: "char" },
turtle_foo: { string: "Foo", type: "char" },
turtle_bar: { string: "Bar", type: "boolean", default: true },
partner_ids: { string: "Partner", type: "many2many", relation: 'partner' },
},
records: [{
id: 1,
display_name: "leonardo",
turtle_foo: "yop",
partner_ids: [],
}, {
id: 2,
display_name: "donatello",
turtle_foo: "blip",
partner_ids: [2, 4],
}, {
id: 3,
display_name: "raphael",
turtle_foo: "kawa",
partner_ids: [],
}],
onchanges: {},
},
user: {
fields: {
name: { string: "Name", type: "char" },
},
records: [{
id: 17,
name: "Aline",
}, {
id: 19,
name: "Christine",
}]
},
};
},
}, function () {
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', async function (assert) {
assert.expect(33);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records.push({ id: 15, display_name: "red", color: 6 });
this.data.partner_type.records.push({ id: 18, display_name: "yellow", color: 4 });
this.data.partner_type.records.push({ id: 21, display_name: "blue", color: 1 });
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '
',
archs: {
'partner_type,false,form': '',
'partner_type,false,list': '',
'partner_type,false,search': '' +
'' +
'',
},
res_id: 1,
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner_type/write') {
assert.strictEqual(args.args[1].display_name, "new name", "should write 'new_name'");
}
if (route === '/web/dataset/call_kw/partner_type/create') {
assert.strictEqual(args.args[0].display_name, "A new type", "should create 'A new type'");
}
if (route === '/web/dataset/call_kw/partner/write') {
var commands = args.args[1].timmy;
assert.strictEqual(commands.length, 1, "should have generated one command");
assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH");
// get the created type's id
var createdType = _.findWhere(this.data.partner_type.records, {
display_name: "A new type"
});
var ids = _.sortBy([12, 15, 18].concat(createdType.id), _.identity.bind(_));
assert.ok(_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), ids),
"new value should be " + ids);
}
return this._super.apply(this, arguments);
},
});
// the SelectCreateDialog requests the session, so intercept its custom
// event to specify a fake session to prevent it from crashing
testUtils.mock.intercept(form, 'get_session', function (event) {
event.data.callback({ user_context: {} });
});
assert.ok(!form.$('.o_kanban_view .delete_icon').length,
'delete icon should not be visible in readonly');
assert.ok(!form.$('.o_field_many2many .o-kanban-button-new').length,
'"Add" button should not be visible in readonly');
await testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2,
'should contain 2 records');
assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'gold',
'display_name of subrecord should be the one in DB');
assert.ok(form.$('.o_kanban_view .delete_icon').length,
'delete icon should be visible in edit');
assert.ok(form.$('.o_field_many2many .o-kanban-button-new').length,
'"Add" button should be visible in edit');
assert.strictEqual(form.$('.o_field_many2many .o-kanban-button-new').text().trim(), "Add",
'Create button should have "Add" label');
// edit existing subrecord
await testUtils.dom.click(form.$('.oe_kanban_global_click:first()'));
await testUtils.fields.editInput($('.modal .o_form_view input'), 'new name');
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'new name',
'value of subrecord should have been updated');
// add subrecords
// -> single select
await testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new'));
assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal");
assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 3,
"list view should contain 3 records");
await testUtils.dom.click($('.modal .o_list_view tbody tr:contains(red)'));
assert.ok(!$('.modal .o_list_view').length, "should have closed the modal");
assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3,
'kanban should now contain 3 records');
assert.ok(form.$('.o_kanban_record:contains(red)').length,
'record "red" should be in the kanban');
// -> multiple select
await testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new'));
assert.ok($('.modal .o_select_button').prop('disabled'), "select button should be disabled");
assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 2,
"list view should contain 2 records");
await testUtils.dom.click($('.modal .o_list_view thead .o_list_record_selector input'));
await testUtils.dom.click($('.modal .o_select_button'));
assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled");
assert.ok(!$('.modal .o_list_view').length, "should have closed the modal");
assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5,
'kanban should now contain 5 records');
// -> created record
await testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new'));
await testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)'));
assert.ok($('.modal .o_form_view.o_form_editable').length,
"should have opened a form view in edit mode, in a modal");
await testUtils.fields.editInput($('.modal .o_form_view input'), 'A new type');
await testUtils.dom.click($('.modal:nth(1) footer .btn-primary:first()'));
assert.ok(!$('.modal').length, "should have closed both modals");
assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 6,
'kanban should now contain 6 records');
assert.ok(form.$('.o_kanban_record:contains(A new type)').length,
'the newly created type should be in the kanban');
// delete subrecords
await testUtils.dom.click(form.$('.o_kanban_record:contains(silver)'));
assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1,
'There should be a modal having Remove Button');
await testUtils.dom.click($('.modal .modal-footer .o_btn_remove'));
assert.containsNone($('.o_modal'), "modal should have been closed");
assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5,
'should contain 5 records');
assert.ok(!form.$('.o_kanban_record:contains(silver)').length,
'the removed record should not be in kanban anymore');
await testUtils.dom.click(form.$('.o_kanban_record:contains(blue) .delete_icon'));
assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4,
'should contain 4 records');
assert.ok(!form.$('.o_kanban_record:contains(blue)').length,
'the removed record should not be in kanban anymore');
// save the record
await testUtils.form.clickSave(form);
form.destroy();
});
QUnit.test('many2many kanban(editable): properly handle create_text node option', async function (assert) {
assert.expect(1);
this.data.partner.records[0].timmy = [12];
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '',
res_id: 1,
});
await testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_many2many[name="timmy"] .o-kanban-button-new').text().trim(),
"Add timmy", "In M2M Kanban, Add button should have 'Add timmy' label");
form.destroy();
});
QUnit.test('many2many kanban: create action disabled', async function (assert) {
assert.expect(4);
this.data.partner.records[0].timmy = [12, 14];
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '',
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '' +
'' +
'',
},
res_id: 1,
session: { user_context: {} },
});
assert.ok(!form.$('.o-kanban-button-new').length,
'"Add" button should not be available in readonly');
await testUtils.form.clickEdit(form);
assert.ok(form.$('.o-kanban-button-new').length,
'"Add" button should be available in edit');
assert.ok(form.$('.o_kanban_view .delete_icon').length,
'delete icon should be visible in edit');
await testUtils.dom.click(form.$('.o-kanban-button-new'));
assert.strictEqual($('.modal .modal-footer .btn-primary').length, 1, // only button 'Select'
'"Create" button should not be available in the modal');
form.destroy();
});
QUnit.test('many2many kanban: conditional create/delete actions', async function (assert) {
assert.expect(6);
this.data.partner.records[0].timmy = [12, 14];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner_type,false,form': '',
'partner_type,false,list': '',
'partner_type,false,search': '',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// color is red
assert.containsOnce(form, '.o-kanban-button-new', '"Add" button should be available');
await testUtils.dom.click(form.$('.o_kanban_record:contains(silver)'));
assert.containsOnce(document.body, '.modal .modal-footer .o_btn_remove',
'remove button should be visible in modal');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
await testUtils.dom.click(form.$('.o-kanban-button-new'));
assert.containsN(document.body, '.modal .modal-footer button', 3,
'there should be 3 buttons available in the modal');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// set color to black
await testUtils.fields.editSelect(form.$('select[name="color"]'), '"black"');
assert.containsOnce(form, '.o-kanban-button-new',
'"Add" button should still be available even after color field changed');
await testUtils.dom.click(form.$('.o-kanban-button-new'));
// only select and cancel button should be available, create
// button should be removed based on color field condition
assert.containsN(document.body, '.modal .modal-footer button', 2,
'"Create" button should not be available in the modal after color field changed');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
await testUtils.dom.click(form.$('.o_kanban_record:contains(silver)'));
assert.containsNone(document.body, '.modal .modal-footer .o_btn_remove',
'remove button should be visible in modal');
form.destroy();
});
QUnit.test('many2many list (non editable): edition', async function (assert) {
assert.expect(29);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 });
this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' };
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '',
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
res_id: 1,
mockRPC: function (route, args) {
if (args.method !== 'load_views') {
assert.step(_.last(route.split('/')));
}
if (args.method === 'write' && args.model === 'partner') {
assert.deepEqual(args.args[1].timmy, [
[6, false, [12, 15]],
]);
}
return this._super.apply(this, arguments);
},
});
assert.containsNone(form.$('.o_list_record_remove'),
'delete icon should not be visible in readonly');
assert.containsNone(form.$('.o_field_x2many_list_row_add'),
'"Add an item" should not be visible in readonly');
await testUtils.form.clickEdit(form);
assert.containsN(form, '.o_list_view td.o_list_number', 2,
'should contain 2 records');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold',
'display_name of first subrecord should be the one in DB');
assert.ok(form.$('.o_list_record_remove').length,
'delete icon should be visible in edit');
assert.ok(form.$('.o_field_x2many_list_row_add').length,
'"Add an item" should be visible in edit');
// edit existing subrecord
await testUtils.dom.click(form.$('.o_list_view tbody tr:first()'));
assert.containsNone($('.modal .modal-footer .o_btn_remove'),
'there should not be a "Remove" button in the modal footer');
await testUtils.fields.editInput($('.modal .o_form_view input'), 'new name');
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
'value of subrecord should have been updated');
// add new subrecords
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.containsNone($('.modal .modal-footer .o_btn_remove'),
'there should not be a "Remove" button in the modal footer');
assert.strictEqual($('.modal .o_list_view').length, 1,
"a modal should be open");
assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1,
"the list should contain one row");
await testUtils.dom.click($('.modal .o_list_view .o_data_row'));
assert.strictEqual($('.modal .o_list_view').length, 0,
"the modal should be closed");
assert.containsN(form, '.o_list_view td.o_list_number', 3,
'should contain 3 subrecords');
// remove subrecords
await testUtils.dom.click(form.$('.o_list_record_remove:nth(1)'));
assert.containsN(form, '.o_list_view td.o_list_number', 2,
'should contain 2 subrecords');
assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), 'new name',
'the updated row still has the correct values');
// save
await testUtils.form.clickSave(form);
assert.containsN(form, '.o_list_view td.o_list_number', 2,
'should contain 2 subrecords');
assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(),
'new name', 'the updated row still has the correct values');
assert.verifySteps([
'read', // main record
'read', // relational field
'read', // relational record in dialog
'write', // save relational record from dialog
'read', // relational field (updated)
'search_read', // list view in dialog
'read', // relational field (updated)
'write', // save main record
'read', // main record
'read', // relational field
]);
form.destroy();
});
QUnit.test('many2many list (editable): edition', async function (assert) {
assert.expect(31);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 });
this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' };
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'',
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
mockRPC: function (route, args) {
if (args.method !== 'load_views') {
assert.step(_.last(route.split('/')));
}
if (args.method === 'write') {
assert.deepEqual(args.args[1].timmy, [
[6, false, [12, 15]],
[1, 12, { display_name: 'new name' }],
]);
}
return this._super.apply(this, arguments);
},
res_id: 1,
});
assert.ok(!form.$('.o_list_record_remove').length,
'delete icon should not be visible in readonly');
assert.ok(!form.$('.o_field_x2many_list_row_add').length,
'"Add an item" should not be visible in readonly');
await testUtils.form.clickEdit(form);
assert.containsN(form, '.o_list_view td.o_list_number', 2,
'should contain 2 records');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold',
'display_name of first subrecord should be the one in DB');
assert.ok(form.$('.o_list_record_remove').length,
'delete icon should be visible in edit');
assert.hasClass(form.$('td.o_list_record_remove button').first(),'fa fa-times',
"should have X icons to remove (unlink) records");
assert.ok(form.$('.o_field_x2many_list_row_add').length,
'"Add an item" should not visible in edit');
// edit existing subrecord
await testUtils.dom.click(form.$('.o_list_view tbody td:first()'));
assert.ok(!$('.modal').length,
'in edit, clicking on a subrecord should not open a dialog');
assert.hasClass(form.$('.o_list_view tbody tr:first()'),'o_selected_row',
'first row should be in edition');
await testUtils.fields.editInput(form.$('.o_list_view input:first()'), 'new name');
assert.hasClass(form.$('.o_list_view .o_data_row:first'),'o_selected_row',
'first row should still be in edition');
assert.strictEqual(form.$('.o_list_view input[name=display_name]').get(0),
document.activeElement, 'edited field should still have the focus');
await testUtils.dom.click(form.$el);
assert.doesNotHaveClass(form.$('.o_list_view tbody tr:first'), 'o_selected_row',
'first row should not be in edition anymore');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
'value of subrecord should have been updated');
assert.verifySteps(['read', 'read']);
// add new subrecords
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.strictEqual($('.modal .o_list_view').length, 1,
"a modal should be open");
assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1,
"the list should contain one row");
await testUtils.dom.click($('.modal .o_list_view .o_data_row'));
assert.strictEqual($('.modal .o_list_view').length, 0,
"the modal should be closed");
assert.containsN(form, '.o_list_view td.o_list_number', 3,
'should contain 3 subrecords');
// remove subrecords
await testUtils.dom.click(form.$('.o_list_record_remove:nth(1)'));
assert.containsN(form, '.o_list_view td.o_list_number', 2,
'should contain 2 subrecord');
assert.strictEqual(form.$('.o_list_view tbody .o_data_row td:first').text(),
'new name', 'the updated row still has the correct values');
// save
await testUtils.form.clickSave(form);
assert.containsN(form, '.o_list_view td.o_list_number', 2,
'should contain 2 subrecords');
assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(),
'new name', 'the updated row still has the correct values');
assert.verifySteps([
'search_read', // list view in dialog
'read', // relational field (updated)
'write', // save main record
'read', // main record
'read', // relational field
]);
form.destroy();
});
QUnit.test('many2many: create & delete attributes', async function (assert) {
assert.expect(4);
this.data.partner.records[0].timmy = [12, 14];
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'',
res_id: 1,
});
await testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_x2many_list_row_add', "should have the 'Add an item' link");
assert.containsN(form, '.o_list_record_remove', 2, "should have the 'Add an item' link");
form.destroy();
form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'',
res_id: 1,
});
await testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_x2many_list_row_add', "should have the 'Add an item' link");
assert.containsN(form, '.o_list_record_remove', 2, "each record should have the 'Remove Item' link");
form.destroy();
});
QUnit.test('many2many list: create action disabled', async function (assert) {
assert.expect(2);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'',
res_id: 1,
});
assert.containsNone(form, '.o_field_x2many_list_row_add',
'"Add an item" link should not be available in readonly');
await testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_x2many_list_row_add',
'"Add an item" link should be available in edit');
form.destroy();
});
QUnit.test('fieldmany2many list comodel not writable', async function (assert) {
/**
* Many2Many List should behave as the m2m_tags
* that is, the relation can be altered even if the comodel itself is not CRUD-able
* This can happen when someone has read access alone on the comodel
* and full CRUD on the current model
*/
assert.expect(12);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch:`
`,
archs:{
'partner_type,false,list': `
`,
'partner_type,false,search': '',
},
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/create') {
assert.deepEqual(args.args[0], {timmy: [[6, false, [12]]]});
}
if (route === '/web/dataset/call_kw/partner/write') {
assert.deepEqual(args.args[1], {timmy: [[6, false, []]]});
}
return this._super.apply(this, arguments);
}
});
assert.containsOnce(form, '.o_field_many2many .o_field_x2many_list_row_add');
await testUtils.dom.click(form.$('.o_field_many2many .o_field_x2many_list_row_add a'));
assert.containsOnce(document.body, '.modal');
assert.containsN($('.modal-footer'), 'button', 2);
assert.containsOnce($('.modal-footer'), 'button.o_select_button');
assert.containsOnce($('.modal-footer'), 'button.o_form_button_cancel');
await testUtils.dom.click($('.modal .o_list_view .o_data_cell:first()'));
assert.containsNone(document.body, '.modal');
assert.containsOnce(form, '.o_field_many2many .o_data_row');
assert.equal($('.o_field_many2many .o_data_row').text(), 'gold');
assert.containsOnce(form, '.o_field_many2many .o_field_x2many_list_row_add');
await testUtils.form.clickSave(form);
await testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_many2many .o_data_row .o_list_record_remove');
await testUtils.dom.click(form.$('.o_field_many2many .o_data_row .o_list_record_remove'));
await testUtils.form.clickSave(form);
form.destroy();
});
QUnit.test('many2many list: conditional create/delete actions', async function (assert) {
assert.expect(6);
this.data.partner.records[0].timmy = [12, 14];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// color is red -> create and delete actions are available
assert.containsOnce(form, '.o_field_x2many_list_row_add',
"should have the 'Add an item' link");
assert.containsN(form, '.o_list_record_remove', 2,
"should have two remove icons");
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.containsN(document.body, '.modal .modal-footer button', 3,
'there should be 3 buttons available in the modal');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// set color to black -> create and delete actions are no longer available
await testUtils.fields.editSelect(form.$('select[name="color"]'), '"black"');
// add a line and remove icon should still be there as they don't create/delete records,
// but rather add/remove links
assert.containsOnce(form, '.o_field_x2many_list_row_add',
'"Add a line" button should still be available even after color field changed');
assert.containsN(form, '.o_list_record_remove', 2,
"should still have remove icon even after color field changed");
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.containsN(document.body, '.modal .modal-footer button', 2,
'"Create" button should not be available in the modal after color field changed');
form.destroy();
});
QUnit.test('many2many field with link/unlink options (list)', async function (assert) {
assert.expect(5);
this.data.partner.records[0].timmy = [12, 14];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// color is red -> link and unlink actions are available
assert.containsOnce(form, '.o_field_x2many_list_row_add',
"should have the 'Add an item' link");
assert.containsN(form, '.o_list_record_remove', 2,
"should have two remove icons");
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.containsN(document.body, '.modal .modal-footer button', 3,
'there should be 3 buttons available in the modal (Create action is available)');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// set color to black -> link and unlink actions are no longer available
await testUtils.fields.editSelect(form.$('select[name="color"]'), '"black"');
assert.containsNone(form, '.o_field_x2many_list_row_add',
'"Add a line" should no longer be available after color field changed');
assert.containsNone(form, '.o_list_record_remove',
"should no longer have remove icon after color field changed");
form.destroy();
});
QUnit.test('many2many field with link/unlink options (list, create="0")', async function (assert) {
assert.expect(5);
this.data.partner.records[0].timmy = [12, 14];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// color is red -> link and unlink actions are available
assert.containsOnce(form, '.o_field_x2many_list_row_add',
"should have the 'Add an item' link");
assert.containsN(form, '.o_list_record_remove', 2,
"should have two remove icons");
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.containsN(document.body, '.modal .modal-footer button', 2,
'there should be 2 buttons available in the modal (Create action is not available)');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// set color to black -> link and unlink actions are no longer available
await testUtils.fields.editSelect(form.$('select[name="color"]'), '"black"');
assert.containsNone(form, '.o_field_x2many_list_row_add',
'"Add a line" should no longer be available after color field changed');
assert.containsNone(form, '.o_list_record_remove',
"should no longer have remove icon after color field changed");
form.destroy();
});
QUnit.test('many2many field with link option (kanban)', async function (assert) {
assert.expect(3);
this.data.partner.records[0].timmy = [12, 14];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// color is red -> link and unlink actions are available
assert.containsOnce(form, '.o-kanban-button-new', "should have the 'Add' button");
await testUtils.dom.click(form.$('.o-kanban-button-new'));
assert.containsN(document.body, '.modal .modal-footer button', 3,
'there should be 3 buttons available in the modal (Create action is available');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// set color to black -> link and unlink actions are no longer available
await testUtils.fields.editSelect(form.$('select[name="color"]'), '"black"');
assert.containsNone(form, '.o-kanban-button-new',
'"Add" should no longer be available after color field changed');
form.destroy();
});
QUnit.test('many2many field with link option (kanban, create="0")', async function (assert) {
assert.expect(3);
this.data.partner.records[0].timmy = [12, 14];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
`,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// color is red -> link and unlink actions are available
assert.containsOnce(form, '.o-kanban-button-new', "should have the 'Add' button");
await testUtils.dom.click(form.$('.o-kanban-button-new'));
assert.containsN(document.body, '.modal .modal-footer button', 2,
'there should be 2 buttons available in the modal (Create action is not available');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// set color to black -> link and unlink actions are no longer available
await testUtils.fields.editSelect(form.$('select[name="color"]'), '"black"');
assert.containsNone(form, '.o-kanban-button-new',
'"Add" should no longer be available after color field changed');
form.destroy();
});
QUnit.test('many2many list: list of id as default value', async function (assert) {
assert.expect(1);
this.data.partner.fields.turtles.default = [2, 3];
this.data.partner.fields.turtles.type = "many2many";
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'',
});
assert.strictEqual(form.$('td.o_data_cell').text(), "blipkawa",
"should have loaded default data");
form.destroy();
});
QUnit.test('many2many checkboxes with default values', async function (assert) {
assert.expect(7);
this.data.partner.fields.turtles.default = [3];
this.data.partner.fields.turtles.type = "many2many";
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'',
mockRPC: function (route, args) {
if (args.method === 'create') {
assert.deepEqual(args.args[0].turtles, [[6, false, [1]]],
"correct values should have been sent to create");
}
return this._super.apply(this, arguments);
}
});
assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(0).prop('checked'),
"first checkbox should not be checked");
assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(1).prop('checked'),
"second checkbox should not be checked");
assert.ok(form.$('.o_form_view .custom-checkbox input').eq(2).prop('checked'),
"third checkbox should be checked");
await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input:checked'));
await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first());
await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first());
await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first());
assert.ok(form.$('.o_form_view .custom-checkbox input').eq(0).prop('checked'),
"first checkbox should be checked");
assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(1).prop('checked'),
"second checkbox should not be checked");
assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(2).prop('checked'),
"third checkbox should not be checked");
await testUtils.form.clickSave(form);
form.destroy();
});
QUnit.test('many2many list with x2many: add a record', async function (assert) {
assert.expect(18);
this.data.partner_type.fields.m2m = {
string: "M2M", type: "many2many", relation: 'turtle',
};
this.data.partner_type.records[0].m2m = [1, 2];
this.data.partner_type.records[1].m2m = [2, 3];
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'',
res_id: 1,
archs: {
'partner_type,false,list': '' +
'' +
'' +
'',
'partner_type,false,search': '' +
'' +
'',
},
mockRPC: function (route, args) {
if (args.method !== 'load_views') {
assert.step(_.last(route.split('/')) + ' on ' + args.model);
}
if (args.model === 'turtle') {
assert.step(JSON.stringify(args.args[0])); // the read ids
}
return this._super.apply(this, arguments);
},
viewOptions: {
mode: 'edit',
},
});
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
await testUtils.dom.click($('.modal .o_data_row:first'));
assert.containsOnce(form, '.o_data_row',
"the record should have been added to the relation");
assert.strictEqual(form.$('.o_data_row:first .o_badge_text').text(), 'leonardodonatello',
"inner m2m should have been fetched and correctly displayed");
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
await testUtils.dom.click($('.modal .o_data_row:first'));
assert.containsN(form, '.o_data_row', 2,
"the second record should have been added to the relation");
assert.strictEqual(form.$('.o_data_row:nth(1) .o_badge_text').text(), 'donatelloraphael',
"inner m2m should have been fetched and correctly displayed");
assert.verifySteps([
'read on partner',
'search_read on partner_type',
'read on turtle',
'[1,2,3]',
'read on partner_type',
'read on turtle',
'[1,2]',
'search_read on partner_type',
'read on turtle',
'[2,3]',
'read on partner_type',
'read on turtle',
'[2,3]',
]);
form.destroy();
});
QUnit.test('many2many with a domain', async function (assert) {
// The domain specified on the field should not be replaced by the potential
// domain the user writes in the dialog, they should rather be concatenated
assert.expect(2);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'',
res_id: 1,
archs: {
'partner_type,false,list': '' +
'' +
'',
'partner_type,false,search': '' +
'' +
'',
},
viewOptions: {
mode: 'edit',
},
});
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.strictEqual($('.modal .o_data_row').length, 1,
"should contain only one row (gold)");
await cpHelpers.editSearch('.modal', 's');
await cpHelpers.validateSearch('.modal');
assert.strictEqual($('.modal .o_data_row').length, 0, "should contain no row");
form.destroy();
});
QUnit.test('many2many list with onchange and edition of a record', async function (assert) {
assert.expect(8);
this.data.partner.fields.turtles.type = "many2many";
this.data.partner.onchanges.turtles = function () { };
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'',
res_id: 1,
archs: {
'turtle,false,form': '',
},
mockRPC: function (route, args) {
assert.step(args.method);
return this._super.apply(this, arguments);
},
});
await testUtils.form.clickEdit(form);
await testUtils.dom.click(form.$('td.o_data_cell:first'));
await testUtils.dom.click($('.modal-body input[type="checkbox"]'));
await testUtils.dom.click($('.modal .modal-footer .btn-primary').first());
// there is nothing left to save -> should not do a 'write' RPC
await testUtils.form.clickSave(form);
assert.verifySteps([
'read', // read initial record (on partner)
'read', // read many2many turtles
'load_views', // load arch of turtles form view
'read', // read missing field when opening record in modal form view
'write', // when saving the modal
'onchange', // onchange should be triggered on partner
'read', // reload many2many
]);
form.destroy();
});
QUnit.test('onchange with 40+ commands for a many2many', async function (assert) {
// this test ensures that the basic_model correctly handles more LINK_TO
// commands than the limit of the dataPoint (40 for x2many kanban)
assert.expect(24);
// create a lot of partner_types that will be linked by the onchange
var commands = [[5]];
for (var i = 0; i < 45; i++) {
var id = 100 + i;
this.data.partner_type.records.push({ id: id, display_name: "type " + id });
commands.push([4, id]);
}
this.data.partner.onchanges = {
foo: function (obj) {
obj.timmy = commands;
},
};
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'' +
'' +
'' +
'',
res_id: 1,
mockRPC: function (route, args) {
assert.step(args.method);
if (args.method === 'write') {
assert.strictEqual(args.args[1].timmy[0][0], 6,
"should send a command 6");
assert.strictEqual(args.args[1].timmy[0][2].length, 45,
"should replace with 45 ids");
}
return this._super.apply(this, arguments);
},
viewOptions: {
mode: 'edit',
},
});
assert.verifySteps(['read']);
await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange');
assert.verifySteps(['onchange', 'read']);
assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
'1-40 / 45', "pager should be correct");
assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
'there should be 40 records displayed on page 1');
await testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next'));
assert.verifySteps(['read']);
assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
'41-45 / 45', "pager should be correct");
assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5,
'there should be 5 records displayed on page 2');
await testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
'1-40 / 45', "pager should be correct");
assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
'there should be 40 records displayed on page 1');
await testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next'));
assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
'41-45 / 45', "pager should be correct");
assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5,
'there should be 5 records displayed on page 2');
await testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next'));
assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
'1-40 / 45', "pager should be correct");
assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
'there should be 40 records displayed on page 1');
assert.verifySteps(['write', 'read', 'read', 'read']);
form.destroy();
});
QUnit.test('default_get, onchange, onchange on m2m', async function (assert) {
assert.expect(1);
this.data.partner.onchanges.int_field = function (obj) {
if (obj.int_field === 2) {
assert.deepEqual(obj.timmy, [
[6, false, [12]],
[1, 12, { display_name: 'gold' }]
]);
}
obj.timmy = [
[5],
[1, 12, { display_name: 'gold' }]
];
};
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'',
});
await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2);
form.destroy();
});
QUnit.test('widget many2many_tags', async function (assert) {
assert.expect(1);
this.data.turtle.records[0].partner_ids = [2];
var form = await createView({
View: FormView,
model: 'turtle',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'',
res_id: 1,
});
assert.deepEqual(
form.$('.o_field_many2manytags.o_field_widget .badge .o_badge_text').attr('title'),
'second record', 'the title should be filled in'
);
form.destroy();
});
QUnit.test('many2many tags widget: select multiple records', async function (assert) {
assert.expect(5);
for (var i = 1; i <= 10; i++) {
this.data.partner_type.records.push({ id: 100 + i, display_name: "Partner" + i});
}
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'',
res_id: 1,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
});
await testUtils.form.clickEdit(form);
await testUtils.fields.many2one.clickOpenDropdown('timmy');
await testUtils.fields.many2one.clickItem('timmy','Search More');
assert.ok($('.modal .o_list_view'), "should have open the modal");
// + 1 for the select all
assert.containsN($(document),'.modal .o_list_view .o_list_record_selector input', this.data.partner_type.records.length + 1,
"Should have record selector checkboxes to select multiple records");
//multiple select tag
await testUtils.dom.click($('.modal .o_list_view thead .o_list_record_selector input'));
assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled");
await testUtils.dom.click($('.o_select_button'));
assert.containsNone($(document),'.modal .o_list_view', "should have closed the modal");
assert.containsN(form, '.o_field_many2manytags[name="timmy"] .badge', this.data.partner_type.records.length,
"many2many tag should now contain 12 records");
form.destroy();
});
QUnit.test("many2many tags widget: select multiple records doesn't show already added tags", async function (assert) {
assert.expect(5);
for (var i = 1; i <= 10; i++) {
this.data.partner_type.records.push({ id: 100 + i, display_name: "Partner" + i});
}
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'',
res_id: 1,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
});
await testUtils.form.clickEdit(form);
await testUtils.fields.many2one.clickOpenDropdown('timmy');
await testUtils.fields.many2one.clickItem('timmy','Partner1');
await testUtils.fields.many2one.clickOpenDropdown('timmy');
await testUtils.fields.many2one.clickItem('timmy','Search More');
assert.ok($('.modal .o_list_view'), "should have open the modal");
// -1 for the one that is already on the form & +1 for the select all,
assert.containsN($(document), '.modal .o_list_view .o_list_record_selector input', this.data.partner_type.records.length - 1 + 1,
"Should have record selector checkboxes to select multiple records");
//multiple select tag
await testUtils.dom.click($('.modal .o_list_view thead .o_list_record_selector input'));
assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled");
await testUtils.dom.click($('.o_select_button'));
assert.containsNone($(document),'.modal .o_list_view', "should have closed the modal");
assert.containsN(form, '.o_field_many2manytags[name="timmy"] .badge', this.data.partner_type.records.length,
"many2many tag should now contain 12 records");
form.destroy();
});
QUnit.test("many2many tags widget: save&new in edit mode doesn't close edit window", async function (assert) {
assert.expect(5);
for (var i = 1; i <= 10; i++) {
this.data.partner_type.records.push({ id: 100 + i, display_name: "Partner" + i});
}
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'',
res_id: 1,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
'partner_type,false,form': ''
},
});
await testUtils.form.clickEdit(form);
await testUtils.fields.many2one.createAndEdit('timmy',"Ralts");
assert.containsOnce($(document), '.modal .o_form_view', "should have opened the modal");
// Create multiple records with save & new
await testUtils.fields.editInput($('.modal input:first'), 'Ralts');
await testUtils.dom.click($('.modal .btn-primary:nth-child(2)'));
assert.containsOnce($(document), '.modal .o_form_view', "modal should still be open");
assert.equal($('.modal input:first')[0].value, '', "input should be empty")
// Create another record and click save & close
await testUtils.fields.editInput($('.modal input:first'), 'Pikachu');
await testUtils.dom.click($('.modal .btn-primary:first'));
assert.containsNone($(document),'.modal .o_list_view', "should have closed the modal");
assert.containsN(form, '.o_field_many2manytags[name="timmy"] .badge', 2, "many2many tag should now contain 2 records");
form.destroy();
});
QUnit.test("many2many tags widget: make tag name input field blank on Save&New", async function (assert) {
assert.expect(4);
let onchangeCalls = 0;
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '',
archs: {
'partner_type,false,form': ''
},
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'onchange') {
if (onchangeCalls === 0) {
assert.deepEqual(args.kwargs.context, { default_name: 'hello' },
"context should have default_name with 'hello' as value");
}
if (onchangeCalls === 1) {
assert.deepEqual(args.kwargs.context, {},
"context should have default_name with false as value");
}
onchangeCalls++;
}
return this._super.apply(this, arguments);
},
});
await testUtils.form.clickEdit(form);
await testUtils.fields.editInput($('.o_field_widget input'), 'hello');
await testUtils.fields.many2one.clickItem('timmy', 'Create and Edit');
assert.strictEqual(document.querySelector('.modal .o_form_view input').value, "hello",
"should contain the 'hello' in the tag name input field");
// Create record with save & new
await testUtils.dom.click(document.querySelector('.modal .btn-primary:nth-child(2)'));
assert.strictEqual(document.querySelector('.modal .o_form_view input').value, "",
"should display the blank value in the tag name input field");
form.destroy();
});
QUnit.test('many2many list add *many* records, remove, re-add', async function (assert) {
assert.expect(5);
this.data.partner.fields.timmy.domain = [['color', '=', 2]];
this.data.partner.fields.timmy.onChange = true;
this.data.partner_type.fields.product_ids = { string: "Product", type: "many2many", relation: 'product' };
for (var i = 0; i < 50; i++) {
var new_record_partner_type = { id: 100 + i, display_name: "batch" + i, color: 2 };
this.data.partner_type.records.push(new_record_partner_type);
}
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '' +
'' +
'' +
'' +
'' +
'' +
'' +
'',
res_id: 1,
archs: {
'partner_type,false,list': '',
'partner_type,false,search': '',
},
mockRPC: function (route, args) {
if (args.method === 'get_formview_id') {
assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id");
return Promise.resolve(false);
}
return this._super(route, args);
},
});
// First round: add 51 records in batch
await testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit'));
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
var $modal = $('.modal-lg');
assert.equal($modal.length, 1,
'There should be one modal');
await testUtils.dom.click($modal.find('thead input[type=checkbox]'));
await testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button'));
assert.strictEqual(form.$('.o_data_row').length, 51,
'We should have added all the records present in the search view to the m2m field'); // the 50 in batch + 'gold'
await testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_save'));
// Secound round: remove one record
await testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit'));
var trash_buttons = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_list_record_remove');
await testUtils.dom.click(trash_buttons.first());
var pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit');
assert.equal(pager_limit.text(), '50',
'We should have 50 records in the m2m field');
// Third round: re-add 1 records
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
$modal = $('.modal-lg');
assert.equal($modal.length, 1,
'There should be one modal');
await testUtils.dom.click($modal.find('thead input[type=checkbox]'));
await testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button'));
assert.strictEqual(form.$('.o_data_row').length, 51,
'We should have 51 records in the m2m field');
form.destroy();
});
QUnit.test('many2many_tags widget: conditional create/delete actions', async function (assert) {
assert.expect(10);
this.data.turtle.records[0].partner_ids = [2];
for (var i = 1; i <= 10; i++) {
this.data.partner.records.push({ id: 100 + i, display_name: "Partner" + i });
}
const form = await createView({
View: FormView,
model: 'turtle',
data: this.data,
arch: `
`,
archs: {
'partner,false,list': '',
'partner,false,search': '',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// turtle_bar is true -> create and delete actions are available
assert.containsOnce(form, '.o_field_many2manytags.o_field_widget .badge .o_delete',
'X icon on badges should not be available');
await testUtils.fields.many2one.clickOpenDropdown('partner_ids');
const $dropdown1 = form.$('.o_field_many2one input').autocomplete('widget');
assert.containsOnce($dropdown1, 'li.o_m2o_start_typing:contains(Start typing...)',
'autocomplete should contain Start typing...');
await testUtils.fields.many2one.clickItem('partner_ids', 'Search More');
assert.containsN(document.body, '.modal .modal-footer button', 3,
'there should be 3 buttons (Select, Create and Cancel) available in the modal footer');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// type something that doesn't exist
await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'),
'Something that does not exist', 'keydown');
// await testUtils.nextTick();
assert.containsN(form.$('.o_field_many2one input').autocomplete('widget'), 'li.o_m2o_dropdown_option', 2,
'autocomplete should contain Create and Create and Edit... options');
// set turtle_bar false -> create and delete actions are no longer available
await testUtils.dom.click(form.$('.o_field_widget[name="turtle_bar"] input').first());
// remove icon should still be there as it doesn't delete records but rather remove links
assert.containsOnce(form, '.o_field_many2manytags.o_field_widget .badge .o_delete',
'X icon on badge should still be there even after turtle_bar is not checked');
await testUtils.fields.many2one.clickOpenDropdown('partner_ids');
const $dropdown2 = form.$('.o_field_many2one input').autocomplete('widget');
// only Search More option should be available
assert.containsOnce($dropdown2, 'li.o_m2o_dropdown_option',
'autocomplete should contain only one option');
assert.containsOnce($dropdown2, 'li.o_m2o_dropdown_option:contains(Search More)',
'autocomplete option should be Search More');
await testUtils.fields.many2one.clickItem('partner_ids', 'Search More');
assert.containsN(document.body, '.modal .modal-footer button', 2,
'there should be 2 buttons (Select and Cancel) available in the modal footer');
await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel'));
// type something that doesn't exist
await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'),
'Something that does not exist', 'keyup');
// await testUtils.nextTick();
// only Search More option should be available
assert.containsOnce($dropdown2, 'li.o_m2o_dropdown_option',
'autocomplete should contain only one option');
assert.containsOnce($dropdown2, 'li.o_m2o_dropdown_option:contains(Search More)',
'autocomplete option should be Search More');
form.destroy();
});
QUnit.test('failing many2one quick create in a many2many_tags', async function (assert) {
assert.expect(5);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '',
mockRPC(route, args) {
if (args.method === 'name_create') {
return Promise.reject();
}
if (args.method === 'create') {
assert.deepEqual(args.args[0], {
color: 8,
name: 'new partner',
});
}
return this._super.apply(this, arguments);
},
archs: {
'partner_type,false,form': `
`,
},
});
assert.containsNone(form, '.o_field_many2manytags .badge');
// try to quick create a record
await testUtils.dom.triggerEvent(form.$('.o_field_many2one input'), 'focus');
await testUtils.fields.many2one.searchAndClickItem('timmy', {
search: 'new partner',
item: 'Create'
});
// as the quick create failed, a dialog should be open to 'slow create' the record
assert.containsOnce(document.body, '.modal .o_form_view');
assert.strictEqual($('.modal .o_field_widget[name=name]').val(), 'new partner');
await testUtils.fields.editInput($('.modal .o_field_widget[name=color]'), 8);
await testUtils.modal.clickButton('Save & Close');
assert.containsOnce(form, '.o_field_many2manytags .badge');
form.destroy();
});
});
});
});