Files
odoo_source/addons/web/static/tests/fields/relational_fields_tests.js
T
Géry Debongnie abf32b8b21 [REF] *: update js test suite to use helpers
This rev. introduces robust helpers to use in the JS tests suite to
interact with DOM and components, and starts using them (almost)
everywhere.

All the helpers are exposed though testUtils.js.

There are 2 kinds of helpers:

1. Assertions
-------------
 * assert.containsNone, containsN, containsOnce check that the DOM
   (or a specific part of the DOM) contains a `selector`. It
   generates a correct error message automatically.
    ex: assert.strictEqual(form.$('.o_form_editable'), 1, "msg");
        -> assert.containsOnce(form, '.o_form_editable');
 * assert.isVisible, isNotVisible check that the DOM has an element
   visible or not. They also check that the element is actually in
   the DOM (before most tests didn't verify this).
 * assert.hasClass, doesNotHaveClass, hasAttrVAlue, check specific
   properties of a DOM element, and also validate that it is
   applied on a single existing DOM element (before most tests
   didn't verify this).
    ex: assert.notOk(form.$('button').hasClass('btn-primary'));
       -> assert.doesNotHaveClass(form.$('button'), 'btn-primary');

2. Utilities
------------
The goal of the utilities is to centralize the definition of many
standard components and interactions, ensuring that when we
refactor the JS framework, we do not need to change all the tests.

Existing mock utilities (addMockEnvironment, intercept, path,
patchDate, unpatch and fieldsViewGet) are moved to
'testUtils.mock.*'.
Existing DOM utilities are moved to 'testUtils.dom.*'.
New dom utilities are created for opendDatePicker, click,
clickFirst and clickLast. Helper `click` verifies that there is
exactly 1 element visible in the DOM you click on, `clickFirst`
and `clickLast` verify that there are more than one element on the
DOM.
 ex: form.$('button').click();
     -> testUtils.dom.click(form.$('button'));

New Form utilities: (testUtils.form.*)
 clickEdit, clickSave, clickCreate, clickDiscard, all clicks on
 the control panel buttons of the form.
 `reload` reloads the form data.

New modal, graph, kanban and pivot utils (testUtils.pivot.*,
testUtils.kanban.*, etc.).

New fields utils: (testUtils.fields.*)
 * editInput, editSelect: allow to change the value of a field,
   using a selector to identify it. They validate that the input
   exists and trigger the change event automatically.
 * editAndTrigger: allow to modify a field and trigger specific
   events after the value change
 * many2one (testUtils.fields.many2one.*)
   clickOpenDropdown, clickHighlightedItem, clickItem,
   searchAndClickItem: use a field name instead of a selector and
   do all the complex mechanism to open, filter and highlight
   many2one fields.

Joint work with aab, dam, ged, mge, svs and vsc.
2018-11-19 11:24:28 +00:00

2582 lines
102 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.containsN(form, '.o_statusbar_status button:not(.dropdown-toggle)', 2);
assert.containsN(form, '.o_statusbar_status button:disabled', 2);
assert.hasClass(form.$('.o_statusbar_status button[data-value="4"]'), '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(5);
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}},
});
assert.hasClass(form.$('.o_statusbar_status button[data-value="4"]'), 'btn-primary');
assert.hasClass(form.$('.o_statusbar_status button[data-value="4"]'), 'disabled');
assert.containsN(form, '.o_statusbar_status button.btn-secondary:not(.dropdown-toggle):not(:disabled)', 2);
var $clickable = form.$('.o_statusbar_status button.btn-secondary:not(.dropdown-toggle):not(:disabled)');
testUtils.dom.click($clickable.last()); // (last is visually the first here (css))
assert.hasClass(form.$('.o_statusbar_status button[data-value="1"]'), "btn-primary");
assert.hasClass(form.$('.o_statusbar_status button[data-value="1"]'), "disabled");
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.hasClass(form.$('.o_statusbar_status'), '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.containsN(form, '.o_statusbar_status button:disabled', 2);
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}},
});
testUtils.form.clickEdit(form);
assert.containsN(form, '.o_statusbar_status button:not(.dropdown-toggle)', 3);
testUtils.dom.click(form.$('.o_statusbar_status button:not(.dropdown-toggle)').last());
assert.containsN(form, '.o_statusbar_status button:not(.dropdown-toggle)', 2);
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}},
});
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_statusbar_status:first .dropdown-menu button.disabled');
assert.containsOnce(form, '.o_statusbar_status:last button.disabled');
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}},
});
testUtils.form.clickEdit(form);
assert.containsN(form, '.o_statusbar_status button.disabled', 3);
assert.strictEqual(rpcCount, 1, "should have done 1 search_read rpc");
testUtils.fields.editInput(form.$('input[name=qux]'), 9.5);
assert.containsN(form, '.o_statusbar_status button.disabled', 2);
assert.strictEqual(rpcCount, 2, "should have done 1 more search_read rpc");
testUtils.fields.editInput(form.$('input[name=qux]'), "hey");
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");
testUtils.dom.click(list.$('td:contains(Red):first'));
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");
testUtils.form.clickEdit(form);
assert.containsN(form, 'select', 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");
testUtils.fields.editSelect(form.$('select[name="product_id"]'), 41);
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.doesNotHaveClass(form.$('.o_field_widget'), '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.hasClass(form.$('.o_field_widget'),'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',
},
});
testUtils.fields.editSelect(form.$('select'), "false");
testUtils.form.clickSave(form);
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.containsN(form, '.o_field_widget[name=trululu] option', 4,
"should be 4 options in the selection");
// trigger an onchange that will update the domain
testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2);
assert.containsOnce(form, '.o_field_widget[name=trululu] option',
"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
});
testUtils.form.clickEdit(form);
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
testUtils.fields.editSelect(form.$('.o_field_widget[name=feedback_value]'), '"bad"');
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
testUtils.fields.many2one.clickOpenDropdown('partner_ids');
testUtils.fields.many2one.clickHighlightedItem('partner_ids');
// add a tag on field timmy
testUtils.fields.many2one.clickOpenDropdown('timmy');
var $input = form.$('.o_field_many2manytags[name="timmy"] input');
assert.strictEqual($input.autocomplete('widget').find('li').length, 3,
"autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')");
testUtils.fields.many2one.clickHighlightedItem('timmy');
assert.containsOnce(form, '.o_field_many2manytags[name="timmy"] .badge',
"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.containsN(form, '.o_field_many2manytags .badge .dropdown-toggle', 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');
testUtils.form.clickEdit(form);
assert.containsN(form, '.o_field_many2manytags .badge .dropdown-toggle', 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.containsN(form, '.o_field_many2manytags .o_delete', 2,
"tags should contain a delete button");
// add an other existing tag
var $input = form.$('.o_field_many2manytags input');
testUtils.fields.many2one.clickOpenDropdown('timmy');
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'");
testUtils.fields.many2one.clickHighlightedItem('timmy');
assert.containsN(form, '.o_field_many2manytags .badge .dropdown-toggle', 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
testUtils.dom.click(form.$('.o_field_many2manytags .badge[data-id=14] .o_delete'));
assert.containsN(form, '.o_field_many2manytags .badge .dropdown-toggle', 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)
testUtils.form.clickSave(form);
// checkbox 'Hide in Kanban'
$input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // selects 'red' tag
testUtils.dom.click($input);
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");
testUtils.fields.editAndTrigger($checkBox, null,['mouseenter','mousedown']);
$input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // refresh
testUtils.dom.click($input);
$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");
testUtils.fields.editAndTrigger($checkBox, null,['mouseenter','mousedown']);
$input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // refresh
testUtils.dom.click($input);
$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.containsOnce(form, '.o_field_many2manytags .badge',
"should contain 1 tag");
assert.ok(form.$('.badge:contains(gold)').length,
'should have fetched and rendered gold partner tag');
testUtils.form.clickEdit(form);
// add an other existing tag
var $input = form.$('.o_field_many2manytags input');
testUtils.fields.many2one.clickOpenDropdown('timmy');
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'");
testUtils.fields.many2one.clickHighlightedItem('timmy');
assert.containsN(form, '.o_field_many2manytags .badge', 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.hasClass(form.$('.o_form_view'),'o_form_editable', "form should be in edit mode");
testUtils.fields.many2one.clickOpenDropdown('timmy');
assert.strictEqual(form.$('.o_field_many2manytags input').autocomplete('widget').find('li').length, 3,
"autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')");
testUtils.fields.many2one.clickHighlightedItem('timmy');
assert.containsOnce(form, '.o_field_many2manytags .badge',
"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)
testUtils.form.clickSave(form);
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;
testUtils.dom.click(form.$('.badge:first() .dropdown-toggle'));
$('.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;
testUtils.form.clickEdit(form);
testUtils.dom.click(form.$('.badge:first() .dropdown-toggle'));
$('.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.containsOnce(list, '.o_data_row:first .o_field_many2manytags .badge',
"m2m field should contain one tag");
// edit first row
testUtils.dom.click(list.$('.o_data_row:first td:nth(2)'));
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
testUtils.fields.many2one.clickOpenDropdown('timmy');
testUtils.fields.many2one.clickHighlightedItem('timmy');
assert.containsN(list, '.o_data_row:first .o_field_many2manytags .badge', 2,
"m2m field should contain 2 tags");
// leave edition
testUtils.dom.click(list.$('.o_data_row:nth(1) td:nth(2)'));
assert.containsN(list, '.o_data_row:first .o_field_many2manytags .badge', 2,
"m2m field should contain 2 tags");
list.destroy();
});
QUnit.test('search more in many2one: group and use the pager', async 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',
},
});
testUtils.fields.many2one.clickOpenDropdown('trululu');
await testUtils.fields.many2one.clickItem('trululu', 'Search');
testUtils.dom.click($('.modal .o_search_options .fa-bars'));
testUtils.dom.click($('.modal .o_search_options .o_group_by_menu a:contains(Bar)'));
testUtils.dom.click($('.modal .o_group_header:first'));
assert.strictEqual($('.modal tbody:nth(1) .o_data_row').length, 7,
"should display 7 records in the first page");
testUtils.dom.click($('.modal .o_group_header:first .o_pager_next'));
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.containsN(form, '.o_field_widget[name="partner_ids"] .badge', 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,
});
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_many2manytags .badge',
"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();
testUtils.fields.editInput(form.$('input:first'), 'trigger onchange');
assert.containsNone(form, '.o_field_many2manytags .badge',
"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
testUtils.fields.many2one.clickOpenDropdown('timmy');
testUtils.fields.many2one.clickHighlightedItem('timmy');
assert.containsOnce(form, '.o_field_many2manytags .badge',
"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
testUtils.dom.click(form.$('.o_field_many2manytags .o_delete'));
assert.containsNone(form, '.o_field_many2manytags .badge',
"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
testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle'));
assert.strictEqual($('.o_colorpicker:visible').length, 1,
"colorpicker should be open");
// click on the badge again to close colorpicker
testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle'));
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
testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle'));
assert.strictEqual($('.o_colorpicker:visible').length, 1,
"colorpicker should be open");
// click on the colorpicker, but not on a color
testUtils.dom.click(form.$('.o_colorpicker'));
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
testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle'));
// 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.containsN(form, 'input.o_radio_input', 2, "should have 2 possible choices");
assert.ok(form.$('label.o_form_label:contains(xphone)').length, "one of them should be xphone");
assert.containsNone(form, 'input:checked', "none of the input should be checked");
testUtils.dom.click(form.$("input.o_radio_input:first"));
assert.containsOnce(form, 'input:checked', "one of the input should be checked");
testUtils.form.clickSave(form);
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>',
});
testUtils.dom.click(form.$("input[type='checkbox']"));
assert.containsOnce(form, 'input.o_radio_input[data-value="37"]:checked', "one of the input should be checked");
assert.containsOnce(form, 'input.o_radio_input[data-value="black"]:checked', "the other of the input should be checked");
testUtils.dom.click(form.$("input[type='checkbox']"));
assert.containsOnce(form, 'input.o_radio_input[data-value="41"]:checked', "the other of the input should be checked");
assert.containsOnce(form, 'input.o_radio_input[data-value="red"]:checked', "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.containsN(form, 'input.o_radio_input', 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
testUtils.dom.click(form.$("input.o_radio_input").eq(1));
testUtils.form.clickSave(form);
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");
testUtils.form.clickEdit(form);
assert.containsNone(form, '.o_radio_input:checked',
"no value should be checked");
testUtils.dom.click(form.$("input.o_radio_input:nth(1)"));
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), 'Black',
"value should be 'Black'");
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_radio_input[data-index=1]:checked',
"'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.containsN(form, '.o_field_widget[name=trululu] .o_radio_item', 3,
"should be 3 radio buttons");
// trigger an onchange that will update the domain
testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2);
assert.containsNone(form, '.o_field_widget[name=trululu] .o_radio_item',
"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.containsN(form, 'span.o_selection_badge', 2, "should have 2 possible choices");
assert.ok(form.$('span.o_selection_badge:contains(xphone)').length, "one of them should be xphone");
assert.containsNone(form, 'span.active', "none of the input should be checked");
testUtils.dom.click($("span.o_selection_badge:first"));
assert.containsOnce(form, 'span.active', "one of the input should be checked");
testUtils.form.clickSave(form);
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.containsN(form, 'span.o_selection_badge', 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
testUtils.dom.click(form.$("span.o_selection_badge").eq(1));
testUtils.form.clickSave(form);
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.containsOnce(form, 'span.o_readonly_modifier', "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.containsN(form, 'div.o_field_widget div.custom-checkbox', 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");
testUtils.form.clickEdit(form);
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
testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(1));
testUtils.form.clickSave(form);
assert.deepEqual(this.data.partner.records[0].timmy, [12, 14],
"should have added the second element to the many2many");
assert.containsN(form, 'input:checked', 2,
"both checkboxes should be checked");
// remove a m2m value by clinking on label
testUtils.form.clickEdit(form);
testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox > label').eq(0));
testUtils.form.clickSave(form);
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'},
});
testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(0));
testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(1));
testUtils.form.clickSave(form);
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.containsOnce(form, 'div.o_field_widget.oe_fileupload',
"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.containsNone(form, 'div.o_field_widget.oe_fileupload .o_attach',
"there should not be an Add button (readonly)");
assert.containsNone(form, 'div.o_field_widget.oe_fileupload .oe_attachment .oe_delete',
"there should not be a Delete button (readonly)");
// to edit mode
testUtils.form.clickEdit(form);
assert.containsOnce(form, 'div.o_field_widget.oe_fileupload .o_attach',
"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.containsOnce(form, 'div.o_field_widget.oe_fileupload .o_hidden_input_file form',
"there should be a hidden form to upload attachments");
// TODO: add an attachment
// no idea how to test this
// delete the attachment
testUtils.dom.click(form.$('div.o_field_widget.oe_fileupload .oe_attachment .oe_delete'));
assert.verifySteps([
'/web/dataset/call_kw/turtle/read',
'/web/dataset/call_kw/ir.attachment/read',
]);
testUtils.form.clickSave(form);
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', async function (assert) {
assert.expect(2);
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);
},
});
testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
await testUtils.fields.many2one.searchAndClickItem('product_id',
{selector:'.modal', search:'new record'});
assert.verifySteps(['name_create']);
form.destroy();
});
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');
testUtils.dom.click($cell_o2m);
// 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');
testUtils.dom.click($modal.find('.o_form_button_cancel'));
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
testUtils.form.clickEdit(form);
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');
testUtils.dom.click(form.$('.o_data_cell'));
// 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.mock.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");
testUtils.dom.click(form.$('a.o_form_uri'));
testUtils.form.clickEdit(form);
assert.containsN(form, '.o_field_widget', 2,
"should contain two field widgets (selection and many2one)");
assert.containsOnce(form, '.o_field_many2one',
"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");
testUtils.dom.click(form.$('.o_external_button'));
assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: custom label',
"dialog title should display the custom string label");
testUtils.dom.click($('.modal .o_form_button_cancel'));
testUtils.fields.editSelect(form.$('.o_field_widget select'), 'partner_type');
assert.strictEqual(form.$('.o_field_widget input').val(), "",
"many2one value should be reset after model change");
testUtils.fields.many2one.clickOpenDropdown('reference');
testUtils.fields.many2one.clickHighlightedItem('reference');
testUtils.form.clickSave(form);
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
testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 12);
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
testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 41);
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
testUtils.dom.click(list.$('.o_data_row .o_data_cell').first());
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);
}
});
testUtils.form.clickEdit(form);
var x2mList = form.$('.o_field_x2many_list[name=p]');
// Add a record in the list
testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a'));
var modal = $('.modal-lg');
var nameInput = modal.find('input.o_input[name=display_name]');
testUtils.fields.editInput(nameInput, 'michelangelo');
// Save the record in the modal (though it is still virtual)
testUtils.dom.click(modal.find('.btn-primary').first());
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
testUtils.form.clickSave(form);
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.containsN(form, 'th', 2,
"should be 2 columns in the one2many");
testUtils.form.clickEdit(form);
testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input'));
testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter'));
assert.containsOnce(form, 'th',
"should be 1 column when the product_id is set");
form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup');
assert.containsN(form, 'th', 2,
"should be 2 columns in the one2many when product_id is not set");
testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input'));
assert.containsOnce(form, 'th',
"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.containsOnce(form, 'th',
"should be only 1 column ('foo') in the one2many");
assert.containsOnce(form, '.o_list_view .o_data_row', "should contain one row");
testUtils.form.clickEdit(form);
// add a new o2m record
testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
form.$('.o_field_one2many input:first').focus();
testUtils.fields.editInput(form.$('.o_field_one2many input:first'), 'New line');
testUtils.dom.click(form.$el);
assert.containsN(form, 'th', 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.containsN(form, 'th', 2,
"should be 2 columns in the one2many");
testUtils.form.clickEdit(form);
testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input'));
testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter'));
assert.containsOnce(form, 'th',
"should be 1 column when the product_id is set");
form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup');
assert.containsN(form, 'th', 2,
"should be 2 columns in the one2many when product_id is not set");
testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input'));
assert.containsOnce(form, 'th',
"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,
}));
testUtils.dom.click(document.activeElement);
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
testUtils.dom.click(form.$el.find('.o_field_x2many_list_row_add>a'));
// open the field turtle_trululu (one2many)
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
testUtils.dom.click(form.$el.find('.o_input_dropdown>input'));
concurrency.delay(0).then(function () {
// click create and edit
testUtils.dom.click($('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter'));
// 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();
});
});
});
});