Files
odoo_source/addons/web/static/tests/fields/relational_fields_tests.js
T
Vincent Schippefilt 98936f79a1 [REF] web: split relational fields tests into multiple files
The file relation_fields_tests.js was a huge 12K lines files for which some editors have a
hard time managing.

We have split it in 4 files to be more managable and removed remove unused data
and import.

closes odoo/odoo#28561
2018-11-12 12:36:53 +00:00

2602 lines
104 KiB
JavaScript

odoo.define('web.relational_fields_tests', function (require) {
"use strict";
var concurrency = require('web.concurrency');
var FormView = require('web.FormView');
var ListView = require('web.ListView');
var relationalFields = require('web.relational_fields');
var testUtils = require('web.test_utils');
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"},
bar: {string: "Bar", type: "boolean", default: true},
int_field: {string: "int_field", type: "integer", sortable: true},
qux: {string: "Qux", type: "float", digits: [16,1] },
p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'},
turtles: {string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu'},
trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'},
product_id: {string: "Product", type: "many2one", relation: 'product'},
color: {
type: "selection",
selection: [['red', "Red"], ['black', "Black"]],
default: 'red',
string: "Color",
},
date: {string: "Some Date", type: "date"},
datetime: {string: "Datetime Field", type: 'datetime'},
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",
bar: true,
foo: "yop",
int_field: 10,
qux: 0.44,
p: [],
turtles: [2],
timmy: [],
trululu: 4,
user_id: 17,
reference: 'product,37',
}, {
id: 2,
display_name: "second record",
bar: true,
foo: "blip",
int_field: 9,
qux: 13,
p: [],
timmy: [],
trululu: 1,
product_id: 37,
date: "2017-01-25",
datetime: "2016-12-12 10:55:05",
user_id: 17,
}, {
id: 4,
display_name: "aaa",
bar: false,
}],
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},
turtle_int: {string: "int", type: "integer", sortable: true},
turtle_description: {string: "Description", type: "text"},
turtle_trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
turtle_ref: {string: "Reference", type: 'reference', selection: [
["product", "Product"], ["partner", "Partner"]]},
product_id: {string: "Product", type: "many2one", relation: 'product', required: true},
partner_ids: {string: "Partner", type: "many2many", relation: 'partner'},
},
records: [{
id: 1,
display_name: "leonardo",
turtle_bar: true,
turtle_foo: "yop",
partner_ids: [],
}, {
id: 2,
display_name: "donatello",
turtle_bar: true,
turtle_foo: "blip",
turtle_int: 9,
partner_ids: [2,4],
}, {
id: 3,
display_name: "raphael",
product_id: 37,
turtle_bar: false,
turtle_foo: "kawa",
turtle_int: 21,
partner_ids: [],
turtle_ref: 'product,37',
}],
onchanges: {},
},
user: {
fields: {
name: {string: "Name", type: "char"},
partner_ids: {string: "one2many partners field", type: "one2many", relation: 'partner', relation_field: 'user_id'},
},
records: [{
id: 17,
name: "Aline",
partner_ids: [1, 2],
}, {
id: 19,
name: "Christine",
}]
},
};
}
}, function () {
QUnit.module('FieldStatus');
QUnit.test('static statusbar widget on many2one field', function (assert) {
assert.expect(5);
this.data.partner.fields.trululu.domain = "[('bar', '=', True)]";
this.data.partner.records[1].bar = false;
var count = 0;
var nb_fields_fetched;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar"/></header>' +
// the following field seem useless, but its presence was the
// cause of a crash when evaluating the field domain.
'<field name="timmy" invisible="1"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'search_read') {
count++;
nb_fields_fetched = args.kwargs.fields.length;
}
return this._super.apply(this, arguments);
},
res_id: 1,
config: {device: {isMobile: false}},
});
assert.strictEqual(count, 1, 'once search_read should have been done to fetch the relational values');
assert.strictEqual(nb_fields_fetched, 1, 'search_read should only fetch field id');
assert.strictEqual(form.$('.o_statusbar_status button:not(.dropdown-toggle)').length, 2, "should have 2 status");
assert.strictEqual(form.$('.o_statusbar_status button:disabled').length, 2,
"all status should be disabled");
assert.ok(form.$('.o_statusbar_status button[data-value="4"]').hasClass('btn-primary'),
"selected status should be btn-primary");
form.destroy();
});
QUnit.test('static statusbar widget on many2one field with domain', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<header><field name="trululu" domain="[(\'user_id\',\'=\',uid)]" widget="statusbar"/></header>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'search_read') {
assert.deepEqual(args.kwargs.domain, ['|', ['id', '=', 4], ['user_id', '=', 17]],
"search_read should sent the correct domain");
}
return this._super.apply(this, arguments);
},
res_id: 1,
session: {user_context: {uid: 17}},
});
form.destroy();
});
QUnit.test('clickable statusbar widget on many2one field', function (assert) {
assert.expect(3);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar" options=\'{"clickable": "1"}\'/></header>' +
'</form>',
res_id: 1,
config: {device: {isMobile: false}},
});
var $selectedStatus = form.$('.o_statusbar_status button[data-value="4"]');
assert.ok($selectedStatus.hasClass('btn-primary') && $selectedStatus.hasClass('disabled'),
"selected status should be btn-primary and disabled");
var $clickable = form.$('.o_statusbar_status button.btn-secondary:not(.dropdown-toggle):not(:disabled)');
assert.strictEqual($clickable.length, 2,
"other status should be btn-secondary and not disabled");
$clickable.last().click(); // (last is visually the first here (css))
var $status = form.$('.o_statusbar_status button[data-value="1"]');
assert.ok($status.hasClass("btn-primary") && $status.hasClass("disabled"),
"value should have been updated");
form.destroy();
});
QUnit.test('statusbar with no status', function (assert) {
assert.expect(2);
this.data.product.records = [];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<header><field name="product_id" widget="statusbar"/></header>' +
'</form>',
res_id: 1,
config: {device: {isMobile: false}},
});
assert.ok(form.$('.o_statusbar_status').hasClass('o_field_empty'),
'statusbar widget should have class o_field_empty');
assert.strictEqual(form.$('.o_statusbar_status').children().length, 0,
'statusbar widget should be empty');
form.destroy();
});
QUnit.test('statusbar with domain but no value (create mode)', function (assert) {
assert.expect(1);
this.data.partner.fields.trululu.domain = "[('bar', '=', True)]";
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar"/></header>' +
'</form>',
config: {device: {isMobile: false}},
});
assert.strictEqual(form.$('.o_statusbar_status button:disabled').length, 2, "should have 2 status");
form.destroy();
});
QUnit.test('clickable statusbar should change m2o fetching domain in edit mode', function (assert) {
assert.expect(2);
this.data.partner.fields.trululu.domain = "[('bar', '=', True)]";
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar" options=\'{"clickable": "1"}\'/></header>' +
'</form>',
res_id: 1,
config: {device: {isMobile: false}},
});
form.$buttons.find('.o_form_button_edit').click();
var $buttons = form.$('.o_statusbar_status button:not(.dropdown-toggle)');
assert.strictEqual($buttons.length, 3, "there should be 3 status");
$buttons.last().click(); // (last is visually the first here (css))
assert.strictEqual(form.$('.o_statusbar_status button:not(.dropdown-toggle)').length, 2,
"there should be 2 status left");
form.destroy();
});
QUnit.test('statusbar fold_field option and statusbar_visible attribute', function (assert) {
assert.expect(2);
this.data.partner.records[0].bar = false;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar" options="{\'fold_field\': \'bar\'}"/>' +
'<field name="color" widget="statusbar" statusbar_visible="red"/></header>' +
'</form>',
res_id: 1,
config: {device: {isMobile: false}},
});
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_statusbar_status:first .dropdown-menu button.disabled').length, 1, "should have 1 folded status");
assert.strictEqual(form.$('.o_statusbar_status:last button.disabled').length, 1, "should have 1 status (other discarded)");
form.destroy();
});
QUnit.test('statusbar with dynamic domain', function (assert) {
assert.expect(5);
this.data.partner.fields.trululu.domain = "[('int_field', '>', qux)]";
this.data.partner.records[2].int_field = 0;
var rpcCount = 0;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar"/></header>' +
'<field name="qux"/>' +
'<field name="foo"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'search_read') {
rpcCount++;
}
return this._super.apply(this, arguments);
},
res_id: 1,
config: {device: {isMobile: false}},
});
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_statusbar_status button.disabled').length, 3, "should have 3 status");
assert.strictEqual(rpcCount, 1, "should have done 1 search_read rpc");
form.$('input:first').val(9.5).trigger("input").trigger("change");
assert.strictEqual(form.$('.o_statusbar_status button.disabled').length, 2, "should have 2 status");
assert.strictEqual(rpcCount, 2, "should have done 1 more search_read rpc");
form.$('input:last').val("hey").trigger("input").trigger("change");
assert.strictEqual(rpcCount, 2, "should not have done 1 more search_read rpc");
form.destroy();
});
QUnit.module('FieldSelection');
QUnit.test('widget selection in a list view', function (assert) {
assert.expect(3);
this.data.partner.records.forEach(function (r) {
r.color = 'red';
});
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree string="Colors" editable="top">' +
'<field name="color"/>' +
'</tree>',
});
assert.strictEqual(list.$('td:contains(Red)').length, 3,
"should have 3 rows with correct value");
list.$('td:contains(Red):first').click();
var $td = list.$('tbody tr.o_selected_row td:not(.o_list_record_selector)');
assert.strictEqual($td.find('select').length, 1, "td should have a child 'select'");
assert.strictEqual($td.contents().length, 1, "select tag should be only child of td");
list.destroy();
});
QUnit.test('widget selection, edition and on many2one field', function (assert) {
assert.expect(18);
this.data.partner.onchanges = {product_id: function () {}};
this.data.partner.records[0].product_id = 37;
this.data.partner.records[0].trululu = false;
var count = 0;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="product_id" widget="selection"/>' +
'<field name="trululu" widget="selection"/>' +
'<field name="color" widget="selection"/>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
count++;
assert.step(args.method);
return this._super(route, args);
},
});
assert.ok(!form.$('select').length, "should not have a select tag in dom");
assert.strictEqual(form.$('.o_field_widget[name=product_id]').text(), 'xphone',
"should have rendered the many2one field correctly");
assert.strictEqual(form.$('.o_field_widget[name=trululu]').text(), '',
"should have rendered the unset many2one field correctly");
assert.strictEqual(form.$('.o_field_widget[name=color]').text(), 'Red',
"should have rendered the selection field correctly");
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('select').length, 3,
"should have 3 select tag in dom");
assert.strictEqual(form.$('select[name="product_id"] option:contains(xphone)').length, 1,
"should have fetched xphone option");
assert.strictEqual(form.$('select[name="product_id"] option:contains(xpad)').length, 1,
"should have fetched xpad option");
assert.strictEqual(form.$('select[name="product_id"]').val(), "37",
"should have correct product_id value");
assert.strictEqual(form.$('select[name="trululu"]').val(), "false",
"should not have any value in trululu field");
form.$('select[name="product_id"]').val(41).trigger('change');
assert.strictEqual(form.$('select[name="product_id"]').val(), "41",
"should have a value of xphone");
assert.strictEqual(form.$('select[name="color"]').val(), "\"red\"",
"should have correct value in color field");
assert.verifySteps(['read', 'name_search', 'name_search', 'onchange']);
count = 0;
form.reload();
assert.strictEqual(count, 1, "should not reload product_id relation");
form.destroy();
});
QUnit.test('unset selection field with 0 as key', function (assert) {
// The server doesn't make a distinction between false value (the field
// is unset), and selection 0, as in that case the value it returns is
// false. So the client must convert false to value 0 if it exists.
assert.expect(2);
this.data.partner.fields.selection = {
type: "selection",
selection: [[0, "Value O"], [1, "Value 1"]],
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="selection"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget').text(), 'Value O',
"the displayed value should be 'Value O'");
assert.notOk(form.$('.o_field_widget').hasClass('o_field_empty'),
"should not have class o_field_empty");
form.destroy();
});
QUnit.test('unset selection field with string keys', function (assert) {
// The server doesn't make a distinction between false value (the field
// is unset), and selection 0, as in that case the value it returns is
// false. So the client must convert false to value 0 if it exists. In
// this test, it doesn't exist as keys are strings.
assert.expect(2);
this.data.partner.fields.selection = {
type: "selection",
selection: [['0', "Value O"], ['1', "Value 1"]],
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="selection"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget').text(), '',
"there should be no displayed value");
assert.ok(form.$('.o_field_widget').hasClass('o_field_empty'),
"should have class o_field_empty");
form.destroy();
});
QUnit.test('unset selection on a many2one field', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="trululu" widget="selection"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.strictEqual(args.args[1].trululu, false,
"should send 'false' as trululu value");
}
return this._super.apply(this, arguments);
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
form.$('select').val("false").trigger('change');
form.$buttons.find('.o_form_button_save').click();
form.destroy();
});
QUnit.test('field selection with many2ones and special characters', function (assert) {
assert.expect(1);
// edit the partner with id=4
this.data.partner.records[2].display_name = '<span>hey</span>';
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="trululu" widget="selection"/>' +
'</form>',
res_id: 1,
viewOptions: {mode: 'edit'},
});
assert.strictEqual(form.$('select option[value="4"]').text(), '<span>hey</span>');
form.destroy();
});
QUnit.test('widget selection on a many2one: domain updated by an onchange', function (assert) {
assert.expect(4);
this.data.partner.onchanges = {
int_field: function () {},
};
var domain = [];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="int_field"/>' +
'<field name="trululu" widget="selection"/>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'onchange') {
domain = [['id', 'in', [10]]];
return $.when({
domain: {
trululu: domain,
}
});
}
if (args.method === 'name_search') {
assert.deepEqual(args.args[1], domain,
"sent domain should be correct");
}
return this._super(route, args);
},
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual(form.$('.o_field_widget[name=trululu] option').length, 4,
"should be 4 options in the selection");
// trigger an onchange that will update the domain
form.$('.o_field_widget[name=int_field]').val(2).trigger('input');
assert.strictEqual(form.$('.o_field_widget[name=trululu] option').length, 1,
"should be 1 option in the selection");
form.destroy();
});
QUnit.test('required selection widget should not have blank option', function (assert) {
assert.expect(3);
this.data.partner.fields.feedback_value = {
type: "selection",
required: true,
selection : [['good', 'Good'], ['bad', 'Bad']],
default: 'good',
string: 'Good'
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="feedback_value"/>' +
'<field name="color" attrs="{\'required\': [(\'feedback_value\', \'=\', \'bad\')]}"/>' +
'</form>',
res_id: 1
});
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_field_widget[name=color]')[0].options.length, 3,
"non required selection field must have 3 options 1 blank option and 2 value options");
assert.strictEqual(form.$('.o_field_widget[name=feedback_value]')[0].options.length, 2,
"should have only 2 options without blank option");
// change value to update widget modifier values
form.$('.o_field_widget[name=feedback_value]').val('"bad"').trigger('change');
assert.strictEqual(form.$('.o_field_widget[name=color]')[0].options.length, 2,
"should have only 2 options");
form.destroy();
});
QUnit.module('FieldMany2ManyTags');
QUnit.test('fieldmany2many tags with and without color', function (assert) {
assert.expect(5);
this.data.partner.fields.partner_ids = {string: "Partner", type: "many2many", relation: 'partner'};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="partner_ids" widget="many2many_tags" options="{\'color_field\': \'color\'}"/>' +
'<field name="timmy" widget="many2many_tags"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method ==='read' && args.model === 'partner_type') {
assert.deepEqual(args.args , [[12], ['display_name']], "should not read any color field");
} else if (args.method ==='read' && args.model === 'partner') {
assert.deepEqual(args.args , [[1], ['display_name', 'color']], "should read color field");
}
return this._super.apply(this, arguments);
}
});
// add a tag on field partner_ids
var $input = form.$('.o_field_many2manytags[name="partner_ids"] input');
$input.click(); // opens the dropdown
$input.autocomplete('widget').find('li:first()').click(); // adds a tag
// add a tag on field timmy
$input = form.$('.o_field_many2manytags[name="timmy"] input');
$input.click(); // opens the dropdown
assert.strictEqual($input.autocomplete('widget').find('li').length, 3,
"autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')");
$input.autocomplete('widget').find('li:first()').click(); // adds a tag
assert.strictEqual(form.$('.o_field_many2manytags[name="timmy"] .badge').length, 1,
"should contain 1 tag");
assert.ok(form.$('.o_field_many2manytags[name="timmy"] .badge:contains("gold")').length,
"should contain newly added tag 'gold'");
form.destroy();
});
QUnit.test('fieldmany2many tags with color: rendering and edition', function (assert) {
assert.expect(28);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records.push({id: 13, display_name: "red", color: 8});
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="timmy" widget="many2many_tags" options="{\'color_field\': \'color\', \'no_create_edit\': True}"/>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
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");
assert.ok(_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), [12, 13]),
"new value should be [12, 13]");
}
if (args.method ==='read' && args.model === 'partner_type') {
assert.deepEqual(args.args[1], ['display_name', 'color'], "should read the color field");
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.$('.o_field_many2manytags .badge .dropdown-toggle').length, 2,
"should contain 2 tags");
assert.ok(form.$('.badge .dropdown-toggle:contains(gold)').length,
'should have fetched and rendered gold partner tag');
assert.ok(form.$('.badge .dropdown-toggle:contains(silver)').length,
'should have fetched and rendered silver partner tag');
assert.strictEqual(form.$('.badge:first()').data('color'), 2,
'should have correctly fetched the color');
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_field_many2manytags .badge .dropdown-toggle').length, 2,
"should still contain 2 tags in edit mode");
assert.ok(form.$('.o_tag_color_2 .o_badge_text:contains(gold)').length,
'first tag should still contain "gold" and be color 2 in edit mode');
assert.strictEqual(form.$('.o_field_many2manytags .o_delete').length, 2,
"tags should contain a delete button");
// add an other existing tag
var $input = form.$('.o_field_many2manytags input');
$input.click(); // opens the dropdown
assert.strictEqual($input.autocomplete('widget').find('li').length, 1,
"autocomplete dropdown should have 1 entry");
assert.strictEqual($input.autocomplete('widget').find('li a:contains("red")').length, 1,
"autocomplete dropdown should contain 'red'");
$input.autocomplete('widget').find('li').click(); // add 'red'
assert.strictEqual(form.$('.o_field_many2manytags .badge .dropdown-toggle').length, 3,
"should contain 3 tags");
assert.ok(form.$('.o_field_many2manytags .badge .dropdown-toggle:contains("red")').length,
"should contain newly added tag 'red'");
assert.ok(form.$('.o_field_many2manytags .badge[data-color=8] .dropdown-toggle:contains("red")').length,
"should have fetched the color of added tag");
// remove tag with id 14
form.$('.o_field_many2manytags .badge[data-id=14] .o_delete').click();
assert.strictEqual(form.$('.o_field_many2manytags .badge .dropdown-toggle').length, 2,
"should contain 2 tags");
assert.ok(!form.$('.o_field_many2manytags .badge .dropdown-toggle:contains("silver")').length,
"should not contain tag 'silver' anymore");
// save the record (should do the write RPC with the correct commands)
form.$buttons.find('.o_form_button_save').click();
// checkbox 'Hide in Kanban'
$input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // selects 'red' tag
$input.click(); // opens the colorpicker dropdown
var $checkBox = form.$('.o_field_many2manytags .badge[data-id=13] .custom-checkbox input');
assert.strictEqual($checkBox.length, 1, "should have a checkbox in the colorpicker dropdown menu");
assert.notOk($checkBox.is(':checked'), "should have unticked checkbox in colorpicker dropdown menu");
$checkBox.mousedown().click();
$input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // refresh
$input.click();
$checkBox = form.$('.o_field_many2manytags .badge[data-id=13] .custom-checkbox input'); // refresh
assert.equal($input.parent().data('color'), "0", "should become transparent when toggling on checkbox");
assert.ok($checkBox.is(':checked'), "should have a ticked checkbox in colorpicker dropdown menu after mousedown");
$checkBox.mousedown().click();
$input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // refresh
$input.click();
$checkBox = form.$('.o_field_many2manytags .badge[data-id=13] .custom-checkbox input'); // refresh
assert.equal($input.parent().data('color'), "8", "should revert to old color when toggling off checkbox");
assert.notOk($checkBox.is(':checked'), "should have an unticked checkbox in colorpicker dropdown menu after 2nd click");
// TODO: it would be nice to test the behaviors of the autocomplete dropdown
// (like refining the research, creating new tags...), but ui-autocomplete
// makes it difficult to test
form.destroy();
});
QUnit.test('fieldmany2many tags view a domain', function (assert) {
assert.expect(7);
this.data.partner.fields.timmy.domain = [['id', '<', 50]];
this.data.partner.records[0].timmy = [12];
this.data.partner_type.records.push({id: 99, display_name: "red", color: 8});
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="timmy" widget="many2many_tags" options="{\'no_create_edit\': True}"/>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'name_search') {
assert.deepEqual(args.kwargs.args, [['id', '<', 50], ['id', 'not in', [12]]],
"domain sent to name_search should be correct");
return $.when([[14, 'silver']]);
}
return this._super.apply(this, arguments);
}
});
assert.strictEqual(form.$('.o_field_many2manytags .badge').length, 1,
"should contain 1 tag");
assert.ok(form.$('.badge:contains(gold)').length,
'should have fetched and rendered gold partner tag');
form.$buttons.find('.o_form_button_edit').click();
// add an other existing tag
var $input = form.$('.o_field_many2manytags input');
$input.click(); // opens the dropdown
assert.strictEqual($input.autocomplete('widget').find('li').length, 1,
"autocomplete dropdown should have 1 entry");
assert.strictEqual($input.autocomplete('widget').find('li a:contains("silver")').length, 1,
"autocomplete dropdown should contain 'silver'");
$input.autocomplete('widget').find('li').click(); // add 'silver'
assert.strictEqual(form.$('.o_field_many2manytags .badge').length, 2,
"should contain 2 tags");
assert.ok(form.$('.o_field_many2manytags .badge:contains("silver")').length,
"should contain newly added tag 'silver'");
form.destroy();
});
QUnit.test('fieldmany2many tags in a new record', function (assert) {
assert.expect(7);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="timmy" widget="many2many_tags"/>' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/create') {
var commands = args.args[0].timmy;
assert.strictEqual(commands.length, 1, "should have generated one command");
assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH");
assert.ok(_.isEqual(commands[0][2], [12]), "new value should be [12]");
}
return this._super.apply(this, arguments);
}
});
assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), "form should be in edit mode");
var $input = form.$('.o_field_many2manytags input');
$input.click(); // opens the dropdown
assert.strictEqual($input.autocomplete('widget').find('li').length, 3,
"autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')");
$input.autocomplete('widget').find('li:first()').click(); // adds a tag
assert.strictEqual(form.$('.o_field_many2manytags .badge').length, 1,
"should contain 1 tag");
assert.ok(form.$('.o_field_many2manytags .badge:contains("gold")').length,
"should contain newly added tag 'gold'");
// save the record (should do the write RPC with the correct commands)
form.$buttons.find('.o_form_button_save').click();
form.destroy();
});
QUnit.test('fieldmany2many tags: update color', function (assert) {
assert.expect(5);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records[0].color = 0;
var color;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="timmy" widget="many2many_tags" options="{\'color_field\': \'color\'}"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.deepEqual(args.args[1], {color: color},
"shoud write the new color");
}
return this._super.apply(this, arguments);
},
res_id: 1,
});
// First checks that default color 0 is rendered as 0 color
assert.ok(form.$('.badge.dropdown:first()').is('.o_tag_color_0'),
'first tag color should be 0');
// Update the color in readonly
color = 1;
form.$('.badge:first() .dropdown-toggle').click();
$('.o_colorpicker a[data-color="' + color + '"]').trigger('mousedown'); // choose color 1
assert.strictEqual(form.$('.badge:first()').data('color'), color,
'should have correctly updated the color (in readonly)');
// Update the color in edit
color = 6;
form.$buttons.find('.o_form_button_edit').click();
form.$('.badge:first() .dropdown-toggle').click();
$('.o_colorpicker a[data-color="' + color + '"]').trigger('mousedown'); // choose color 6
assert.strictEqual(form.$('.badge:first()').data('color'), color,
'should have correctly updated the color (in edit)');
form.destroy();
});
QUnit.test('fieldmany2many tags in editable list', function (assert) {
assert.expect(7);
this.data.partner.records[0].timmy = [12];
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
context: {take: 'five'},
arch:'<tree editable="bottom">' +
'<field name="foo"/>' +
'<field name="timmy" widget="many2many_tags"/>' +
'</tree>',
mockRPC: function (route, args) {
if (args.method === 'read' && args.model === 'partner_type') {
assert.deepEqual(args.kwargs.context, {take: 'five'},
'The context should be passed to the RPC');
}
return this._super.apply(this, arguments);
}
});
assert.strictEqual(list.$('.o_data_row:first .o_field_many2manytags .badge').length, 1,
"m2m field should contain one tag");
// edit first row
list.$('.o_data_row:first td:nth(2)').click();
var $m2o = list.$('.o_data_row:first .o_field_many2manytags .o_field_many2one');
assert.strictEqual($m2o.length, 1, "a many2one widget should have been instantiated");
// add a tag
var $input = $m2o.find('input');
$input.click();
$input.autocomplete('widget').find('li:first()').click(); // adds a tag
assert.strictEqual(list.$('.o_data_row:first .o_field_many2manytags .badge').length, 2,
"m2m field should contain 2 tags");
// leave edition
list.$('.o_data_row:nth(1) td:nth(2)').click();
assert.strictEqual(list.$('.o_data_row:first .o_field_many2manytags .badge').length, 2,
"m2m field should contain 2 tags");
list.destroy();
});
QUnit.test('search more in many2one: group and use the pager', function (assert) {
assert.expect(2);
this.data.partner.records.push({
id: 5,
display_name: "Partner 4",
}, {
id: 6,
display_name: "Partner 5",
}, {
id: 7,
display_name: "Partner 6",
}, {
id: 8,
display_name: "Partner 7",
}, {
id: 9,
display_name: "Partner 8",
}, {
id: 10,
display_name: "Partner 9",
});
this.data.partner.fields.datetime.searchable = true;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="trululu"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
archs: {
'partner,false,list': '<tree limit="7"><field name="display_name"/></tree>',
'partner,false,search': '<search><group>' +
' <filter name="bar" string="Bar" context="{\'group_by\': \'bar\'}"/>' +
'</group></search>',
},
viewOptions: {
mode: 'edit',
},
});
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
form.$('.o_field_many2one input').click();
$dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); // Open Search More
$('.modal .o_search_options .fa-bars').click();
$('.modal .o_search_options .o_group_by_menu a:contains(Bar)').click(); // group by 'Bar'
$('.modal .o_group_header:first').click();
assert.strictEqual($('.modal tbody:nth(1) .o_data_row').length, 7,
"should display 7 records in the first page");
$('.modal .o_group_header:first .o_pager_next').click();
assert.strictEqual($('.modal tbody:nth(1) .o_data_row').length, 1,
"should display 1 record in the second page");
form.destroy();
});
QUnit.test('many2many_tags can load more than 40 records', function (assert) {
assert.expect(1);
this.data.partner.fields.partner_ids = {string: "Partner", type: "many2many", relation: 'partner'};
this.data.partner.records[0].partner_ids = [];
for (var i = 15; i < 115; i++) {
this.data.partner.records.push({id: i, display_name: 'walter' + i});
this.data.partner.records[0].partner_ids.push(i);
}
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="partner_ids" widget="many2many_tags"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget[name="partner_ids"] .badge').length, 100,
'should have rendered 100 tags');
form.destroy();
});
QUnit.test('field many2many_tags keeps focus when being edited', function (assert) {
assert.expect(7);
this.data.partner.records[0].timmy = [12];
this.data.partner.onchanges.foo = function (obj) {
obj.timmy = [[5]]; // DELETE command
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="foo"/>' +
'<field name="timmy" widget="many2many_tags"/>' +
'</form>',
res_id: 1,
});
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_field_many2manytags .badge').length, 1,
"should contain one tag");
// update foo, which will trigger an onchange and update timmy
// -> m2mtags input should not have taken the focus
form.$('input:first').focus();
form.$('input:first').val('trigger onchange').trigger('input');
assert.strictEqual(form.$('.o_field_many2manytags .badge').length, 0,
"should contain no tags");
assert.strictEqual(form.$('input:first').get(0), document.activeElement,
"foo input should have kept the focus");
// add a tag -> m2mtags input should still have the focus
form.$('.o_field_many2manytags input').click(); // opens the dropdown
form.$('.o_field_many2manytags input').autocomplete('widget').find('li:first').click();
assert.strictEqual(form.$('.o_field_many2manytags .badge').length, 1,
"should contain a tag");
assert.strictEqual(form.$('.o_field_many2manytags input').get(0), document.activeElement,
"m2m tags input should have kept the focus");
// remove a tag -> m2mtags input should still have the focus
form.$('.o_field_many2manytags .o_delete').click();
assert.strictEqual(form.$('.o_field_many2manytags .badge').length, 0,
"should contain no tags");
assert.strictEqual(form.$('.o_field_many2manytags input').get(0), document.activeElement,
"m2m tags input should have kept the focus");
form.destroy();
});
QUnit.test('widget many2many_tags: tags title attribute', function (assert) {
assert.expect(1);
this.data.turtle.records[0].partner_ids = [2];
var form = createView({
View: FormView,
model: 'turtle',
data: this.data,
arch:'<form string="Turtles">' +
'<sheet>' +
'<field name="display_name"/>' +
'<field name="partner_ids" widget="many2many_tags"/>' +
'</sheet>' +
'</form>',
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('widget many2many_tags: toggle colorpicker multiple times', function (assert) {
assert.expect(11);
this.data.partner.records[0].timmy = [12];
this.data.partner_type.records[0].color = 0;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="timmy" widget="many2many_tags" options="{\'color_field\': \'color\'}"/>' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual($('.o_field_many2manytags .badge').length, 1,
"should have one tag");
assert.strictEqual($('.o_field_many2manytags .badge').data('color'), 0,
"tag should have color 0");
assert.strictEqual($('.o_colorpicker:visible').length, 0,
"colorpicker should be closed");
// click on the badge to open colorpicker
form.$('.o_field_many2manytags .badge .dropdown-toggle').click();
assert.strictEqual($('.o_colorpicker:visible').length, 1,
"colorpicker should be open");
// click on the badge again to close colorpicker
form.$('.o_field_many2manytags .badge .dropdown-toggle').click();
assert.strictEqual($('.o_field_many2manytags .badge').data('color'), 0,
"tag should still have color 0");
assert.strictEqual($('.o_colorpicker:visible').length, 0,
"colorpicker should be closed");
// click on the badge to open colorpicker
form.$('.o_field_many2manytags .badge .dropdown-toggle').click();
assert.strictEqual($('.o_colorpicker:visible').length, 1,
"colorpicker should be open");
// click on the colorpicker, but not on a color
form.$('.o_colorpicker').click();
assert.strictEqual($('.o_field_many2manytags .badge').data('color'), 0,
"tag should still have color 0");
assert.strictEqual($('.o_colorpicker:visible').length, 0,
"colorpicker should be closed");
// click on the badge to open colorpicker
form.$('.o_field_many2manytags .badge .dropdown-toggle').click();
// click on a color in the colorpicker
form.$('.o_colorpicker .o_tag_color_2').trigger('mousedown');
assert.strictEqual($('.o_field_many2manytags .badge').data('color'), 2,
"tag should have color 2");
assert.strictEqual($('.o_colorpicker:visible').length, 0,
"colorpicker should be closed");
form.destroy();
});
QUnit.module('FieldRadio');
QUnit.test('fieldradio widget on a many2one in a new record', function (assert) {
assert.expect(6);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="product_id" widget="radio"/>' +
'</form>',
});
assert.ok(form.$('div.o_radio_item').length, "should have rendered outer div");
assert.strictEqual(form.$('input.o_radio_input').length, 2, "should have 2 possible choices");
assert.ok(form.$('label.o_form_label:contains(xphone)').length, "one of them should be xphone");
assert.strictEqual(form.$('input:checked').length, 0, "none of the input should be checked");
form.$("input.o_radio_input:first").click();
assert.strictEqual(form.$('input:checked').length, 1, "one of the input should be checked");
form.$buttons.find('.o_form_button_save').click();
var newRecord = _.last(this.data.partner.records);
assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value");
form.destroy();
});
QUnit.test('fieldradio change value by onchange', function (assert) {
assert.expect(4);
this.data.partner.onchanges = {bar: function (obj) {
obj.product_id = obj.bar ? 41 : 37;
obj.color = obj.bar ? 'red' : 'black';
}};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="bar"/>' +
'<field name="product_id" widget="radio"/>' +
'<field name="color" widget="radio"/>' +
'</form>',
});
form.$("input[type='checkbox']").click();
assert.strictEqual(form.$('input.o_radio_input[data-value="37"]:checked').length, 1, "one of the input should be checked");
assert.strictEqual(form.$('input.o_radio_input[data-value="black"]:checked').length, 1, "the other of the input should be checked");
form.$("input[type='checkbox']").click();
assert.strictEqual(form.$('input.o_radio_input[data-value="41"]:checked').length, 1, "the other of the input should be checked");
assert.strictEqual(form.$('input.o_radio_input[data-value="red"]:checked').length, 1, "one of the input should be checked");
form.destroy();
});
QUnit.test('fieldradio widget on a selection in a new record', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="color" widget="radio"/>' +
'</form>',
});
assert.ok(form.$('div.o_radio_item').length, "should have rendered outer div");
assert.strictEqual(form.$('input.o_radio_input').length, 2, "should have 2 possible choices");
assert.ok(form.$('label.o_form_label:contains(Red)').length, "one of them should be Red");
// click on 2nd option
form.$("input.o_radio_input").eq(1).click();
form.$buttons.find('.o_form_button_save').click();
var newRecord = _.last(this.data.partner.records);
assert.strictEqual(newRecord.color, 'black', "should have saved record with correct value");
form.destroy();
});
QUnit.test('fieldradio widget has o_horizontal or o_vertical class', function (assert) {
assert.expect(2);
this.data.partner.fields.color2 = this.data.partner.fields.color;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<group>' +
'<field name="color" widget="radio"/>' +
'<field name="color2" widget="radio" options="{\'horizontal\': True}"/>' +
'</group>' +
'</form>',
});
var btn1 = form.$('div.o_field_radio.o_vertical');
var btn2 = form.$('div.o_field_radio.o_horizontal');
assert.strictEqual(btn1.length, 1, "should have o_vertical class");
assert.strictEqual(btn2.length, 1, "should have o_horizontal class");
form.destroy();
});
QUnit.test('fieldradio widget with numerical keys encoded as strings', function (assert) {
assert.expect(5);
this.data.partner.fields.selection = {
type: 'selection',
selection: [['0', "Red"], ['1', "Black"]],
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="selection" widget="radio"/>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.strictEqual(args.args[1].selection, '1',
"should write correct value");
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.$('.o_field_widget').text(), '',
"field should be unset");
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_radio_input:checked').length, 0,
"no value should be checked");
form.$("input.o_radio_input:nth(1)").click(); // click on 2nd option
form.$buttons.find('.o_form_button_save').click();
assert.strictEqual(form.$('.o_field_widget').text(), 'Black',
"value should be 'Black'");
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_radio_input[data-index=1]:checked').length, 1,
"'Black' should be checked");
form.destroy();
});
QUnit.test('widget radio on a many2one: domain updated by an onchange', function (assert) {
assert.expect(4);
this.data.partner.onchanges = {
int_field: function () {},
};
var domain = [];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="int_field"/>' +
'<field name="trululu" widget="radio"/>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'onchange') {
domain = [['id', 'in', [10]]];
return $.when({
value: {
trululu: false,
},
domain: {
trululu: domain,
},
});
}
if (args.method === 'search_read') {
assert.deepEqual(args.kwargs.domain, domain,
"sent domain should be correct");
}
return this._super(route, args);
},
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual(form.$('.o_field_widget[name=trululu] .o_radio_item').length, 3,
"should be 3 radio buttons");
// trigger an onchange that will update the domain
form.$('.o_field_widget[name=int_field]').val(2).trigger('input');
assert.strictEqual(form.$('.o_field_widget[name=trululu] .o_radio_item').length, 0,
"should be no more radio button");
form.destroy();
});
QUnit.module('FieldSelectionBadge');
QUnit.test('FieldSelectionBadge widget on a many2one in a new record', function (assert) {
assert.expect(6);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="product_id" widget="selection_badge"/>' +
'</form>',
});
assert.ok(form.$('span.o_selection_badge').length, "should have rendered outer div");
assert.strictEqual(form.$('span.o_selection_badge').length, 2, "should have 2 possible choices");
assert.ok(form.$('span.o_selection_badge:contains(xphone)').length, "one of them should be xphone");
assert.strictEqual(form.$('span.active').length, 0, "none of the input should be checked");
$("span.o_selection_badge:first").click();
assert.strictEqual(form.$('span.active').length, 1, "one of the input should be checked");
form.$buttons.find('.o_form_button_save').click();
var newRecord = _.last(this.data.partner.records);
assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value");
form.destroy();
});
QUnit.test('FieldSelectionBadge widget on a selection in a new record', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="color" widget="selection_badge"/>' +
'</form>',
});
assert.ok(form.$('span.o_selection_badge').length, "should have rendered outer div");
assert.strictEqual(form.$('span.o_selection_badge').length, 2, "should have 2 possible choices");
assert.ok(form.$('span.o_selection_badge:contains(Red)').length, "one of them should be Red");
// click on 2nd option
form.$("span.o_selection_badge").eq(1).click();
form.$buttons.find('.o_form_button_save').click();
var newRecord = _.last(this.data.partner.records);
assert.strictEqual(newRecord.color, 'black', "should have saved record with correct value");
form.destroy();
});
QUnit.test('FieldSelectionBadge widget on a selection in a readonly mode', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="color" widget="selection_badge" readonly="1"/>' +
'</form>',
});
assert.strictEqual(form.$('span.o_readonly_modifier').length, 1, "should have 1 possible value in readonly mode");
form.destroy();
});
QUnit.module('FieldMany2ManyCheckBoxes');
QUnit.test('widget many2many_checkboxes', function (assert) {
assert.expect(10);
this.data.partner.records[0].timmy = [12];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<group><field name="timmy" widget="many2many_checkboxes"/></group>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('div.o_field_widget div.custom-checkbox').length, 2,
"should have fetched and displayed the 2 values of the many2many");
assert.ok(form.$('div.o_field_widget div.custom-checkbox input').eq(0).prop('checked'),
"first checkbox should be checked");
assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').eq(1).prop('checked'),
"second checkbox should not be checked");
assert.ok(form.$('div.o_field_widget div.custom-checkbox input').prop('disabled'),
"the checkboxes should be disabled");
form.$buttons.find('.o_form_button_edit').click();
assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').prop('disabled'),
"the checkboxes should not be disabled");
// add a m2m value by clicking on input
form.$('div.o_field_widget div.custom-checkbox input').eq(1).click();
form.$buttons.find('.o_form_button_save').click();
assert.deepEqual(this.data.partner.records[0].timmy, [12, 14],
"should have added the second element to the many2many");
assert.strictEqual(form.$('input:checked').length, 2,
"both checkboxes should be checked");
// remove a m2m value by clinking on label
form.$buttons.find('.o_form_button_edit').click();
form.$('div.o_field_widget div.custom-checkbox > label').eq(0).click();
form.$buttons.find('.o_form_button_save').click();
assert.deepEqual(this.data.partner.records[0].timmy, [14],
"should have removed the first element to the many2many");
assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').eq(0).prop('checked'),
"first checkbox should be checked");
assert.ok(form.$('div.o_field_widget div.custom-checkbox input').eq(1).prop('checked'),
"second checkbox should not be checked");
form.destroy();
});
QUnit.test('widget many2many_checkboxes: start non empty, then remove twice', function (assert) {
assert.expect(2);
this.data.partner.records[0].timmy = [12,14];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<group><field name="timmy" widget="many2many_checkboxes"/></group>' +
'</form>',
res_id: 1,
viewOptions: {mode: 'edit'},
});
form.$('div.o_field_widget div.custom-checkbox input').eq(0).click();
form.$('div.o_field_widget div.custom-checkbox input').eq(1).click();
form.$buttons.find('.o_form_button_save').click();
assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').eq(0).prop('checked'),
"first checkbox should not be checked");
assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').eq(1).prop('checked'),
"second checkbox should not be checked");
form.destroy();
});
QUnit.module('FieldMany2ManyBinaryMultiFiles');
QUnit.test('widget many2many_binary', function (assert) {
assert.expect(14);
this.data['ir.attachment'] = {
fields: {
name: {string:"Name", type: "char"},
mimetype: {string: "Mimetype", type: "char"},
},
records: [{
id: 17,
name: 'Marley&Me.jpg',
mimetype: 'jpg',
}],
};
this.data.turtle.fields.picture_ids = {
string: "Pictures",
type: "many2many",
relation: 'ir.attachment',
};
this.data.turtle.records[0].picture_ids = [17];
var form = createView({
View: FormView,
model: 'turtle',
data: this.data,
arch:'<form string="Turtles">' +
'<group><field name="picture_ids" widget="many2many_binary"/></group>' +
'</form>',
archs: {
'ir.attachment,false,list': '<tree string="Pictures"><field name="name"/></tree>',
},
res_id: 1,
mockRPC: function (route, args) {
assert.step(route);
if (route === '/web/dataset/call_kw/ir.attachment/read') {
assert.deepEqual(args.args[1], ['name', 'datas_fname', 'mimetype']);
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload').length, 1,
"there should be the attachment widget");
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .oe_attachments').children().length, 1,
"there should be no attachment");
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .o_attach').length, 0,
"there should not be an Add button (readonly)");
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .oe_attachment .oe_delete').length, 0,
"there should not be a Delete button (readonly)");
// to edit mode
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .o_attach').length, 1,
"there should be an Add button");
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .o_attach').text().trim(), "Pictures",
"the button should be correctly named");
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .o_hidden_input_file form').length, 1,
"there should be a hidden form to upload attachments");
// TODO: add an attachment
// no idea how to test this
// delete the attachment
form.$('div.o_field_widget.oe_fileupload .oe_attachment .oe_delete').click();
assert.verifySteps([
'/web/dataset/call_kw/turtle/read',
'/web/dataset/call_kw/ir.attachment/read',
]);
form.$buttons.find('.o_form_button_save').click();
assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .oe_attachments').children().length, 0,
"there should be no attachment");
form.destroy();
});
QUnit.test('name_create in form dialog', function (assert) {
var done = assert.async();
assert.expect(2);
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<group>' +
'<field name="p">' +
'<tree>' +
'<field name="bar"/>' +
'</tree>' +
'<form>' +
'<field name="product_id"/>' +
'</form>' +
'</field>' +
'</group>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'name_create') {
assert.step('name_create');
}
return this._super.apply(this, arguments);
},
});
form.$buttons.find('.o_form_button_edit').click();
form.$('.o_field_x2many_list_row_add a').click();
var $dropdown = $('.modal .o_field_many2one input').autocomplete('widget');
$('.modal input').val('new record').trigger('keydown');
concurrency.delay(0).then(function () {
$dropdown.find('li:first()').click(); // quick create 'new record'
assert.verifySteps(['name_create']);
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
form.destroy();
done();
});
});
QUnit.module('FieldReference');
QUnit.test('Reference field in modal readonly mode', function (assert) {
assert.expect(4);
this.data.partner.records[0].p = [2];
this.data.partner.records[1].trululu = 1;
this.data.partner.records[1].reference = 'product,41';
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="reference"/>' +
'<field name="p"/>' +
'</form>',
archs: {
'partner,false,form': '<form><field name="reference"/></form>',
'partner,false,list': '<tree><field name="display_name"/></tree>',
},
res_id: 1,
});
// Cuurent Form
assert.equal(form.$('.o_form_uri.o_field_widget[name=reference]').text(), 'xphone',
'the field reference of the form should have the right value');
var $cell_o2m = form.$('.o_data_cell');
assert.equal($cell_o2m.text(), 'second record',
'the list should have one record');
$cell_o2m.click();
// In modal
var $modal = $('.modal-lg');
assert.equal($modal.length, 1,
'there should be one modal opened');
assert.equal($modal.find('.o_form_uri.o_field_widget[name=reference]').text(), 'xpad',
'The field reference in the modal should have the right value');
$modal.find('.o_form_button_cancel').click();
form.destroy();
});
QUnit.test('Reference field in modal write mode', function (assert) {
assert.expect(5);
this.data.partner.records[0].p = [2];
this.data.partner.records[1].trululu = 1;
this.data.partner.records[1].reference = 'product,41';
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="reference"/>' +
'<field name="p"/>' +
'</form>',
archs: {
'partner,false,form': '<form><field name="reference"/></form>',
'partner,false,list': '<tree><field name="display_name"/></tree>',
},
res_id: 1,
});
// current form
form.$buttons.find('.o_form_button_edit').click();
var $fieldRef = form.$('.o_field_widget.o_field_many2one[name=reference]');
assert.equal($fieldRef.find('option:selected').text(), 'Product',
'The reference field\'s model should be Product');
assert.equal($fieldRef.find('.o_input.ui-autocomplete-input').val(), 'xphone',
'The reference field\'s record should be xphone');
form.$('.o_data_cell').click();
// In modal
var $modal = $('.modal-lg');
assert.equal($modal.length, 1,
'there should be one modal opened');
var $fieldRefModal = $modal.find('.o_field_widget.o_field_many2one[name=reference]');
assert.equal($fieldRefModal.find('option:selected').text(), 'Product',
'The reference field\'s model should be Product');
assert.equal($fieldRefModal.find('.o_input.ui-autocomplete-input').val(), 'xpad',
'The reference field\'s record should be xpad');
form.destroy();
});
QUnit.test('reference in form view', function (assert) {
assert.expect(15);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="reference" string="custom label"/>' +
'</group>' +
'</sheet>' +
'</form>',
archs: {
'product,false,form': '<form string="Product"><field name="display_name"/></form>',
},
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'get_formview_action') {
assert.deepEqual(args.args[0], [37], "should call get_formview_action with correct id");
return $.when({
res_id: 17,
type: 'ir.actions.act_window',
target: 'current',
res_model: 'res.partner'
});
}
if (args.method === 'get_formview_id') {
assert.deepEqual(args.args[0], [37], "should call get_formview_id with correct id");
return $.when(false);
}
if (args.method === 'name_search') {
assert.strictEqual(args.model, 'partner_type',
"the name_search should be done on the newly set model");
}
if (args.method === 'write') {
assert.strictEqual(args.model, 'partner',
"should write on the current model");
assert.deepEqual(args.args, [[1], {reference: 'partner_type,12'}],
"should write the correct value");
}
return this._super(route, args);
},
});
testUtils.intercept(form, 'do_action', function (event) {
assert.strictEqual(event.data.action.res_id, 17,
"should do a do_action with correct parameters");
});
assert.strictEqual(form.$('a.o_form_uri:contains(xphone)').length, 1,
"should contain a link");
form.$('a.o_form_uri').click(); // click on the link in readonly mode (should trigger do_action)
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_field_widget').length, 2,
"should contain two field widgets (selection and many2one)");
assert.strictEqual(form.$('.o_field_many2one').length, 1,
"should contain one many2one");
assert.strictEqual(form.$('.o_field_widget select').val(), "product",
"widget should contain one select with the model");
assert.strictEqual(form.$('.o_field_widget input').val(), "xphone",
"widget should contain one input with the record");
var options = _.map(form.$('.o_field_widget select > option'), function (el) {
return $(el).val();
});
assert.deepEqual(options, ['', 'product', 'partner_type', 'partner'],
"the options should be correctly set");
form.$('.o_external_button').click(); // click on the external button (should do an RPC)
assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: custom label',
"dialog title should display the custom string label");
$('.modal .o_form_button_cancel').click();
form.$('.o_field_widget select').val('partner_type').trigger('change');
assert.strictEqual(form.$('.o_field_widget input').val(), "",
"many2one value should be reset after model change");
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
// change the value of the m2o with a suggestion of the dropdown
form.$('.o_field_many2one input').click(); // will trigger a name_search on partner_type model
$dropdown.find('li:first()').click();
form.$buttons.find('.o_form_button_save').click();
assert.strictEqual(form.$('a.o_form_uri:contains(gold)').length, 1,
"should contain a link with the new value");
form.destroy();
});
QUnit.test('default_get and onchange with a reference field', function (assert) {
assert.expect(8);
this.data.partner.fields.reference.default = 'product,37';
this.data.partner.onchanges = {
int_field: function (obj) {
if (obj.int_field !== 0) {
obj.reference = 'partner_type,' + obj.int_field;
}
},
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="int_field"/>' +
'<field name="reference"/>' +
'</group>' +
'</sheet>' +
'</form>',
viewOptions: {
mode: 'edit',
},
mockRPC: function (route, args) {
if (args.method === 'name_get') {
assert.step(args.model);
}
return this._super(route, args);
},
});
assert.verifySteps(['product'], "the first name_get should have been done");
assert.strictEqual(form.$('.o_field_widget[name="reference"] select').val(), "product",
"reference field model should be correctly set");
assert.strictEqual(form.$('.o_field_widget[name="reference"] input').val(), "xphone",
"reference field value should be correctly set");
// trigger onchange
form.$('.o_field_widget[name=int_field]').val(12).trigger('input');
assert.verifySteps(['product', 'partner_type'], "the second name_get should have been done");
assert.strictEqual(form.$('.o_field_widget[name="reference"] select').val(), "partner_type",
"reference field model should be correctly set");
assert.strictEqual(form.$('.o_field_widget[name="reference"] input').val(), "gold",
"reference field value should be correctly set");
form.destroy();
});
QUnit.test('default_get a reference field in a x2m', function (assert) {
assert.expect(1);
this.data.partner.fields.turtles.default = [
[0, false, {turtle_ref: 'product,37'}]
];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="turtles">' +
'<tree>' +
'<field name="turtle_ref"/>' +
'</tree>' +
'</field>' +
'</sheet>' +
'</form>',
viewOptions: {
mode: 'edit',
},
archs: {
'turtle,false,form': '<form><field name="display_name"/><field name="turtle_ref"/></form>',
},
});
assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o_data_row:first').text(), "xphone",
"the default value should be correctly handled");
form.destroy();
});
QUnit.test('widget reference on char field, reset by onchange', function (assert) {
assert.expect(4);
this.data.partner.records[0].foo = 'product,37';
this.data.partner.onchanges = {
int_field: function (obj) {
obj.foo = 'product,' + obj.int_field;
},
};
var nbNameGet = 0;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="int_field"/>' +
'<field name="foo" widget="reference" readonly="1"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
mockRPC: function (route, args) {
if (args.model === 'product' && args.method === 'name_get') {
nbNameGet++;
}
return this._super(route, args);
},
});
assert.strictEqual(nbNameGet, 1,
"the first name_get should have been done");
assert.strictEqual(form.$('a[name="foo"]').text(), "xphone",
"foo field should be correctly set");
// trigger onchange
form.$('.o_field_widget[name=int_field]').val(41).trigger('input');
assert.strictEqual(nbNameGet, 2,
"the second name_get should have been done");
assert.strictEqual(form.$('a[name="foo"]').text(), "xpad",
"foo field should have been updated");
form.destroy();
});
QUnit.test('reference and list navigation', function (assert) {
assert.expect(2);
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree editable="bottom"><field name="reference"/></tree>',
});
// edit first row
list.$('.o_data_row .o_data_cell').first().click();
assert.strictEqual(list.$('.o_data_row:eq(0) .o_field_widget[name="reference"] input')[0], document.activeElement,
'input of first data row should be selected');
// press TAB to go to next line
list.$('.o_data_row:eq(0) input').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
assert.strictEqual(list.$('.o_data_row:eq(1) .o_field_widget[name="reference"] select')[0], document.activeElement,
'select of second data row should be selected');
list.destroy();
});
QUnit.test('one2many with extra field from server not in form', function (assert) {
assert.expect(6);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="p" >' +
'<tree>' +
'<field name="datetime"/>' +
'<field name="display_name"/>' +
'</tree>' +
'</field>' +
'</form>',
res_id: 1,
archs: {
'partner,false,form': '<form>' +
'<field name="display_name"/>' +
'</form>'},
mockRPC: function(route, args) {
if (route === '/web/dataset/call_kw/partner/write') {
args.args[1].p[0][2].datetime = '2018-04-05 12:00:00';
}
return this._super.apply(this, arguments);
}
});
form.$buttons.find('.o_form_button_edit').click();
var x2mList = form.$('.o_field_x2many_list[name=p]');
// Add a record in the list
x2mList.find('.o_field_x2many_list_row_add a').click();
var modal = $('.modal-lg');
var nameInput = modal.find('input.o_input[name=display_name]');
nameInput.val('michelangelo').trigger('input');
// Save the record in the modal (though it is still virtual)
modal.find('.btn-primary').first().click();
assert.equal(x2mList.find('.o_data_row').length, 1,
'There should be 1 records in the x2m list');
var newlyAdded = x2mList.find('.o_data_row').eq(0);
assert.equal(newlyAdded.find('.o_data_cell').first().text(), '',
'The create_date field should be empty');
assert.equal(newlyAdded.find('.o_data_cell').eq(1).text(), 'michelangelo',
'The display name field should have the right value');
// Save the whole thing
form.$buttons.find('.o_form_button_save').click();
x2mList = form.$('.o_field_x2many_list[name=p]');
// Redo asserts in RO mode after saving
assert.equal(x2mList.find('.o_data_row').length, 1,
'There should be 1 records in the x2m list');
newlyAdded = x2mList.find('.o_data_row').eq(0);
assert.equal(newlyAdded.find('.o_data_cell').first().text(), '04/05/2018 12:00:00',
'The create_date field should have the right value');
assert.equal(newlyAdded.find('.o_data_cell').eq(1).text(), 'michelangelo',
'The display name field should have the right value');
form.destroy();
});
QUnit.test('one2many invisible depends on parent field', function (assert) {
assert.expect(4);
this.data.partner.records[0].p = [2];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="product_id"/>' +
'</group>' +
'<notebook>' +
'<page string="Partner page">' +
'<field name="bar"/>' +
'<field name="p">' +
'<tree>' +
'<field name="foo" attrs="{\'column_invisible\': [(\'parent.product_id\', \'!=\', False)]}"/>' +
'<field name="bar" attrs="{\'column_invisible\': [(\'parent.bar\', \'=\', False)]}"/>' +
'</tree>' +
'</field>' +
'</page>' +
'</notebook>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('th').length, 2,
"should be 2 columns in the one2many");
form.$buttons.find('.o_form_button_edit').click();
form.$('.o_field_many2one[name="product_id"] input').click();
$('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click();
assert.strictEqual(form.$('th').length, 1,
"should be 1 column when the product_id is set");
form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup');
assert.strictEqual(form.$('th').length, 2,
"should be 2 columns in the one2many when product_id is not set");
form.$('.o_field_boolean[name="bar"] input').click();
assert.strictEqual(form.$('th').length, 1,
"should be 1 column after the value change");
form.destroy();
});
QUnit.test('one2many column visiblity depends on onchange of parent field', function (assert) {
assert.expect(3);
this.data.partner.records[0].p = [2];
this.data.partner.records[0].bar = false;
this.data.partner.onchanges.p = function (obj) {
// set bar to true when line is added
if (obj.p.length > 1 && obj.p[1][2].foo === 'New line') {
obj.bar = true;
}
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form>' +
'<field name="bar"/>' +
'<field name="p">' +
'<tree editable="bottom">' +
'<field name="foo"/>' +
'<field name="int_field" attrs="{\'column_invisible\': [(\'parent.bar\', \'=\', False)]}"/>' +
'</tree>' +
'</field>' +
'</form>',
res_id: 1,
});
// bar is false so there should be 1 column
assert.strictEqual(form.$('th').length, 1,
"should be only 1 column ('foo') in the one2many");
assert.strictEqual(form.$('.o_list_view .o_data_row').length, 1, "should contain one row");
form.$buttons.find('.o_form_button_edit').click();
// add a new o2m record
form.$('.o_field_x2many_list_row_add a').click();
form.$('.o_field_one2many input:first').focus();
form.$('.o_field_one2many input:first').val('New line').trigger('input');
form.$el.click(); // click outside to save a line
assert.strictEqual(form.$('th').length, 2, "should be 2 columns('foo' + 'int_field')");
form.destroy();
});
QUnit.test('one2many column_invisible on view not inline', function (assert) {
assert.expect(4);
this.data.partner.records[0].p = [2];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="product_id"/>' +
'</group>' +
'<notebook>' +
'<page string="Partner page">' +
'<field name="bar"/>' +
'<field name="p"/>' +
'</page>' +
'</notebook>' +
'</sheet>' +
'</form>',
res_id: 1,
archs: {
'partner,false,list': '<tree>' +
'<field name="foo" attrs="{\'column_invisible\': [(\'parent.product_id\', \'!=\', False)]}"/>' +
'<field name="bar" attrs="{\'column_invisible\': [(\'parent.bar\', \'=\', False)]}"/>' +
'</tree>',
},
});
assert.strictEqual(form.$('th').length, 2,
"should be 2 columns in the one2many");
form.$buttons.find('.o_form_button_edit').click();
form.$('.o_field_many2one[name="product_id"] input').click();
$('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click();
assert.strictEqual(form.$('th').length, 1,
"should be 1 column when the product_id is set");
form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup');
assert.strictEqual(form.$('th').length, 2,
"should be 2 columns in the one2many when product_id is not set");
form.$('.o_field_boolean[name="bar"] input').click();
assert.strictEqual(form.$('th').length, 1,
"should be 1 column after the value change");
form.destroy();
});
QUnit.module('TabNavigation');
QUnit.test('when Navigating to a many2one with tabs, it receives the focus and adds a new line', function (assert) {
assert.expect(3);
var form = createView({
View: FormView,
model: 'partner',
viewOptions: {
mode: 'edit',
},
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="qux"/>' +
'</group>' +
'<notebook>' +
'<page string="Partner page">' +
'<field name="turtles">' +
'<tree editable="bottom">' +
'<field name="turtle_foo"/>' +
'</tree>' +
'</field>' +
'</page>' +
'</notebook>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$el.find('input[name="qux"]')[0],
document.activeElement,
"initially, the focus should be on the 'qux' field because it is the first input");
form.$el.find('input[name="qux"]').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
assert.strictEqual(assert.strictEqual(form.$el.find('input[name="turtle_foo"]')[0],
document.activeElement,
"after tab, the focus should be on the many2one on the first input of the newly added line"));
form.destroy();
});
QUnit.test('when Navigating to a many to one with tabs, it places the focus on the first visible field', function (assert) {
assert.expect(3);
var form = createView({
View: FormView,
model: 'partner',
viewOptions: {
mode: 'edit',
},
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="qux"/>' +
'</group>' +
'<notebook>' +
'<page string="Partner page">' +
'<field name="turtles">' +
'<tree editable="bottom">' +
'<field name="turtle_bar" invisible="1"/>'+
'<field name="turtle_foo"/>' +
'</tree>' +
'</field>' +
'</page>' +
'</notebook>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$el.find('input[name="qux"]')[0],
document.activeElement,
"initially, the focus should be on the 'qux' field because it is the first input");
form.$el.find('input[name="qux"]').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
document.activeElement.click();
assert.strictEqual(assert.strictEqual(form.$el.find('input[name="turtle_foo"]')[0],
document.activeElement,
"after tab, the focus should be on the many2one"));
form.destroy();
});
QUnit.test('when Navigating to a many2one with tabs, not filling any field and hitting tab,' +
' we should not add a first line but navigate to the next control', function (assert) {
assert.expect(3);
this.data.partner.records[0].turtles = [];
var form = createView({
View: FormView,
model: 'partner',
viewOptions: {
mode: 'edit',
},
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="qux"/>' +
'</group>' +
'<notebook>' +
'<page string="Partner page">' +
'<field name="turtles">' +
'<tree editable="bottom">' +
'<field name="turtle_foo"/>' +
'<field name="turtle_description"/>' +
'</tree>' +
'</field>' +
'</page>' +
'</notebook>' +
'<group>' +
'<field name="foo"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$el.find('input[name="qux"]')[0],
document.activeElement,
"initially, the focus should be on the 'qux' field because it is the first input");
form.$el.find('input[name="qux"]').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
// skips the first field of the one2many
$(document.activeElement).trigger($.Event('keydown', {which: $.ui.keyCode.TAB}));
// skips the second (and last) field of the one2many
$(document.activeElement).trigger($.Event('keydown', {which: $.ui.keyCode.TAB}));
assert.strictEqual(assert.strictEqual(form.$el.find('input[name="foo"]')[0],
document.activeElement,
"after tab, the focus should be on the many2one"));
form.destroy();
});
QUnit.test('when Navigating to a many to one with tabs, editing in a popup, the popup should receive the focus then give it back', function (assert) {
assert.expect(3);
this.data.partner.records[0].turtles = [];
var form = createView({
View: FormView,
model: 'partner',
viewOptions: {
mode: 'edit',
},
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="qux"/>' +
'</group>' +
'<notebook>' +
'<page string="Partner page">' +
'<field name="turtles">' +
'<tree>' +
'<field name="turtle_foo"/>' +
'<field name="turtle_description"/>' +
'</tree>' +
'</field>' +
'</page>' +
'</notebook>' +
'<group>' +
'<field name="foo"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
archs: {
'turtle,false,form': '<form><group><field name="turtle_foo"/><field name="turtle_int"/></group></form>',
},
});
assert.strictEqual(form.$el.find('input[name="qux"]')[0],
document.activeElement,
"initially, the focus should be on the 'qux' field because it is the first input");
form.$el.find('input[name="qux"]').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
assert.strictEqual($.find('input[name="turtle_foo"]')[0],
document.activeElement,
"when the one2many received the focus, the popup should open because it automatically adds a new line");
$('input[name="turtle_foo"]').trigger($.Event('keydown', {
which: $.ui.keyCode.ESCAPE,
keyCode: $.ui.keyCode.ESCAPE,
}));
assert.strictEqual(form.$el.find('.o_field_x2many_list_row_add a')[0],
document.activeElement,
"after escape, the focus should be back on the add new line link");
form.destroy();
});
QUnit.test('when creating a new many2one on a x2many then discarding it immediately with ESCAPE, it should not crash', function (assert) {
var done = assert.async();
assert.expect(1);
this.data.partner.records[0].turtles = [];
var form = createView({
View: FormView,
model: 'partner',
viewOptions: {
mode: 'edit',
},
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<field name="turtles">' +
'<tree editable="top">' +
'<field name="turtle_foo"/>' +
'<field name="turtle_trululu"/>' +
'</tree>' +
'</field>' +
'</sheet>' +
'</form>',
res_id: 1,
archs: {
'partner,false,form': '<form><group><field name="foo"/><field name="bar"/></group></form>'
},
});
// add a new line
form.$el.find('.o_field_x2many_list_row_add>a').click();
// open the field turtle_trululu (one2many)
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
form.$el.find('.o_input_dropdown>input').click();
concurrency.delay(0).then(function () {
// click create and edit
$('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter').click();
// hit escape immediately
var escapeKey = $.ui.keyCode.ESCAPE;
$(document.activeElement).trigger(
$.Event('keydown', {which: escapeKey, keyCode: escapeKey}));
assert.ok('did not crash');
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
form.destroy();
done();
});
});
QUnit.test('navigating through an editable list with custom controls [REQUIRE FOCUS]', function (assert) {
assert.expect(5);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form>' +
'<field name="display_name"/>' +
'<field name="p">' +
'<tree editable="bottom">' +
'<control>' +
'<create string="Custom 1" context="{\'default_foo\': \'1\'}"/>' +
'<create string="Custom 2" context="{\'default_foo\': \'2\'}"/>' +
'</control>' +
'<field name="foo"/>' +
'</tree>' +
'</field>' +
'<field name="int_field"/>' +
'</form>',
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual(document.activeElement, form.$('.o_field_widget[name="display_name"]')[0],
"first input should be focused by default");
// press tab to navigate to the list
form.$('.o_field_widget[name="display_name"]').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
// press ESC to cancel 1st control click (create)
form.$('.o_data_cell input').trigger($.Event('keydown', {
which: $.ui.keyCode.ESCAPE,
keyCode: $.ui.keyCode.ESCAPE,
}));
assert.strictEqual(document.activeElement, form.$('.o_field_x2many_list_row_add a:first')[0],
"first editable list control should now have the focus");
// press right to focus the second control
form.$('.o_field_x2many_list_row_add a:first').trigger($.Event('keydown', {
which: $.ui.keyCode.RIGHT,
keyCode: $.ui.keyCode.RIGHT,
}));
assert.strictEqual(document.activeElement, form.$('.o_field_x2many_list_row_add a:nth(1)')[0],
"second editable list control should now have the focus");
// press left to come back to first control
form.$('.o_field_x2many_list_row_add a:nth(1)').trigger($.Event('keydown', {
which: $.ui.keyCode.LEFT,
keyCode: $.ui.keyCode.LEFT,
}));
assert.strictEqual(document.activeElement, form.$('.o_field_x2many_list_row_add a:first')[0],
"first editable list control should now have the focus");
// press tab to leave the list
form.$('.o_field_x2many_list_row_add a:first').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
assert.strictEqual(document.activeElement, form.$('.o_field_widget[name="int_field"]')[0],
"last input should now be focused");
form.destroy();
});
});
});
});