summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/fields
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-07-07 14:30:30 +0200
committerChristophe Simonis <chs@odoo.com>2017-07-07 14:30:30 +0200
commitbf51fe55a149fb642a410f579dc4b6dcbd794eac (patch)
treef4f208f06094bcef6541fd1fc034e0265de4ad21 /addons/web/static/tests/fields
parentbf32940142beb879211b7034bb77a7845c9d0555 (diff)
parent0f2b2c4e2b99df8d68456016a4bea86e26c1c80e (diff)
[MERGE] forward port branch saas-16 up to 0f2b2c4e2b9
Diffstat (limited to 'addons/web/static/tests/fields')
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js101
-rw-r--r--addons/web/static/tests/fields/field_utils_tests.js23
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js830
3 files changed, 937 insertions, 17 deletions
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index c492537c63f..c732fe35ce6 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -1043,6 +1043,40 @@ QUnit.module('basic_fields', {
_t.database.multi_lang = multiLang;
});
+ QUnit.test('go to next line (and not the next row) when pressing enter', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.fields.foo.type = 'text';
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<list editable="top">' +
+ '<field name="int_field"/>' +
+ '<field name="foo"/>' +
+ '<field name="qux"/>' +
+ '</list>',
+ });
+
+ list.$('tbody tr:first .o_list_text').click();
+ var $textarea = list.$('textarea.o_field_text');
+ assert.strictEqual($textarea.length, 1, "should have a text area");
+ assert.strictEqual($textarea.val(), 'yop', 'should still be "yop" in edit');
+
+ assert.strictEqual(list.$('textarea').get(0), document.activeElement,
+ "text area should have the focus");
+
+ // click on enter
+ list.$('textarea')
+ .trigger({type: "keydown", which: $.ui.keyCode.ENTER})
+ .trigger({type: "keyup", which: $.ui.keyCode.ENTER});
+
+ assert.strictEqual(list.$('textarea').first().get(0), document.activeElement,
+ "text area should still have the focus");
+
+ list.destroy();
+ });
+
QUnit.module('FieldBinary');
QUnit.test('binary fields are correctly rendered', function (assert) {
@@ -1973,6 +2007,63 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('monetary field with currency set by an onchange', function (assert) {
+ // this test ensures that the monetary field can be re-rendered with and
+ // without currency (which can happen as the currency can be set by an
+ // onchange)
+ assert.expect(8);
+
+ this.data.partner.onchanges = {
+ int_field: function (obj) {
+ obj.currency_id = obj.int_field ? 2 : null;
+ },
+ };
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="top">' +
+ '<field name="int_field"/>' +
+ '<field name="qux" widget="monetary"/>' +
+ '<field name="currency_id" invisible="1"/>' +
+ '</tree>',
+ session: {
+ currencies: _.indexBy(this.data.currency.records, 'id'),
+ },
+ });
+
+ list.$buttons.find('.o_list_button_add').click();
+ assert.strictEqual(list.$('div.o_field_widget[name=qux] input').length, 1,
+ "monetary field should have been rendered correctly (without currency)");
+ assert.strictEqual(list.$('.o_field_widget[name=qux] span').length, 0,
+ "monetary field should have been rendered correctly (without currency)");
+
+ // set a value for int_field -> should set the currency and re-render qux
+ list.$('.o_field_widget[name=int_field]').click().val('7').trigger('input');
+ assert.strictEqual(list.$('div.o_field_widget[name=qux] input').length, 1,
+ "monetary field should have been re-rendered correctly (with currency)");
+ assert.strictEqual(list.$('.o_field_widget[name=qux] span:contains(€)').length, 1,
+ "monetary field should have been re-rendered correctly (with currency)");
+ var $quxInput = list.$('.o_field_widget[name=qux] input');
+ $quxInput.click(); // check that the field is focusable
+ assert.strictEqual(document.activeElement, $quxInput[0],
+ "focus should be on the qux field's input");
+
+ // unset the value of int_field -> should unset the currency and re-render qux
+ list.$('.o_field_widget[name=int_field]').click().val('0').trigger('input');
+ $quxInput = list.$('div.o_field_widget[name=qux] input');
+ assert.strictEqual($quxInput.length, 1,
+ "monetary field should have been re-rendered correctly (without currency)");
+ assert.strictEqual(list.$('.o_field_widget[name=qux] span').length, 0,
+ "monetary field should have been re-rendered correctly (without currency)");
+ $quxInput.click(); // check that the field is still focusable
+ assert.strictEqual(document.activeElement, $quxInput[0],
+ "focus should be on the qux field's input");
+
+ list.destroy();
+ });
+
QUnit.module('FieldInteger');
QUnit.test('integer field when unset', function (assert) {
@@ -2024,7 +2115,7 @@ QUnit.module('basic_fields', {
});
QUnit.test('integer field in form view with virtual id', function (assert) {
- assert.expect(2);
+ assert.expect(1);
var params = {
View: FormView,
model: 'partner',
@@ -2036,15 +2127,7 @@ QUnit.module('basic_fields', {
var form = createView(params);
assert.strictEqual(form.$('.o_field_widget').text(), "2-20170808020000",
"Should display virtual id");
- form.destroy();
- params.res_id = this.data.partner.records[1].id = "2.65-20170808020000";
- try {
- form = createView(params);
- } catch (error) {
- assert.strictEqual(error.message, '"2.65-20170808020000" is not an integer or a virtual id',
- "Should throw an exception because it's a wrong value");
- }
form.destroy();
});
diff --git a/addons/web/static/tests/fields/field_utils_tests.js b/addons/web/static/tests/fields/field_utils_tests.js
index e00d45ef91f..a49197f6e65 100644
--- a/addons/web/static/tests/fields/field_utils_tests.js
+++ b/addons/web/static/tests/fields/field_utils_tests.js
@@ -2,6 +2,7 @@ odoo.define('web.field_utils_tests', function (require) {
"use strict";
var core = require('web.core');
+var session = require('web.session');
var fieldUtils = require('web.field_utils');
QUnit.module('fields', {}, function () {
@@ -122,7 +123,20 @@ QUnit.test('parse float', function(assert) {
});
QUnit.test('parse monetary', function(assert) {
- assert.expect(6);
+ assert.expect(11);
+ var originalCurrencies = session.currencies;
+ session.currencies = {
+ 1: {
+ digits: [69, 2],
+ position: "after",
+ symbol: "€"
+ },
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$"
+ }
+ };
assert.strictEqual(fieldUtils.parse.monetary(""), 0);
assert.strictEqual(fieldUtils.parse.monetary("0"), 0);
@@ -130,6 +144,13 @@ QUnit.test('parse monetary', function(assert) {
assert.strictEqual(fieldUtils.parse.monetary("-100.00"), -100);
assert.strictEqual(fieldUtils.parse.monetary("1,000.00"), 1000);
assert.strictEqual(fieldUtils.parse.monetary("1,000,000.00"), 1000000);
+ assert.strictEqual(fieldUtils.parse.monetary("$&nbsp;125.00", {}, {currency_id: 3}), 125);
+ assert.strictEqual(fieldUtils.parse.monetary("1,000.00&nbsp;€", {}, {currency_id: 1}), 1000);
+ assert.throws(function() {fieldUtils.parse.monetary("$ 12.00", {}, {currency_id: 3})}, /is not a correct/);
+ assert.throws(function() {fieldUtils.parse.monetary("$&nbsp;12.00", {}, {currency_id: 1})}, /is not a correct/);
+ assert.throws(function() {fieldUtils.parse.monetary("$&nbsp;12.00&nbsp;34", {}, {currency_id: 3})}, /is not a correct/);
+
+ session.currencies = originalCurrencies;
});
});
});
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 3bf541b097d..d4d3a6307f9 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -34,6 +34,8 @@ QUnit.module('relational_fields', {
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,
@@ -47,6 +49,7 @@ QUnit.module('relational_fields', {
timmy: [],
trululu: 4,
user_id: 17,
+ reference: 'product,37',
}, {
id: 2,
display_name: "second record",
@@ -963,7 +966,9 @@ QUnit.module('relational_fields', {
});
QUnit.test('domain and context are correctly used when doing a name_search in a m2o', function (assert) {
- assert.expect(2);
+ assert.expect(4);
+
+ this.data.partner.records[0].timmy = [12];
var form = createView({
View: FormView,
@@ -975,11 +980,13 @@ QUnit.module('relational_fields', {
'domain="[[\'foo\', \'=\', \'bar\'], [\'foo\', \'=\', foo]]" ' +
'context="{\'hello\': \'world\', \'test\': foo}"/>' +
'<field name="foo"/>' +
+ '<field name="trululu" context="{\'timmy\': timmy}" domain="[[\'id\', \'in\', timmy]]"/>' +
+ '<field name="timmy" widget="many2many_tags" invisible="1"/>' +
'</form>',
res_id: 1,
session: {user_context: {hey: "ho"}},
mockRPC: function (route, args) {
- if (route === '/web/dataset/call_kw/product/name_search') {
+ if (args.method === 'name_search' && args.model === 'product') {
assert.deepEqual(
args.kwargs.args,
[['foo', '=', 'bar'], ['foo', '=', 'yop']],
@@ -990,12 +997,20 @@ QUnit.module('relational_fields', {
'the field attr context should have been used for the '
+ 'RPC (evaluated and merged with the session one)');
}
+ if (args.method === 'name_search' && args.model === 'partner') {
+ assert.deepEqual(args.kwargs.args, [['id', 'in', [12]]],
+ 'the field attr domain should have been used for the RPC (and evaluated)');
+ assert.deepEqual(args.kwargs.context, {hey: 'ho', timmy: [[6, false, [12]]]},
+ 'the field attr context should have been used for the RPC (and evaluated)');
+ }
return this._super.apply(this, arguments);
},
});
form.$buttons.find('.o_form_button_edit').click();
- form.$('input').first().click();
+ form.$('.o_field_widget[name=product_id] input').click();
+
+ form.$('.o_field_widget[name=trululu] input').click();
form.destroy();
});
@@ -1235,6 +1250,56 @@ QUnit.module('relational_fields', {
});
});
+ QUnit.test('many2one: domain updated by an onchange', function (assert) {
+ assert.expect(2);
+
+ 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"/>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ domain = [['id', 'in', [10]]];
+ return $.when({
+ domain: {
+ trululu: domain,
+ unexisting_field: domain,
+ }
+ });
+ }
+ if (args.method === 'name_search') {
+ assert.deepEqual(args.kwargs.args, domain,
+ "sent domain should be correct");
+ }
+ return this._super(route, args);
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // trigger a name_search (domain should be [])
+ form.$('.o_field_widget[name=trululu] input').click();
+ // close the dropdown
+ form.$('.o_field_widget[name=trululu] input').click();
+ // trigger an onchange that will update the domain
+ form.$('.o_field_widget[name=int_field]').val(2).trigger('input');
+ // trigger a name_search (domain should be [['id', 'in', [10]]])
+ form.$('.o_field_widget[name=trululu] input').click();
+
+ form.destroy();
+ });
+
QUnit.module('FieldOne2Many');
QUnit.test('one2many basic properties', function (assert) {
@@ -1522,6 +1587,178 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('edition of one2many field with pager', function (assert) {
+ assert.expect(31);
+
+ var ids = [];
+ for (var i = 0; i < 45; i++) {
+ var id = 10 + i;
+ ids.push(id);
+ this.data.partner.records.push({
+ id: id,
+ display_name: "relational record " + id,
+ });
+ }
+ this.data.partner.records[0].p = ids;
+
+ var saveCount = 0;
+ var checkRead = false;
+ var readIDs;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<kanban>' +
+ '<field name="display_name"/>' +
+ '<templates>' +
+ '<t t-name="kanban-box">' +
+ '<div class="oe_kanban_global_click">' +
+ '<a t-if="!read_only_mode" type="delete" class="fa fa-times pull-right delete_icon"/>' +
+ '<span><t t-esc="record.display_name.value"/></span>' +
+ '</div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'partner,false,form': '<form><field name="display_name"/></form>',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'read' && checkRead) {
+ readIDs = args.args[0];
+ checkRead = false;
+ }
+ if (args.method === 'write') {
+ saveCount++;
+ var nbCommands = args.args[1].p.length;
+ var nbLinkCommands = _.filter(args.args[1].p, function (command) {
+ return command[0] === 4;
+ }).length;
+ switch(saveCount) {
+ case 1:
+ assert.strictEqual(nbCommands, 46,
+ "should send 46 commands (one for each record)");
+ assert.strictEqual(nbLinkCommands, 45,
+ "should send a LINK_TO command for each existing record");
+ assert.deepEqual(args.args[1].p[45], [0, false, {
+ display_name: 'new record',
+ }], "should sent a CREATE command for the new record");
+ break;
+ case 2:
+ assert.strictEqual(nbCommands, 46,
+ "should send 46 commands");
+ assert.strictEqual(nbLinkCommands, 45,
+ "should send a LINK_TO command for each existing record");
+ assert.deepEqual(args.args[1].p[45], [2, 10, false],
+ "should sent a DELETE command for the deleted record");
+ break;
+ case 3:
+ assert.strictEqual(nbCommands, 47,
+ "should send 47 commands");
+ assert.strictEqual(nbLinkCommands, 43,
+ "should send a LINK_TO command for each existing record");
+ assert.deepEqual(args.args[1].p[43],
+ [0, false, {display_name: 'new record page 1'}],
+ "should sent correct CREATE command");
+ assert.deepEqual(args.args[1].p[44],
+ [0, false, {display_name: 'new record page 2'}],
+ "should sent correct CREATE command");
+ assert.deepEqual(args.args[1].p[45],
+ [2, 11, false],
+ "should sent correct DELETE command");
+ assert.deepEqual(args.args[1].p[46],
+ [2, 52, false],
+ "should sent correct DELETE command");
+ break;
+ }
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'there should be 40 records on page 1');
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '1-40 / 45', "pager range should be correct");
+
+ // add a record on page one
+ checkRead = true;
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o-kanban-button-new').click();
+ $('.modal input').val('new record').trigger('input');
+ $('.modal .modal-footer .btn-primary:first').click(); // save and close
+ // checks
+ assert.strictEqual(readIDs, undefined, "should not have read any record");
+ assert.strictEqual(form.$('span:contains(new record)').length, 0,
+ "new record should be on page 2");
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'there should be 40 records on page 1');
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '1-40 / 46', "pager range should be correct");
+ assert.strictEqual(form.$('.o_kanban_record:first span:contains(new record)').length,
+ 0, 'new record should not be on page 1');
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+
+ // delete a record on page one
+ checkRead = true;
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 10)').length,
+ 1, 'first record should be the one with id 10 (next checks rely on that)');
+ form.$('.delete_icon:first').click();
+ // checks
+ assert.deepEqual(readIDs, [50],
+ "should have read a record (to display 40 records on page 1)");
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'there should be 40 records on page 1');
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '1-40 / 45', "pager range should be correct");
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+
+ // add and delete records in both pages
+ form.$buttons.find('.o_form_button_edit').click();
+ checkRead = true;
+ readIDs = undefined;
+ // add and delete a record in page 1
+ form.$('.o-kanban-button-new').click();
+ $('.modal input').val('new record page 1').trigger('input');
+ $('.modal .modal-footer .btn-primary:first').click(); // save and close
+ assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 11)').length,
+ 1, 'first record should be the one with id 11 (next checks rely on that)');
+ form.$('.delete_icon:first').click();
+ assert.deepEqual(readIDs, [51],
+ "should have read a record (to display 40 records on page 1)");
+ // add and delete a record in page 2
+ form.$('.o_x2m_control_panel .o_pager_next').click();
+ assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 52)').length,
+ 1, 'first record should be the one with id 52 (next checks rely on that)');
+ checkRead = true;
+ readIDs = undefined;
+ form.$('.delete_icon:first').click();
+ form.$('.o-kanban-button-new').click();
+ $('.modal input').val('new record page 2').trigger('input');
+ $('.modal .modal-footer .btn-primary:first').click(); // save and close
+ assert.strictEqual(readIDs, undefined, "should not have read any record");
+ // checks
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5,
+ 'there should be 5 records on page 2');
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '41-45 / 45', "pager range should be correct");
+ assert.strictEqual(form.$('.o_kanban_record span:contains(new record page 1)').length,
+ 1, 'new records should be on page 2');
+ assert.strictEqual(form.$('.o_kanban_record span:contains(new record page 2)').length,
+ 1, 'new records should be on page 2');
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
QUnit.test('sorting one2many fields', function (assert) {
assert.expect(4);
@@ -2357,6 +2594,45 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('editable o2m with onchange and required field: delete an invalid line', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.onchanges = {
+ turtles: function () {},
+ };
+ this.data.partner.records[0].turtles = [1];
+ this.data.turtle.records[0].product_id = 37;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="product_id"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ form.$('.o_data_cell:first').click();
+ form.$('.o_field_widget[name="product_id"] input').val('').trigger('keyup');
+ assert.verifySteps(['read', 'read'], 'no onchange should be done as line is invalid');
+ form.$('.o_list_record_delete').click();
+ assert.verifySteps(['read', 'read', 'onchange'], 'onchange should have been done');
+
+ form.destroy();
+ });
+
QUnit.test('onchange in a one2many', function (assert) {
assert.expect(1);
@@ -2515,6 +2791,75 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('one2many and onchange (with command DELETE_ALL)', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.onchanges = {
+ foo: function (obj) {
+ obj.p = [[5]];
+ },
+ p: function () {}, // dummy onchange on the o2m to execute _isX2ManyValid()
+ };
+ this.data.partner.records[0].p = [2];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="foo"/>' +
+ '<field name="p">' +
+ '<tree editable="bottom">' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ mockRPC: function (method, args) {
+ if (args.method === 'write') {
+ assert.deepEqual(args.args[1].p, [
+ [0, false, {display_name: 'z'}],
+ [2, 2, false],
+ ], "correct commands should be sent");
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "o2m should contain one row");
+
+ // empty o2m by triggering the onchange
+ form.$('.o_field_widget[name=foo]').val('trigger onchange').trigger('input');
+
+ assert.strictEqual(form.$('.o_data_row').length, 0,
+ "rows of the o2m should have been deleted");
+
+ // add two new subrecords
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_field_widget[name=display_name]').val('x').trigger('input');
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_field_widget[name=display_name]').val('y').trigger('input');
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ "o2m should contain two rows");
+
+ // empty o2m by triggering the onchange
+ form.$('.o_field_widget[name=foo]').val('trigger onchange again').trigger('input');
+
+ assert.strictEqual(form.$('.o_data_row').length, 0,
+ "rows of the o2m should have been deleted");
+
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_field_widget[name=display_name]').val('z').trigger('input');
+
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+
QUnit.test('one2many list (editable): readonly domain is evaluated', function (assert) {
assert.expect(2);
@@ -2782,6 +3127,41 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('value of invisible x2many fields is correctly evaluated in context', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.records[0].timmy = [12];
+ this.data.partner.records[0].p = [2, 3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form string="Partners">' +
+ '<field name="product_id" context="{\'p\': p, \'timmy\': timmy}"/>' +
+ '<field name="p" invisible="1"/>' +
+ '<field name="timmy" invisible="1"/>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'name_search') {
+ assert.deepEqual(
+ args.kwargs.context, {
+ p: [[4, 2, false], [4, 3, false]],
+ timmy: [[6, false, [12]]],
+ }, 'values of x2manys should have been correctly evaluated in context');
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_field_widget[name=product_id] input').click();
+
+ form.destroy();
+ });
+
QUnit.test('one2many list, editable, with many2one and with context with parent key', function (assert) {
assert.expect(1);
@@ -4328,6 +4708,93 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('editable list: contexts are correctly sent', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records[0].timmy = [12];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="foo"/>' +
+ '<field name="timmy" context="{\'key\': parent.foo}">' +
+ '<tree editable="top">' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'read' && args.model === 'partner') {
+ assert.deepEqual(args.kwargs.context, {
+ active_field: 2,
+ bin_size: true,
+ someKey: 'some value',
+ }, "sent context should be correct");
+ }
+ if (args.method === 'read' && args.model === 'partner_type') {
+ assert.deepEqual(args.kwargs.context, {
+ key: 'yop',
+ someKey: 'some value',
+ }, "sent context should be correct");
+ }
+ if (args.method === 'write') {
+ assert.deepEqual(args.kwargs.context, {
+ active_field: 2,
+ someKey: 'some value',
+ }, "sent context should be correct");
+ }
+ return this._super.apply(this, arguments);
+ },
+ session: {
+ user_context: {someKey: 'some value'},
+ },
+ viewOptions: {
+ mode: 'edit',
+ context: {active_field: 2},
+ },
+ res_id: 1,
+ });
+
+ form.$('.o_data_cell:first').click();
+ form.$('.o_field_widget[name=display_name]').val('abc').trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('resetting invisible one2manys', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records[0].turtles = [];
+ this.data.partner.onchanges.foo = function (obj) {
+ obj.turtles = [[5], [4, 1]];
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="foo"/>' +
+ '<field name="turtles" invisible="1"/>' +
+ '</form>',
+ viewOptions: {
+ mode: 'edit',
+ },
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$('input[name="foo"]').val('abcd').trigger('input');
+ assert.verifySteps(['read', 'onchange']);
+
+ form.destroy();
+ });
+
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', function (assert) {
@@ -4987,6 +5454,96 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('onchange with 40+ commands for a many2many', function (assert) {
+ // this test ensures that the basic_model correctly handles more LINK_TO
+ // commands than the limit of the dataPoint (40 for x2many kanban)
+ assert.expect(23);
+
+ // create a lot of partner_types that will be linked by the onchange
+ var commands = [[5]];
+ for (var i = 0; i < 45; i++) {
+ var id = 100 + i;
+ this.data.partner_type.records.push({id: id, display_name: "type " + id});
+ commands.push([4, id]);
+ }
+ this.data.partner.onchanges = {
+ foo: function (obj) {
+ obj.timmy = commands;
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="foo"/>' +
+ '<field name="timmy">' +
+ '<kanban>' +
+ '<field name="display_name"/>' +
+ '<templates>' +
+ '<t t-name="kanban-box">' +
+ '<div><t t-esc="record.display_name.value"/></div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].timmy[0][0], 6,
+ "should send a command 6");
+ assert.strictEqual(args.args[1].timmy[0][2].length, 45,
+ "should replace with 45 ids");
+ }
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ assert.verifySteps(['read']);
+
+ form.$('.o_field_widget[name=foo]').val('trigger onchange').trigger('input');
+
+ assert.verifySteps(['read', 'onchange', 'read']);
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '1-40 / 45', "pager should be correct");
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'there should be 40 records displayed on page 1');
+
+ form.$('.o_pager_next').click();
+ assert.verifySteps(['read', 'onchange', 'read', 'read']);
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '41-45 / 45', "pager should be correct");
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5,
+ 'there should be 5 records displayed on page 2');
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '1-40 / 45', "pager should be correct");
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'there should be 40 records displayed on page 1');
+
+ form.$('.o_pager_next').click();
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '41-45 / 45', "pager should be correct");
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5,
+ 'there should be 5 records displayed on page 2');
+
+ form.$('.o_pager_next').click(); // back to page 1
+ assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
+ '1-40 / 45', "pager should be correct");
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'there should be 40 records displayed on page 1');
+
+ form.destroy();
+ });
+
QUnit.module('FieldStatus');
QUnit.test('static statusbar widget on many2one field', function (assert) {
@@ -5380,10 +5937,117 @@ QUnit.module('relational_fields', {
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('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.module('FieldMany2ManyTags');
- QUnit.test('fieldmany2many tags: rendering and edition', function (assert) {
- assert.expect(17);
+ QUnit.test('fieldmany2many tags without color', function (assert) {
+ assert.expect(4);
+
+ 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 (args.method ==='read' && args.model === 'partner_type') {
+ assert.deepEqual(args.args , [[12], ['display_name']], "should not read any color field");
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+ 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 > span').length, 1,
+ "should contain 1 tag");
+ assert.ok(form.$('.o_field_many2manytags > span:contains("gold")').length,
+ "should contain newly added tag 'gold'");
+ form.destroy();
+ });
+
+ QUnit.test('fieldmany2many tags with color: rendering and edition', function (assert) {
+ assert.expect(20);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records.push({id: 13, display_name: "red", color: 8});
@@ -5392,7 +6056,7 @@ QUnit.module('relational_fields', {
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
- '<field name="timmy" widget="many2many_tags" options="{\'no_create_edit\': True}"/>' +
+ '<field name="timmy" widget="many2many_tags" options="{\'color_field\': \'color\', \'no_create_edit\': True}"/>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
@@ -5403,6 +6067,9 @@ QUnit.module('relational_fields', {
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);
},
});
@@ -5548,7 +6215,7 @@ QUnit.module('relational_fields', {
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
- '<field name="timmy" widget="many2many_tags"/>' +
+ '<field name="timmy" widget="many2many_tags" options="{\'color_field\': \'color\'}"/>' +
'</form>',
res_id: 1,
});
@@ -5794,6 +6461,58 @@ QUnit.module('relational_fields', {
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('FieldMany2ManyCheckBoxes');
QUnit.test('widget many2many_checkboxes', function (assert) {
@@ -5973,6 +6692,103 @@ QUnit.module('relational_fields', {
});
});
+ QUnit.module('FieldReference');
+
+ 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();
+ });
+
});
});
});