summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/fields
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2017-04-07 11:12:58 +0200
committerAaron Bohy <aab@odoo.com>2017-04-11 19:44:38 +0200
commit875ea7964c62552ba976f09ad6c94db6fffb5b66 (patch)
tree94dfdf90e30dcea5c457961de7df785dcb82e83e /addons/web/static/tests/fields
parentf0ee1e2be579904495b2a78d56c314960392e94f (diff)
[REF] account, web, web_diagram: rename static test folder
For consistency, every static test folder should be named 'tests', with a s.
Diffstat (limited to 'addons/web/static/tests/fields')
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js2718
-rw-r--r--addons/web/static/tests/fields/field_utils_tests.js81
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js3742
3 files changed, 6541 insertions, 0 deletions
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
new file mode 100644
index 00000000000..c56f694e1fe
--- /dev/null
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -0,0 +1,2718 @@
+odoo.define('web.basic_fields_tests', function (require) {
+"use strict";
+
+var concurrency = require('web.concurrency');
+var FormView = require('web.FormView');
+var KanbanView = require('web.KanbanView');
+var ListView = require('web.ListView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('fields', {}, function () {
+
+QUnit.module('basic_fields', {
+ beforeEach: function () {
+ this.data = {
+ partner: {
+ fields: {
+ date: {string: "A date", type: "date", searchable: true},
+ datetime: {string: "A datetime", type: "datetime", searchable: true},
+ display_name: {string: "Displayed name", type: "char", searchable: true},
+ foo: {string: "Foo", type: "char", default: "My little Foo Value", searchable: true},
+ bar: {string: "Bar", type: "boolean", default: true, searchable: true},
+ int_field: {string: "int_field", type: "integer", sortable: true, searchable: true},
+ qux: {string: "Qux", type: "float", digits: [16,1], searchable: true},
+ p: {string: "one2many field", type: "one2many", relation: 'partner', searchable: true},
+ trululu: {string: "Trululu", type: "many2one", relation: 'partner', searchable: true},
+ timmy: {string: "pokemon", type: "many2many", relation: 'partner_type', searchable: true},
+ product_id: {string: "Product", type: "many2one", relation: 'product', searchable: true},
+ sequence: {type: "integer", string: "Sequence", searchable: true},
+ currency_id: {string: "Currency", type: "many2one", relation: "currency", searchable: true},
+ selection: {string: "Selection", type: "selection", searchable:true,
+ selection: [['normal', 'Normal'],['blocked', 'Blocked'],['done', 'Done']]},
+ },
+ records: [{
+ id: 1,
+ date: "2017-02-03",
+ datetime: "2017-02-08 10:00:00",
+ display_name: "first record",
+ bar: true,
+ foo: "yop",
+ int_field: 10,
+ qux: 0.44444,
+ p: [],
+ timmy: [],
+ trululu: 4,
+ selection: 'blocked',
+ }, {
+ id: 2,
+ display_name: "second record",
+ bar: true,
+ foo: "blip",
+ int_field: 0,
+ qux: 0,
+ p: [],
+ timmy: [],
+ trululu: 1,
+ sequence: 4,
+ currency_id: 2,
+ selection: 'normal',
+ }, {
+ id: 4,
+ display_name: "aaa",
+ foo: "abc",
+ sequence: 9,
+ int_field: false,
+ qux: false,
+ selection: 'done',
+ },
+ {id: 3, bar: true, foo: "gnap", int_field: 80, qux: -3.89859, m2o: 1, m2m: []},
+ {id: 5, bar: false, foo: "blop", int_field: -4, qux: 9.1, m2o: 1, m2m: [1], currency_id: 1}],
+ onchanges: {},
+ },
+ product: {
+ fields: {
+ name: {string: "Product Name", type: "char", searchable: true}
+ },
+ records: [{
+ id: 37,
+ display_name: "xphone",
+ }, {
+ id: 41,
+ display_name: "xpad",
+ }]
+ },
+ partner_type: {
+ fields: {
+ name: {string: "Partner Type", type: "char", searchable: true},
+ color: {string: "Color index", type: "integer", searchable: true},
+ },
+ records: [
+ {id: 12, display_name: "gold", color: 2},
+ {id: 14, display_name: "silver", color: 5},
+ ]
+ },
+ currency: {
+ fields: {
+ symbol: {string: "Currency Sumbol", type: "char", searchable: true},
+ position: {string: "Currency Position", type: "char", searchable: true},
+ },
+ records: [{
+ id: 1,
+ display_name: "$",
+ symbol: "$",
+ position: "before",
+ }, {
+ id: 2,
+ display_name: "€",
+ symbol: "€",
+ position: "after",
+ }]
+ },
+ };
+ }
+}, function () {
+
+ QUnit.module('FieldBoolean');
+
+ QUnit.test('boolean field in form view', function (assert) {
+ assert.expect(7);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form><field name="bar"/></form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_field_boolean input:checked').length, 1,
+ "checkbox should be checked");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_field_boolean input:checked').length, 1,
+ "checkbox should still be checked");
+
+ // uncheck the checkbox
+ form.$('.o_field_boolean input:checked').click();
+ assert.strictEqual(form.$('.o_field_boolean input:checked').length, 0,
+ "checkbox should no longer be checked");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_field_boolean input:checked').length, 0,
+ "checkbox should still no longer be checked");
+
+ // switch to edit mode and test the opposite change
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_field_boolean input:checked').length, 0,
+ "checkbox should still be unchecked");
+ // check the checkbox by hitting the "enter" key after focusing it
+ form.$('.o_field_boolean input')
+ .trigger("focusin")
+ .trigger({type: "keydown", which: $.ui.keyCode.ENTER})
+ .trigger({type: "keyup", which: $.ui.keyCode.ENTER});
+ assert.strictEqual(form.$('.o_field_boolean input:checked').length, 1,
+ "checkbox should now be checked");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_field_boolean input:checked').length, 1,
+ "checkbox should still be checked");
+ form.destroy();
+ });
+
+ QUnit.test('boolean field in editable list view', function (assert) {
+ assert.expect(11);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="bar"/></tree>',
+ });
+
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input').length, 5,
+ "should have 5 checkboxes");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input:checked').length, 4,
+ "should have 4 checked input");
+
+ // Edit a line
+ var $cell = list.$('tr.o_data_row:has(.o_checkbox input:checked) td:not(.o_list_record_selector)').first();
+ assert.ok($cell.find('.o_checkbox input:checked').prop('disabled'),
+ "input should be disabled in readonly mode")
+ $cell.click();
+ assert.ok(!$cell.find('.o_checkbox input:checked').prop('disabled'),
+ "input should not have the disabled property in edit mode")
+ $cell.find('.o_checkbox input:checked').click();
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ $cell = list.$('tr.o_data_row:has(.o_checkbox input:not(:checked)) td:not(.o_list_record_selector)').first();
+ assert.ok($cell.find('.o_checkbox input:not(:checked)').prop('disabled'),
+ "input should be disabled again")
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input').length, 5,
+ "should still have 5 checkboxes");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input:checked').length, 3,
+ "should now have only 3 checked input");
+
+ // Re-Edit the line and fake-check the checkbox
+ $cell.click();
+ var $checkbox = $cell.find('.o_checkbox input:not(:checked)');
+ $checkbox.click(); // Change the checkbox
+ $checkbox.click(); // Undo the change
+
+ // Save
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input').length, 5,
+ "should still have 5 checkboxes");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input:checked').length, 3,
+ "should still have only 3 checked input");
+
+ // Re-Edit the line to check the checkbox back
+ $cell = list.$('tr.o_data_row:has(.o_checkbox input:not(:checked)) td:not(.o_list_record_selector)').first();
+ $cell.click();
+ $cell.find('.o_checkbox input:not(:checked)').click();
+
+ // save
+ list.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input').length, 5,
+ "should still have 5 checkboxes");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .o_checkbox input:checked').length, 4,
+ "should now have 4 checked input back");
+ list.destroy();
+ });
+
+
+ QUnit.module('FieldBooleanButton');
+
+ QUnit.test('use custom terminology in form view', function (assert) {
+ assert.expect(2);
+
+ var terminology = {
+ string_true: "Production Environment",
+ hover_true: "Switch to test environment",
+ string_false: "Test Environment",
+ hover_false: "Switch to production environment"
+ };
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<div name="button_box" class="oe_button_box">' +
+ '<button type="object" class="oe_stat_button" icon="fa-check-square">' +
+ '<field name="bar" widget="boolean_button" options=\'{"terminology": ' +
+ JSON.stringify(terminology) + '}\'/>' +
+ '</button>' +
+ '</div>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ assert.strictEqual(form.$('.o_stat_text.o_not_hover:contains(Production Environment)').length, 1,
+ "button should contain correct string");
+ assert.strictEqual(form.$('.o_stat_text.o_hover:contains(Switch to test environment)').length, 1,
+ "button should display correct string when hovering");
+ form.destroy();
+ });
+
+
+ QUnit.module('FieldFloat');
+
+ QUnit.test('float field when unset', function(assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux" digits="[5,3]"/>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 4,
+ });
+
+ assert.ok(form.$('.o_form_field').hasClass('o_form_field_empty'),
+ 'Non-set float field should be recognized as unset.');
+
+ form.destroy();
+ });
+
+ QUnit.test('float fields use correct digit precision', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="qux"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+ assert.strictEqual(form.$('span.o_form_field_number:contains(0.4)').length, 1,
+ "should contain a number rounded to 1 decimal");
+ form.destroy();
+ });
+
+ QUnit.test('float field in form view', function(assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux" widget="float" digits="[5,3]"/>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ assert.ok(!form.$('.o_form_field').hasClass('o_form_field_empty'),
+ 'Float field should be considered set for value 0.');
+ assert.strictEqual(form.$('.o_form_field.o_form_field_number').first().text(), '0.000',
+ 'The value should be displayed properly.')
+
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input.o_form_input').val(), '0.000',
+ 'The value should be rendered with correct precision.')
+
+ form.$('input.o_form_input').val('108.2458938598598').trigger('input');
+ assert.strictEqual(form.$('input.o_form_input').val(), '108.2458938598598',
+ 'The value should not be formated yet.')
+
+ form.$('input.o_form_input').val('18.8958938598598').trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field.o_form_field_number').first().text(), '18.896',
+ 'The new value should be rounded properly.')
+
+ form.destroy();
+ });
+
+ QUnit.test('float field in editable list view', function (assert) {
+ assert.expect(4);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="qux" widget="float" digits="[5,3]"/>' +
+ '</tree>',
+ });
+
+ var zeroValues = list.$('td').filter(function() {return $(this).text() === '0.000'});
+ assert.strictEqual(zeroValues.length, 2,
+ 'Unset float values should be rendered as zeros.');
+
+ // switch to edit mode
+ var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first();
+ $cell.click();
+
+ assert.strictEqual(list.$('input.o_form_input').length, 1,
+ 'The view should have 1 input for editable float.')
+
+ list.$('input.o_form_input').val('108.2458938598598').trigger('input');
+ assert.strictEqual(list.$('input.o_form_input').val(), '108.2458938598598',
+ 'The value should not be formated yet.')
+
+ list.$('input.o_form_input').val('18.8958938598598').trigger('input');
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('.o_form_field.o_form_field_number').first().text(), '18.896',
+ 'The new value should be rounded properly.')
+
+ list.destroy();
+ });
+
+
+ QUnit.module('EmailWidget');
+
+ QUnit.test('email field in form view', function (assert) {
+ assert.expect(7);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo" widget="email"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ var $mailtoLink = form.$('a.o_form_uri.o_form_field.o_text_overflow');
+ assert.strictEqual($mailtoLink.length, 1,
+ "should have a anchor with correct classes");
+ assert.strictEqual($mailtoLink.text(), 'yop',
+ "the value should be displayed properly");
+ assert.strictEqual($mailtoLink.attr('href'), 'mailto:yop',
+ "should have proper mailto prefix");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').length, 1,
+ "should have an input for the email field");
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').val(), 'yop',
+ "input should contain field value in edit mode");
+
+ // change value in edit mode
+ form.$('input[type="text"].o_form_input.o_form_field').val('new').trigger('input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ $mailtoLink = form.$('a.o_form_uri.o_form_field.o_text_overflow');
+ assert.strictEqual($mailtoLink.text(), 'new',
+ "new value should be displayed properly");
+ assert.strictEqual($mailtoLink.attr('href'), 'mailto:new',
+ "should still have proper mailto prefix");
+
+ form.destroy();
+ });
+
+ QUnit.test('email field in editable list view', function(assert) {
+ assert.expect(10);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="foo" widget="email"/></tree>',
+ });
+
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5,
+ "should have 5 cells");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop',
+ "value should be displayed properly as text");
+
+ var $mailtoLink = list.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ assert.strictEqual($mailtoLink.length, 5,
+ "should have anchors with correct classes");
+ assert.strictEqual($mailtoLink.first().attr('href'), 'mailto:yop',
+ "should have proper mailto prefix");
+
+ // Edit a line and check the result
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+ assert.ok($cell.hasClass('o_edit_mode'), 'should be set as edit mode');
+ assert.strictEqual($cell.find('input').val(), 'yop',
+ 'should have the corect value in internal input');
+ $cell.find('input').val('new').trigger('input');;
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ assert.ok(!$cell.hasClass('o_edit_mode'), 'should not be in edit mode anymore');
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new',
+ "value should be properly updated");
+ $mailtoLink = list.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ assert.strictEqual($mailtoLink.length, 5,
+ "should still have anchors with correct classes");
+ assert.strictEqual($mailtoLink.first().attr('href'), 'mailto:new',
+ "should still have proper mailto prefix");
+
+ list.destroy();
+ });
+
+
+ QUnit.module('FieldChar');
+
+ QUnit.test('char widget isValid method works', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.fields.foo.required = true;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="foo"/>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ var charField = form.renderer.widgets[0];
+ assert.strictEqual(charField.isValid(), true);
+ form.destroy();
+ });
+
+ QUnit.test('char field in form view', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_form_field').text(), 'yop',
+ "the value should be displayed properly");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').length, 1,
+ "should have an input for the char field");
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').val(), 'yop',
+ "input should contain field value in edit mode");
+
+ // change value in edit mode
+ form.$('input[type="text"].o_form_input.o_form_field').val('limbo').trigger('input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field').text(), 'limbo',
+ 'the new value should be displayed');
+ form.destroy();
+ });
+
+ QUnit.test('char field in editable list view', function(assert) {
+ assert.expect(6);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="foo"/></tree>',
+ });
+
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5,
+ "should have 5 cells");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop',
+ "value should be displayed properly as text");
+
+ // Edit a line and check the result
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+ assert.ok($cell.hasClass('o_edit_mode'), 'should be set as edit mode');
+ assert.strictEqual($cell.find('input').val(), 'yop',
+ 'should have the corect value in internal input');
+ $cell.find('input').val('brolo').trigger('input');;
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ assert.ok(!$cell.hasClass('o_edit_mode'), 'should not be in edit mode anymore');
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'brolo',
+ "value should be properly updated");
+ list.destroy();
+ });
+
+
+ QUnit.module('UrlWidget');
+
+ QUnit.test('url widget in form view', function (assert) {
+ assert.expect(8);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo" widget="url"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('a.o_form_uri.o_form_field.o_text_overflow').length, 1,
+ "should have a anchor with correct classes");
+ assert.strictEqual(form.$('a.o_form_uri.o_form_field.o_text_overflow').attr('href'), 'yop',
+ "should have proper href link");
+ assert.strictEqual(form.$('a.o_form_uri.o_form_field.o_text_overflow').text(), 'yop',
+ "the value should be displayed properly");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').length, 1,
+ "should have an input for the char field");
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').val(), 'yop',
+ "input should contain field value in edit mode");
+
+ // change value in edit mode
+ form.$('input[type="text"].o_form_input.o_form_field').val('limbo').trigger('input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('a.o_form_uri.o_form_field.o_text_overflow').length, 1,
+ "should still have a anchor with correct classes");
+ assert.strictEqual(form.$('a.o_form_uri.o_form_field.o_text_overflow').attr('href'), 'limbo',
+ "should have proper new href link");
+ assert.strictEqual(form.$('a.o_form_uri.o_form_field.o_text_overflow').text(), 'limbo',
+ 'the new value should be displayed');
+
+ form.destroy();
+ });
+
+ QUnit.test('char field in editable list view', function(assert) {
+ assert.expect(10);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="foo" widget="url"/></tree>',
+ });
+
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5,
+ "should have 5 cells");
+ assert.strictEqual(list.$('a.o_form_uri.o_field_widget.o_text_overflow').length, 5,
+ "should have 5 anchors with correct classes");
+ assert.strictEqual(list.$('a.o_form_uri.o_field_widget.o_text_overflow').first().attr('href'), 'yop',
+ "should have proper href link");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop',
+ "value should be displayed properly as text");
+
+ // Edit a line and check the result
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+ assert.ok($cell.hasClass('o_edit_mode'), 'should be set as edit mode');
+ assert.strictEqual($cell.find('input').val(), 'yop',
+ 'should have the corect value in internal input');
+ $cell.find('input').val('brolo').trigger('input');;
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ assert.ok(!$cell.hasClass('o_edit_mode'), 'should not be in edit mode anymore');
+ assert.strictEqual(list.$('a.o_form_uri.o_field_widget.o_text_overflow').length, 5,
+ "should still have 5 anchors with correct classes");
+ assert.strictEqual(list.$('a.o_form_uri.o_field_widget.o_text_overflow').first().attr('href'), 'brolo',
+ "should have proper new href link");
+ assert.strictEqual(list.$('a.o_form_uri.o_field_widget.o_text_overflow').first().text(), 'brolo',
+ "value should be properly updated");
+
+ list.destroy();
+ });
+
+
+ QUnit.module('FieldText');
+
+ QUnit.test('text fields are correctly rendered', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.fields.foo.type = 'text';
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="int_field"/>' +
+ '<field name="foo"/>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(form.$('div.o_form_textarea').length, "should have a text area");
+ assert.strictEqual(form.$('div.o_form_textarea').text(), 'yop', 'should be "yop" in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ var $textarea = form.$('.o_form_textarea textarea');
+ assert.ok($textarea.length, "should have a text area");
+ assert.strictEqual($textarea.val(), 'yop', 'should still be "yop" in edit');
+
+ $textarea.val('hello').trigger('input');
+ assert.strictEqual($textarea.val(), 'hello', 'should be "hello" after first edition');
+
+ $textarea.val('hello world').trigger('input');
+ assert.strictEqual($textarea.val(), 'hello world', 'should be "hello world" after second edition');
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('div.o_form_textarea').text(), 'hello world',
+ 'should be "hello world" after save');
+ form.destroy();
+ });
+
+ QUnit.test('text fields in edit mode have correct height', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.fields.foo.type = 'text';
+ this.data.partner.records[0].foo = "f\nu\nc\nk\nm\ni\nl\ng\nr\no\nm";
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="foo"/>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ var $field = form.$('div.o_list_text');
+
+ assert.strictEqual($field.outerHeight(), $field[0].scrollHeight,
+ "text field should not have a scroll bar");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ var $textarea = form.$('textarea:first');
+
+ // the difference is to take small calculation errors into account
+ assert.strictEqual($textarea.innerHeight(), $textarea[0].scrollHeight,
+ "textarea should not have a scroll bar");
+ form.destroy();
+ });
+
+
+ QUnit.test('text field rendering in list view', function (assert) {
+ assert.expect(1);
+
+ var data = {
+ foo: {
+ fields: {foo: {string: "F", type: "text"}},
+ records: [{id: 1, foo: "some text"}]
+ },
+ };
+ var list = createView({
+ View: ListView,
+ model: 'foo',
+ data: data,
+ arch: '<tree><field name="foo"/></tree>',
+ });
+
+ assert.strictEqual(list.$('tbody td.o_list_text:contains(some text)').length, 1,
+ "should have a td with the .o_list_text class");
+ list.destroy();
+ });
+
+ QUnit.test('field text in editable list view', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.fields.foo.type = 'text';
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree string="Phonecalls" editable="top">' +
+ '<field name="foo"/>' +
+ '</tree>',
+ });
+
+ list.$buttons.find('.o_list_button_add').click();
+
+ assert.strictEqual(list.$('textarea').first().get(0), document.activeElement,
+ "text area should have the focus");
+ list.destroy();
+ });
+
+ QUnit.test('field changes are correctly debounced in text fields', function (assert) {
+ var done = assert.async();
+ assert.expect(5);
+
+ this.data.partner.fields.foo.type = 'text';
+
+ var def = $.Deferred();
+ var nbNotifyChanges = 0;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="foo"/>' +
+ '</form>',
+ res_id: 2,
+ fieldDebounce: 3,
+ });
+
+ var _onFieldChanged = form._onFieldChanged;
+ form._onFieldChanged = function () {
+ _onFieldChanged.apply(form, arguments);
+ nbNotifyChanges++;
+ def.resolve();
+ };
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('textarea').val("1").trigger('input');
+ assert.strictEqual(nbNotifyChanges, 0,
+ "no event should have been triggered");
+ form.$('textarea').val("12").trigger('input');
+ assert.strictEqual(nbNotifyChanges, 0,
+ "no event should have been triggered");
+
+ return waitForChangeTriggered().then(function () {
+ assert.strictEqual(nbNotifyChanges, 1,
+ "one event should have been triggered");
+
+ // add something in the textarea, then focus another input
+ form.$('textarea').first().val("123").trigger('input');
+ form.$('textarea').first().change();
+ assert.strictEqual(nbNotifyChanges, 2,
+ "one event should have been triggered immediately");
+
+ return waitForChangeTriggered();
+ }).then(function () {
+ assert.strictEqual(nbNotifyChanges, 2,
+ "no extra event should have been triggered");
+
+ form.destroy();
+ done();
+ });
+
+ function waitForChangeTriggered() {
+ return def.then(function () {
+ def = $.Deferred();
+ return concurrency.delay(0);
+ });
+ }
+ });
+
+ QUnit.module('JournalDashboardGraph');
+
+ QUnit.test('graph dashboard widget is rendered correctly', function (assert) {
+ assert.expect(2);
+
+ _.extend(this.data.partner.fields, {
+ graph_data: { string: "Graph Data", type: "text" },
+ graph_type: {
+ string: "Graph Type",
+ type: "selection",
+ selection: [['line', 'Line'], ['bar', 'Bar']]
+ },
+ });
+ this.data.partner.records[0].graph_type = "bar";
+ this.data.partner.records[1].graph_type = "line";
+ var graph_values = [
+ {'value': 300, 'label': '5-11 Dec'},
+ {'value': 500, 'label': '12-18 Dec'},
+ {'value': 100, 'label': '19-25 Dec'},
+ ];
+ this.data.partner.records[0].graph_data = JSON.stringify([{
+ color: 'red',
+ title: 'Partner 0',
+ values: graph_values,
+ key: 'A key',
+ area: true,
+ }]);
+ this.data.partner.records[1].graph_data = JSON.stringify([{
+ color: 'blue',
+ title: 'Partner 1',
+ values: graph_values,
+ key: 'A key',
+ area: true,
+ }]);
+ var kanban = createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<kanban class="o_kanban_test">' +
+ '<field name="graph_type"/>' +
+ '<templates><t t-name="kanban-box">' +
+ '<div>' +
+ '<field name="graph_data" t-att-graph_type="record.graph_type.raw_value" widget="dashboard_graph"/>' +
+ '</div>' +
+ '</t>' +
+ '</templates></kanban>',
+ domain: [['id', 'in', [1, 2]]],
+ });
+
+ // nvd3 seems to do a setTimeout(0) each time the addGraph function is
+ // called, which is done twice in this case as there are 2 records.
+ // for that reason, we need to do two setTimeout(0) as well here to ensure
+ // that both graphs are rendered before starting to check if the rendering
+ // is correct.
+ var done = assert.async();
+ return concurrency.delay(0).then(function () {
+ return concurrency.delay(0);
+ }).then(function () {
+ assert.ok(kanban.$('.o_kanban_record:first() .o_graph_barchart').length,
+ "graph of first record should be a barchart");
+ assert.ok(kanban.$('.o_kanban_record:nth(1) .o_graph_linechart').length,
+ "graph of second record should be a linechart");
+ kanban.destroy();
+ done();
+ });
+
+ });
+
+ QUnit.module('AceEditor');
+
+ QUnit.test('ace widget on text fields works', function (assert) {
+ assert.expect(3);
+ var done = assert.async();
+
+ assert.notOk('ace' in window, "the ace library should not be loaded");
+ this.data.partner.fields.foo.type = 'text';
+ testUtils.createAsyncView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="foo" widget="ace"/>' +
+ '</form>',
+ res_id: 1,
+ }).then(function (form) {
+ assert.ok('ace' in window, "the ace library should now be loaded");
+ assert.ok(form.$('div.ace_content').length, "should have rendered something with ace editor");
+ delete window.require;
+ delete window.ace;
+ form.destroy();
+ done();
+ });
+ });
+
+ QUnit.module('HandleWidget');
+
+ QUnit.test('handle widget', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.records[0].p = [2, 4];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="bottom">' +
+ '<field name="sequence" widget="handle"/>' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('td span.o_row_handle').text(), "",
+ "handle should not have any content");
+
+ assert.notOk(form.$('td span.o_row_handle').is(':visible'),
+ "handle should be invisible in readonly mode");
+
+ assert.strictEqual(form.$('span.o_row_handle').length, 2, "should have 2 handles");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(form.$('td:first').hasClass('o_readonly'),
+ "column should be displayed as readonly");
+
+ assert.ok(form.$('td:first').hasClass('o_handle_cell'),
+ "column widget should be displayed in css class");
+
+ assert.ok(form.$('td span.o_row_handle').is(':visible'),
+ "handle should be visible in readonly mode");
+
+ form.$('td').eq(1).click();
+ assert.strictEqual(form.$('td:first span.o_row_handle').length, 1,
+ "content of the cell should have been replaced");
+ form.destroy();
+ });
+
+
+ QUnit.module('FieldDate');
+
+ QUnit.test('date field is empty if no date is set', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners"><field name="date"/></form>',
+ res_id: 4,
+ });
+ var $span = form.$('span.o_form_field');
+ assert.strictEqual($span.length, 1, "should have one span in the form view");
+ assert.strictEqual($span.text(), "", "and it should be empty");
+ form.destroy();
+ });
+
+ QUnit.test('date field in form view', function (assert) {
+ assert.expect(7);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners"><field name="date"/></form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/write') {
+ assert.strictEqual(args.args[1].date, '2017-02-22', 'the correct value should be saved');
+ }
+ return this._super.apply(this, arguments);
+ },
+ translateParameters: { // Avoid issues due to localization formats
+ date_format: '%m/%d/%Y',
+ }
+ });
+
+ assert.strictEqual(form.$('.o_form_field_date').text(), '02/03/2017',
+ 'the date should be correctly displayed in readonly');
+
+ // switch to edit mode
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017',
+ 'the date should be correct in edit mode');
+
+ // click on the input and select another value
+ form.$('.o_datepicker_input').click();
+ assert.ok(form.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
+ form.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection
+ form.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection
+ form.$('.bootstrap-datetimepicker-widget .year:contains(2017)').click();
+ form.$('.bootstrap-datetimepicker-widget .month').eq(1).click(); // February
+ form.$('.day:contains(22)').click(); // select the 22 February
+ assert.ok(!form.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be closed');
+ assert.strictEqual(form.$('.o_datepicker_input').val(), '02/22/2017',
+ 'the selected date should be displayed in the input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field_date').text(), '02/22/2017',
+ 'the selected date should be displayed after saving');
+ form.destroy();
+ });
+
+ QUnit.test('date field in editable list view', function (assert) {
+ assert.expect(8);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="date"/>' +
+ '</tree>',
+ translateParameters: { // Avoid issues due to localization formats
+ date_format: '%m/%d/%Y',
+ }
+ });
+
+ var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first();
+ assert.strictEqual($cell.text(), '02/03/2017',
+ 'the date should be displayed correctly in readonly');
+ $cell.click();
+
+ assert.strictEqual(list.$('input.o_datepicker_input').length, 1,
+ "the view should have a date input for editable mode");
+
+ assert.strictEqual(list.$('input.o_datepicker_input').get(0), document.activeElement,
+ "date input should have the focus");
+
+ assert.strictEqual(list.$('input.o_datepicker_input').val(), '02/03/2017',
+ 'the date should be correct in edit mode');
+
+ // click on the input and select another value
+ list.$('input.o_datepicker_input').click();
+ assert.ok(list.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
+ list.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection
+ list.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection
+ list.$('.bootstrap-datetimepicker-widget .year:contains(2017)').click();
+ list.$('.bootstrap-datetimepicker-widget .month').eq(1).click(); // February
+ list.$('.day:contains(22)').click(); // select the 22 February
+ assert.ok(!list.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be closed');
+ assert.strictEqual(list.$('.o_datepicker_input').val(), '02/22/2017',
+ 'the selected date should be displayed in the input');
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('tr.o_data_row td:not(.o_list_record_selector)').text(), '02/22/2017',
+ 'the selected date should be displayed after saving');
+
+ list.destroy();
+ });
+
+
+ QUnit.module('FieldDatetime');
+
+ QUnit.test('datetime field in form view', function(assert) {
+ assert.expect(6);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners"><field name="datetime"/></form>',
+ res_id: 1,
+ translateParameters: { // Avoid issues due to localization formats
+ date_format: '%m/%d/%Y',
+ time_format: '%H:%M:%S',
+ }
+ });
+
+ var expectedDateString = "02/08/2017 10:00:00";
+ assert.strictEqual(form.$('.o_form_field_date').text(), expectedDateString,
+ 'the datetime should be correctly displayed in readonly');
+
+ // switch to edit mode
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_datepicker_input').val(), expectedDateString,
+ 'the datetime should be correct in edit mode');
+ // click on the input and select 22 February at 8:23:33
+ form.$('.o_datepicker_input').click();
+ assert.ok(form.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
+ form.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection
+ form.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection
+ form.$('.bootstrap-datetimepicker-widget .year:contains(2017)').click();
+ form.$('.bootstrap-datetimepicker-widget .month').eq(3).click(); // April
+ form.$('.bootstrap-datetimepicker-widget .day:contains(22)').click();
+ form.$('.bootstrap-datetimepicker-widget .fa-clock-o').click();
+ form.$('.bootstrap-datetimepicker-widget .timepicker-hour').click();
+ form.$('.bootstrap-datetimepicker-widget .hour:contains(08)').click();
+ form.$('.bootstrap-datetimepicker-widget .timepicker-minute').click();
+ form.$('.bootstrap-datetimepicker-widget .minute:contains(25)').click();
+ form.$('.bootstrap-datetimepicker-widget .timepicker-second').click();
+ form.$('.bootstrap-datetimepicker-widget .second:contains(35)').click();
+ form.$('.bootstrap-datetimepicker-widget .fa-times').click(); // close
+ assert.ok(!form.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be closed');
+
+ var newExpectedDateString = "04/22/2017 08:25:35";
+ assert.strictEqual(form.$('.o_datepicker_input').val(), newExpectedDateString,
+ 'the selected date should be displayed in the input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field_date').text(), newExpectedDateString,
+ 'the selected date should be displayed after saving');
+
+ form.destroy();
+ });
+
+ QUnit.test('datetime field in editable list view', function (assert) {
+ assert.expect(8);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="datetime"/>' +
+ '</tree>',
+ translateParameters: { // Avoid issues due to localization formats
+ date_format: '%m/%d/%Y',
+ time_format: '%H:%M:%S',
+ }
+ });
+
+ var expectedDateString = "02/08/2017 10:00:00";
+ var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first();
+ assert.strictEqual($cell.text(), expectedDateString,
+ 'the datetime should be correctly displayed in readonly');
+
+ // switch to edit mode
+ $cell.click();
+ assert.strictEqual(list.$('input.o_datepicker_input').length, 1,
+ "the view should have a date input for editable mode");
+
+ assert.strictEqual(list.$('input.o_datepicker_input').get(0), document.activeElement,
+ "date input should have the focus");
+
+ assert.strictEqual(list.$('input.o_datepicker_input').val(), expectedDateString,
+ 'the date should be correct in edit mode');
+
+ // click on the input and select 22 February at 8:23:33
+ list.$('input.o_datepicker_input').click();
+ assert.ok(list.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
+ list.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection
+ list.$('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection
+ list.$('.bootstrap-datetimepicker-widget .year:contains(2017)').click();
+ list.$('.bootstrap-datetimepicker-widget .month').eq(3).click(); // April
+ list.$('.bootstrap-datetimepicker-widget .day:contains(22)').click();
+ list.$('.bootstrap-datetimepicker-widget .fa-clock-o').click();
+ list.$('.bootstrap-datetimepicker-widget .timepicker-hour').click();
+ list.$('.bootstrap-datetimepicker-widget .hour:contains(08)').click();
+ list.$('.bootstrap-datetimepicker-widget .timepicker-minute').click();
+ list.$('.bootstrap-datetimepicker-widget .minute:contains(25)').click();
+ list.$('.bootstrap-datetimepicker-widget .timepicker-second').click();
+ list.$('.bootstrap-datetimepicker-widget .second:contains(35)').click();
+ list.$('.bootstrap-datetimepicker-widget .fa-times').click(); // close
+ assert.ok(!list.$('.bootstrap-datetimepicker-widget').length, 'datepicker should be closed');
+
+ var newExpectedDateString = "04/22/2017 08:25:35";
+ assert.strictEqual(list.$('.o_datepicker_input').val(), newExpectedDateString,
+ 'the selected datetime should be displayed in the input');
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('tr.o_data_row td:not(.o_list_record_selector)').text(), newExpectedDateString,
+ 'the selected datetime should be displayed after saving');
+
+ list.destroy();
+ });
+
+
+ QUnit.module('FieldMonetary');
+
+ QUnit.test('monetary field in form view', function(assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux" widget="monetary"/>' +
+ '<field name="currency_id" invisible="1"/>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 5,
+ session: {
+ currencies: _.indexBy(this.data.currency.records, 'id'),
+ },
+ });
+
+ // Non-breaking space between the currency and the amount
+ assert.strictEqual(form.$('.o_form_field').first().text(), '$\u00a09.10',
+ 'The value should be displayed properly.');
+
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input.o_form_input').val(), '9.10',
+ 'The input should be rendered without the currency symbol.');
+ assert.strictEqual(form.$('input.o_form_input').parent().children().first().text(), '$',
+ 'The input should be preceded by a span containing the currency symbol.');
+
+ form.$('input.o_form_input').val('108.2458938598598').trigger('input');
+ assert.strictEqual(form.$('input.o_form_input').val(), '108.2458938598598',
+ 'The value should not be formated yet.');
+
+ form.$buttons.find('.o_form_button_save').click();
+ // Non-breaking space between the currency and the amount
+ assert.strictEqual(form.$('.o_form_field').first().text(), '$\u00a0108.25',
+ 'The new value should be rounded properly.');
+
+ form.destroy();
+ });
+
+ QUnit.test('monetary field with currency symbol after', function(assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux" widget="monetary"/>' +
+ '<field name="currency_id" invisible="1"/>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 2,
+ session: {
+ currencies: _.indexBy(this.data.currency.records, 'id'),
+ },
+ });
+
+ // Non-breaking space between the currency and the amount
+ assert.strictEqual(form.$('.o_form_field').first().text(), '0.00\u00a0€',
+ 'The value should be displayed properly.');
+
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input.o_form_input').val(), '0.00',
+ 'The input should be rendered without the currency symbol.');
+ assert.strictEqual(form.$('input.o_form_input').parent().children().eq(1).text(), '€',
+ 'The input should be followed by a span containing the currency symbol.');
+
+ form.$('input.o_form_input').val('108.2458938598598').trigger('input');
+ assert.strictEqual(form.$('input.o_form_input').val(), '108.2458938598598',
+ 'The value should not be formated yet.');
+
+ form.$buttons.find('.o_form_button_save').click();
+ // Non-breaking space between the currency and the amount
+ assert.strictEqual(form.$('.o_form_field').first().text(), '108.25\u00a0€',
+ 'The new value should be rounded properly.');
+
+ form.destroy();
+ });
+
+ QUnit.test('monetary field in editable list view', function (assert) {
+ assert.expect(9);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="qux" widget="monetary"/>' +
+ '<field name="currency_id" invisible="1"/>' +
+ '</tree>',
+ session: {
+ currencies: _.indexBy(this.data.currency.records, 'id'),
+ },
+ });
+
+ var dollarValues = list.$('td').filter(function() {return _.str.include($(this).text(), '$')});
+ assert.strictEqual(dollarValues.length, 1,
+ 'Only one line has dollar as a currency.');
+
+ var euroValues = list.$('td').filter(function() {return _.str.include($(this).text(), '€')});
+ assert.strictEqual(euroValues.length, 1,
+ 'One one line has euro as a currency.');
+
+ var zeroValues = list.$('td').filter(function() {return _.str.include($(this).text(), '0.00')});
+ assert.strictEqual(zeroValues.length, 2,
+ 'Unset float values should be rendered as zeros.');
+
+ // switch to edit mode
+ var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector):contains($)');
+ $cell.click();
+
+ assert.strictEqual($cell.children().length, 1,
+ 'The cell td should only contain the special div of monetary widget.');
+ assert.strictEqual(list.$('input.o_form_input').length, 1,
+ 'The view should have 1 input for editable monetary float.');
+ assert.strictEqual(list.$('input.o_form_input').val(), '9.10',
+ 'The input should be rendered without the currency symbol.');
+ assert.strictEqual(list.$('input.o_form_input').parent().children().first().text(), '$',
+ 'The input should be preceded by a span containing the currency symbol.');
+
+ list.$('input.o_form_input').val('108.2458938598598').trigger('input');
+ assert.strictEqual(list.$('input.o_form_input').val(), '108.2458938598598',
+ 'The typed value should be correctly displayed.');
+
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('tr.o_data_row td:not(.o_list_record_selector):contains($)').text(), '$\u00a0108.25',
+ 'The new value should be rounded properly.');
+
+ list.destroy();
+ });
+
+ QUnit.test('monetary field with real monetary field in model', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.fields.qux.type = "monetary";
+
+ this.data.partner.onchanges = {
+ bar: function (obj) {
+ obj.qux = obj.bar ? 100 : obj.qux;
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux"/>' +
+ '<field name="currency_id"/>' +
+ '<field name="bar"/>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 5,
+ session: {
+ currencies: _.indexBy(this.data.currency.records, 'id'),
+ },
+ });
+
+ assert.strictEqual(form.$('.o_form_field_monetary').html(), "$&nbsp;9.10",
+ "readonly value should contain the currency");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_form_field_monetary > input').val(), "9.10",
+ "input value in edition should only contain the value, without the currency");
+
+ form.$('input[type="checkbox"]').click(); // Change the field on which the monetary depends
+ assert.strictEqual(form.$('.o_form_field_monetary > input').length, 1,
+ "After the onchange, the monetary <input/> should not have been duplicated");
+
+ var $dropdown = form.$('.o_form_field_many2one input').autocomplete('widget');
+ form.$('.o_form_field_many2one input').click();
+ $dropdown.find('li:not(.o_m2o_dropdown_option):last').mouseenter().click();
+ assert.strictEqual(form.$('.o_form_field_monetary > span').html(), "€",
+ "After currency change, the monetary field currency should have been updated");
+
+ form.destroy();
+ });
+
+ QUnit.module('FieldInteger');
+
+ QUnit.test('integer field when unset', function(assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners"><field name="int_field"/></form>',
+ res_id: 4,
+ });
+
+ assert.ok(form.$('.o_form_field').hasClass('o_form_field_empty'),
+ 'Non-set integer field should be recognized as unset.');
+
+ form.destroy();
+ });
+
+ QUnit.test('integer field in form view', function(assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners"><field name="int_field"/></form>',
+ res_id: 2,
+ });
+
+ assert.ok(!form.$('.o_form_field').hasClass('o_form_field_empty'),
+ 'Integer field should be considered set for value 0.');
+
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input.o_form_input').val(), '0',
+ 'The value should be rendered correctly in edit mode.')
+
+ form.$('input.o_form_input').val('-18').trigger('input');
+ assert.strictEqual(form.$('input.o_form_input').val(), '-18',
+ 'The value should be correctly displayed in the input.')
+
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field').text(), '-18',
+ 'The new value should be saved and displayed properly.')
+
+ form.destroy();
+ });
+
+ QUnit.test('integer field in editable list view', function (assert) {
+ assert.expect(4);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="int_field"/>' +
+ '</tree>',
+ });
+
+ var zeroValues = list.$('td').filter(function() {return $(this).text() === '0'});
+ assert.strictEqual(zeroValues.length, 1,
+ 'Unset integer values should not be rendered as zeros.');
+
+ // switch to edit mode
+ var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first();
+ $cell.click();
+
+ assert.strictEqual(list.$('input.o_form_input').length, 1,
+ 'The view should have 1 input for editable integer.')
+
+ list.$('input.o_form_input').val('-28').trigger('input');
+ assert.strictEqual(list.$('input.o_form_input').val(), '-28',
+ 'The value should be displayed properly in the input.')
+
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('td:not(.o_list_record_selector)').first().text(), '-28',
+ 'The new value should be saved and displayed properly.')
+
+ list.destroy();
+ });
+
+
+ QUnit.module('FieldFloatTime');
+
+ QUnit.test('float_time field in form view', function(assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux" widget="float_time"/>' +
+ '</sheet>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/write') {
+ // 48 / 60 = 0.8
+ assert.strictEqual(args.args[1].qux, -11.8, 'the correct float value should be saved');
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 5,
+ });
+
+ // 9 + 0.1 * 60 = 9.06
+ assert.strictEqual(form.$('.o_form_field').first().text(), '09:06',
+ 'The formatted time value should be displayed properly.')
+
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input.o_form_input').val(), '09:06',
+ 'The value should be rendered correctly in the input.')
+
+ form.$('input.o_form_input').val('-11:48').trigger('input');
+ assert.strictEqual(form.$('input.o_form_input').val(), '-11:48',
+ 'The new value should be displayed properly in the input.')
+
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field').first().text(), '-11:48',
+ 'The new value should be saved and displayed properly.')
+
+ form.destroy();
+ });
+
+
+ QUnit.module('PhoneWidget');
+
+ QUnit.test('phone field in form view on extra small screens', function (assert) {
+ assert.expect(7);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo" widget="phone"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ config: {
+ device: {
+ size_class: 0, // Screen XS
+ SIZES: { XS: 0, SM: 1, MD: 2, LG: 3 },
+ }
+ },
+ });
+
+ var $phoneLink = form.$('a.o_form_uri.o_form_field.o_text_overflow');
+ assert.strictEqual($phoneLink.length, 1,
+ "should have a anchor with correct classes");
+ assert.strictEqual($phoneLink.text(), 'y\u00ADop',
+ "value should be displayed properly as text with the skype obfuscation");
+ assert.strictEqual($phoneLink.attr('href'), 'tel:yop',
+ "should have proper tel prefix");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').length, 1,
+ "should have an input for the phone field");
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').val(), 'yop',
+ "input should contain field value in edit mode");
+
+ // change value in edit mode
+ form.$('input[type="text"].o_form_input.o_form_field').val('new').trigger('input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ $phoneLink = form.$('a.o_form_uri.o_form_field.o_text_overflow');
+ assert.strictEqual($phoneLink.text(), 'n\u00ADew',
+ "new value should be displayed properly as text with the skype obfuscation");
+ assert.strictEqual($phoneLink.attr('href'), 'tel:new',
+ "should still have proper tel prefix");
+
+ form.destroy();
+ });
+
+ QUnit.test('phone field in editable list view on extra small screens', function(assert) {
+ assert.expect(10);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="foo" widget="phone"/></tree>',
+ config: {
+ device: {
+ size_class: 0, // Screen XS
+ SIZES: { XS: 0, SM: 1, MD: 2, LG: 3 },
+ }
+ },
+ });
+
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5,
+ "should have 5 cells");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'y\u00ADop',
+ "value should be displayed properly as text with the skype obfuscation");
+
+ var $phoneLink = list.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ assert.strictEqual($phoneLink.length, 5,
+ "should have anchors with correct classes");
+ assert.strictEqual($phoneLink.first().attr('href'), 'tel:yop',
+ "should have proper tel prefix");
+
+ // Edit a line and check the result
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+ assert.ok($cell.hasClass('o_edit_mode'), 'should be set as edit mode');
+ assert.strictEqual($cell.find('input').val(), 'yop',
+ 'should have the corect value in internal input');
+ $cell.find('input').val('new').trigger('input');;
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ assert.ok(!$cell.hasClass('o_edit_mode'), 'should not be in edit mode anymore');
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'n\u00ADew',
+ "value should be properly updated");
+ $phoneLink = list.$('a.o_form_uri.o_field_widget.o_text_overflow');
+ assert.strictEqual($phoneLink.length, 5,
+ "should still have anchors with correct classes");
+ assert.strictEqual($phoneLink.first().attr('href'), 'tel:new',
+ "should still have proper tel prefix");
+
+ list.destroy();
+ });
+
+ QUnit.test('phone field in form view on normal screens', function (assert) {
+ // The behavior of this widget is completely altered by crm_voip so this
+ // test is irrelevant and fails if crm_voip is installed. The enterprise
+ // module is responsible for testing its own behavior in its own tests.
+ if ('crm.voip' in odoo.__DEBUG__.services) {
+ assert.expect(0);
+ return;
+ }
+
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo" widget="phone"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ config: {
+ device: {
+ size_class: 1, // Screen SM
+ SIZES: { XS: 0, SM: 1, MD: 2, LG: 3 },
+ }
+ },
+ });
+
+ var $phone = form.$('span.o_form_field.o_text_overflow:not(.o_form_uri)');
+ assert.strictEqual($phone.length, 1,
+ "should have a simple span rather than a link");
+ assert.strictEqual($phone.text(), 'yop',
+ "value should be displayed properly as text without skype obfuscation");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').length, 1,
+ "should have an input for the phone field");
+ assert.strictEqual(form.$('input[type="text"].o_form_input.o_form_field').val(), 'yop',
+ "input should contain field value in edit mode");
+
+ // change value in edit mode
+ form.$('input[type="text"].o_form_input.o_form_field').val('new').trigger('input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('span.o_form_field.o_text_overflow:not(.o_form_uri)').text(), 'new',
+ "new value should be displayed properly as text without skype obfuscation");
+
+ form.destroy();
+ });
+
+ QUnit.test('phone field in editable list view on normal screens', function(assert) {
+ // The behavior of this widget is completely altered by crm_voip so this
+ // test is irrelevant and fails if crm_voip is installed. The enterprise
+ // module is responsible for testing its own behavior in its own tests.
+ if ('crm.voip' in odoo.__DEBUG__.services) {
+ assert.expect(0);
+ return;
+ }
+
+ assert.expect(8);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="foo" widget="phone"/></tree>',
+ config: {
+ device: {
+ size_class: 1, // Screen SM
+ SIZES: { XS: 0, SM: 1, MD: 2, LG: 3 },
+ }
+ },
+ });
+
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5,
+ "should have 5 cells");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop',
+ "value should be displayed properly as text without skype obfuscation");
+
+ assert.strictEqual(list.$('span.o_field_widget.o_text_overflow:not(.o_form_uri)').length, 5,
+ "should have spans with correct classes");
+
+ // Edit a line and check the result
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+ assert.ok($cell.hasClass('o_edit_mode'), 'should be set as edit mode');
+ assert.strictEqual($cell.find('input').val(), 'yop',
+ 'should have the corect value in internal input');
+ $cell.find('input').val('new').trigger('input');;
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ assert.ok(!$cell.hasClass('o_edit_mode'), 'should not be in edit mode anymore');
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new',
+ "value should be properly updated");
+ assert.strictEqual(list.$('span.o_field_widget.o_text_overflow:not(.o_form_uri)').length, 5,
+ "should still have spans with correct classes");
+
+ list.destroy();
+ });
+
+
+ QUnit.module('PriorityWidget');
+
+ QUnit.test('priority widget when not set', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="selection" widget="priority"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ assert.strictEqual(form.$('.o_form_field.o_priority:not(.o_form_field_empty)').length, 1,
+ "widget should be considered set, even though there is no value for this field");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should have two stars for representing each possible value: no star, one star and two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 0,
+ "should have no full star since there is no value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 2,
+ "should have two empty stars since there is no value");
+
+ form.destroy();
+ });
+
+ QUnit.test('priority widget in form view', function (assert) {
+ assert.expect(22);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="selection" widget="priority"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_form_field.o_priority:not(.o_form_field_empty)').length, 1,
+ "widget should be considered set");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should have two stars for representing each possible value: no star, one star and two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 1,
+ "should have one full star since the value is the second value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 1,
+ "should have one empty star since the value is the second value");
+
+ // hover last star
+ form.$('.o_form_field.o_priority a.o_priority_star.fa-star-o').last().trigger('mouseover');
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should have two stars for representing each possible value: no star, one star and two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 2,
+ "should temporary have two full stars since we are hovering the third value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 0,
+ "should temporary have no empty star since we are hovering the third value");
+
+ // Here we should test with mouseout, but currently the effect associated with it
+ // occurs in a setTimeout after 200ms so it's not trivial to test it here.
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 1,
+ "should still have one full star since the value is the second value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 1,
+ "should still have one empty star since the value is the second value");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 1,
+ "should still have one full star since the value is the second value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 1,
+ "should still have one empty star since the value is the second value");
+
+ // switch to edit mode to check that the new value was properly written
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 1,
+ "should still have one full star since the value is the second value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 1,
+ "should still have one empty star since the value is the second value");
+
+ // click on the second star in edit mode
+ form.$('.o_form_field.o_priority a.o_priority_star.fa-star-o').last().click();
+
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 2,
+ "should now have two full stars since the value is the third value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 0,
+ "should now have no empty star since the value is the third value");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star').length, 2,
+ "should now have two full stars since the value is the third value");
+ assert.strictEqual(form.$('.o_form_field.o_priority').find('a.o_priority_star.fa-star-o').length, 0,
+ "should now have no empty star since the value is the third value");
+
+ form.destroy();
+ });
+
+ QUnit.test('priority widget in editable list view', function(assert) {
+ assert.expect(25);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="selection" widget="priority"/></tree>',
+ });
+
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority:not(.o_form_field_empty)').length, 1,
+ "widget should be considered set");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2,
+ "should have two stars for representing each possible value: no star, one star and two stars");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star').length, 1,
+ "should have one full star since the value is the second value");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star-o').length, 1,
+ "should have one empty star since the value is the second value");
+
+ // Here we should test with mouseout, but currently the effect associated with it
+ // occurs in a setTimeout after 200ms so it's not trivial to test it here.
+
+ // switch to edit mode and check the result
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2,
+ "should have two stars for representing each possible value: no star, one star and two stars");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star').length, 1,
+ "should have one full star since the value is the second value");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star-o').length, 1,
+ "should have one empty star since the value is the second value");
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2,
+ "should have two stars for representing each possible value: no star, one star and two stars");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star').length, 1,
+ "should have one full star since the value is the second value");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star-o').length, 1,
+ "should have one empty star since the value is the second value");
+
+ // hover last star
+ list.$('.o_data_row .o_priority a.o_priority_star.fa-star-o').first().trigger('mouseenter');
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2,
+ "should have two stars for representing each possible value: no star, one star and two stars");
+ assert.strictEqual(list.$('.o_data_row').first().find('a.o_priority_star.fa-star').length, 2,
+ "should temporary have two full stars since we are hovering the third value");
+ assert.strictEqual(list.$('.o_data_row').first().find('a.o_priority_star.fa-star-o').length, 0,
+ "should temporary have no empty star since we are hovering the third value");
+
+ // click on the first star in readonly mode
+ list.$('.o_priority a.o_priority_star.fa-star').first().click();
+
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star').length, 0,
+ "should now have no full star since the value is the first value");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star-o').length, 2,
+ "should now have two empty stars since the value is the first value");
+
+ // re-enter edit mode to force re-rendering the widget to check if the value was correctly saved
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star').length, 0,
+ "should now only have no full star since the value is the first value");
+ assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star-o').length, 2,
+ "should now have two empty stars since the value is the first value");
+
+ // Click on second star in edit mode
+ list.$('.o_priority a.o_priority_star.fa-star-o').last().click();
+
+ assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star.fa-star').length, 2,
+ "should now have two full stars since the value is the third value");
+ assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star.fa-star-o').length, 0,
+ "should now have no empty star since the value is the third value");
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star').length, 2,
+ "should still have two stars");
+ assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star.fa-star').length, 2,
+ "should now have two full stars since the value is the third value");
+ assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star.fa-star-o').length, 0,
+ "should now have no empty star since the value is the third value");
+
+ list.destroy();
+ });
+
+
+ QUnit.module('StateSelection Widget');
+
+ QUnit.test('state_selection widget in form view', function (assert) {
+ assert.expect(21);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="selection" widget="state_selection"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 1,
+ "should have one red status since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 0,
+ "should not have one green status since selection is the second, blocked state");
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown");
+
+ // Click on the status button to make the dropdown appear
+ form.$('.o_form_field.o_selection .o_status').first().click();
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 1,
+ "there should be a dropdown");
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible li').length, 2,
+ "there should be two options in the dropdown");
+
+ // Click on the first option, "Normal"
+ form.$('ul.dropdown-menu.state:visible li').first().click();
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown anymore");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0,
+ "should not have one red status since selection is the first, normal state");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 0,
+ "should not have one green status since selection is the first, normal state");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status').length, 1,
+ "should have one grey status since selection is the first, normal state");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should still not be a dropdown");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0,
+ "should still not have one red status since selection is the first, normal state");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 0,
+ "should still not have one green status since selection is the first, normal state");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status').length, 1,
+ "should still have one grey status since selection is the first, normal state");
+
+ // Click on the status button to make the dropdown appear
+ form.$('.o_form_field.o_selection .o_status').first().click();
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 1,
+ "there should be a dropdown");
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible li').length, 2,
+ "there should be two options in the dropdown");
+
+ // Click on the last option, "Done"
+ form.$('ul.dropdown-menu.state:visible li').last().click();
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown anymore");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0,
+ "should not have one red status since selection is the third, done state");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 1,
+ "should have one green status since selection is the third, done state");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should still not be a dropdown anymore");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0,
+ "should still not have one red status since selection is the third, done state");
+ assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 1,
+ "should still have one green status since selection is the third, done state");
+
+ form.destroy();
+ });
+
+ QUnit.test('state_selection widget in editable list view', function(assert) {
+ assert.expect(34);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="selection" widget="state_selection"/>' +
+ '</tree>',
+ });
+
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5,
+ "should have five status selection widgets");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 1,
+ "should have one red status");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 1,
+ "should have one green status");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown");
+
+ // Click on the status button to make the dropdown appear
+ var $cell = list.$('tbody td.o_state_selection_cell').first();
+ list.$('.o_state_selection_cell .o_selection > a span.o_status').first().click();
+ assert.ok(!$cell.hasClass('o_edit_mode'),
+ 'should not be in edit mode since we clicked on the widget and not the cell itself');
+ assert.ok(!$cell.siblings().hasClass('o_edit_mode'),
+ 'siblings should not be in edit mode since we clicked on the widget and not the cell itself');
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 1,
+ "there should be a dropdown");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible li').length, 2,
+ "there should be two options in the dropdown");
+
+ // Click on the first option, "Normal"
+ list.$('ul.dropdown-menu.state:visible li').first().click();
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5,
+ "should still have five status selection widgets");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0,
+ "should now have no red status");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 1,
+ "should still have one green status");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown");
+
+ // switch to edit mode and check the result
+ $cell = list.$('tbody td.o_state_selection_cell').first();
+ $cell.click();
+ assert.ok(!$cell.hasClass('o_edit_mode'),
+ 'should still not be in edit mode since widget is readonly');
+ assert.ok($cell.siblings().hasClass('o_edit_mode'),
+ 'siblings should be in edit mode since we clicked on the cell and not the widget itself');
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5,
+ "should still have five status selection widgets");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0,
+ "should now have no red status");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 1,
+ "should still have one green status");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown");
+
+ // Click on the status button to make the dropdown appear
+ list.$('.o_state_selection_cell .o_selection > a span.o_status').first().click();
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 1,
+ "there should be a dropdown");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible li').length, 2,
+ "there should be two options in the dropdown");
+
+ // Click on another row
+ var $firstCell = list.$('tbody td.o_state_selection_cell').first();
+ var $lastCell = list.$('tbody td.o_state_selection_cell').last();
+ $lastCell.click();
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown anymore");
+ assert.ok(!$firstCell.siblings().hasClass('o_edit_mode'),
+ 'first row should not be in edit mode anymore');
+ assert.ok($lastCell.siblings().hasClass('o_edit_mode'),
+ 'last row should be in edit mode');
+
+ // Click on the last status button to make the dropdown appear
+ list.$('.o_state_selection_cell .o_selection > a span.o_status').last().click();
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 1,
+ "there should be a dropdown");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible li').length, 2,
+ "there should be two options in the dropdown");
+
+ // Click on the last option, "Done"
+ list.$('ul.dropdown-menu.state:visible li').last().click();
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown anymore");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5,
+ "should still have five status selection widgets");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0,
+ "should still have no red status");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 2,
+ "should now have two green status");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown");
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5,
+ "should have five status selection widgets");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0,
+ "should have no red status");
+ assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 2,
+ "should have two green status");
+ assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0,
+ "there should not be a dropdown");
+
+ list.destroy();
+ });
+
+
+ QUnit.module('LabelSelectionWidget');
+
+ QUnit.test('label_selection widget in form view', function (assert) {
+ assert.expect(12);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="selection" widget="label_selection" ' +
+ ' options="{\'classes\': {\'normal\': \'default\', \'blocked\': \'warning\',\'done\': \'success\'}}"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_form_field.label.label-warning').length, 1,
+ "should have a warning status label since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-default').length, 0,
+ "should not have a default status since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-success').length, 0,
+ "should not have a success status since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-warning').text(), 'Blocked',
+ "the label should say 'Blocked' since this is the label value for that state");
+
+ // // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_form_field.label.label-warning').length, 1,
+ "should have a warning status label since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-default').length, 0,
+ "should not have a default status since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-success').length, 0,
+ "should not have a success status since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-warning').text(), 'Blocked',
+ "the label should say 'Blocked' since this is the label value for that state");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field.label.label-warning').length, 1,
+ "should have a warning status label since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-default').length, 0,
+ "should not have a default status since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-success').length, 0,
+ "should not have a success status since selection is the second, blocked state");
+ assert.strictEqual(form.$('.o_form_field.label.label-warning').text(), 'Blocked',
+ "the label should say 'Blocked' since this is the label value for that state");
+
+ form.destroy();
+ });
+
+ QUnit.test('label_selection widget in editable list view', function(assert) {
+ assert.expect(21);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="selection" widget="label_selection"' +
+ ' options="{\'classes\': {\'normal\': \'default\', \'blocked\': \'warning\',\'done\': \'success\'}}"/>' +
+ '</tree>',
+ });
+
+ assert.strictEqual(list.$('.o_field_widget.label:not(:empty)').length, 3,
+ "should have three visible status labels");
+ assert.strictEqual(list.$('.o_field_widget.label.label-warning').length, 1,
+ "should have one warning status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-warning').text(), 'Blocked',
+ "the warning label should read 'Blocked'");
+ assert.strictEqual(list.$('.o_field_widget.label.label-default').length, 1,
+ "should have one default status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-default').text(), 'Normal',
+ "the default label should read 'Normal'");
+ assert.strictEqual(list.$('.o_field_widget.label.label-success').length, 1,
+ "should have one success status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-success').text(), 'Done',
+ "the success label should read 'Done'");
+
+ // switch to edit mode and check the result
+ list.$('tbody td:not(.o_list_record_selector)').first().click();
+ assert.strictEqual(list.$('.o_field_widget.label:not(:empty)').length, 3,
+ "should have three visible status labels");
+ assert.strictEqual(list.$('.o_field_widget.label.label-warning').length, 1,
+ "should have one warning status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-warning').text(), 'Blocked',
+ "the warning label should read 'Blocked'");
+ assert.strictEqual(list.$('.o_field_widget.label.label-default').length, 1,
+ "should have one default status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-default').text(), 'Normal',
+ "the default label should read 'Normal'");
+ assert.strictEqual(list.$('.o_field_widget.label.label-success').length, 1,
+ "should have one success status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-success').text(), 'Done',
+ "the success label should read 'Done'");
+
+ // save and check the result
+ list.$buttons.find('.o_list_button_save').click();
+ assert.strictEqual(list.$('.o_field_widget.label:not(:empty)').length, 3,
+ "should have three visible status labels");
+ assert.strictEqual(list.$('.o_field_widget.label.label-warning').length, 1,
+ "should have one warning status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-warning').text(), 'Blocked',
+ "the warning label should read 'Blocked'");
+ assert.strictEqual(list.$('.o_field_widget.label.label-default').length, 1,
+ "should have one default status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-default').text(), 'Normal',
+ "the default label should read 'Normal'");
+ assert.strictEqual(list.$('.o_field_widget.label.label-success').length, 1,
+ "should have one success status label");
+ assert.strictEqual(list.$('.o_field_widget.label.label-success').text(), 'Done',
+ "the success label should read 'Done'");
+
+ list.destroy();
+ });
+
+
+ QUnit.module('StatInfo');
+
+ QUnit.test('statinfo widget in form view', function (assert) {
+ assert.expect(9);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<div class="oe_button_box" name="button_box">' +
+ '<button class="oe_stat_button" name="items" type="object" icon="fa-gear">' +
+ '<field name="int_field" widget="statinfo"/>' +
+ '</button>' +
+ '</div>' +
+ '<group>' +
+ '<field name="foo"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ 'int_field', "should have 'int_field' as text");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should still have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should still have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ 'int_field', "should have 'int_field' as text");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ 'int_field', "should have 'int_field' as text");
+
+ form.destroy();
+ });
+
+ QUnit.test('statinfo widget in form view with specific label_field', function (assert) {
+ assert.expect(9);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<div class="oe_button_box" name="button_box">' +
+ '<button class="oe_stat_button" name="items" type="object" icon="fa-gear">' +
+ '<field string="Useful stat button" name="int_field" widget="statinfo" ' +
+ 'options="{\'label_field\': \'foo\'}"/>' +
+ '</button>' +
+ '</div>' +
+ '<group>' +
+ '<field name="foo" invisible="1"/>' +
+ '<field name="bar"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ 'yop', "should have 'yop' as text, since it is the value of field foo");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should still have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should still have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ 'yop', "should have 'yop' as text, since it is the value of field foo");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ 'yop', "should have 'yop' as text, since it is the value of field foo");
+
+ form.destroy();
+ });
+
+ QUnit.test('statinfo widget in form view with no label', function (assert) {
+ assert.expect(9);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<div class="oe_button_box" name="button_box">' +
+ '<button class="oe_stat_button" name="items" type="object" icon="fa-gear">' +
+ '<field string="Useful stat button" name="int_field" widget="statinfo" ' +
+ 'options="{\'nolabel\': \'true\'}"/>' +
+ '</button>' +
+ '</div>' +
+ '<group>' +
+ '<field name="foo" invisible="1"/>' +
+ '<field name="bar"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ '', "should not have any label");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should still have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should still have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ '', "should not have any label");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info').length, 1,
+ "should have one stat button");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_value').text(),
+ '10', "should have 10 as value");
+ assert.strictEqual(form.$('.oe_stat_button .o_form_field.o_stat_info .o_stat_text').text(),
+ '', "should not have any label");
+
+ form.destroy();
+ });
+
+
+ QUnit.module('PercentPie');
+
+ QUnit.test('percentpie widget in form view with value < 50%', function (assert) {
+ assert.expect(12);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="int_field" widget="percentpie"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie').length, 1,
+ "should have a pie chart");
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_pie_value').text(),
+ '10%', "should have 10% as pie value since int_field=10");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').first().attr('style'),
+ 'transform: rotate(180deg);'), "left mask should be covering the whole left side of the pie");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').last().attr('style'),
+ 'transform: rotate(36deg);'), "right mask should be rotated from 360*(10/100) = 36 degrees");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie').length, 1,
+ "should have a pie chart");
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_pie_value').text(),
+ '10%', "should have 10% as pie value since int_field=10");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').first().attr('style'),
+ 'transform: rotate(180deg);'), "left mask should be covering the whole left side of the pie");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').last().attr('style'),
+ 'transform: rotate(36deg);'), "right mask should be rotated from 360*(10/100) = 36 degrees");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie').length, 1,
+ "should have a pie chart");
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_pie_value').text(),
+ '10%', "should have 10% as pie value since int_field=10");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').first().attr('style'),
+ 'transform: rotate(180deg);'), "left mask should be covering the whole left side of the pie");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').last().attr('style'),
+ 'transform: rotate(36deg);'), "right mask should be rotated from 360*(10/100) = 36 degrees");
+
+ form.destroy();
+ });
+
+ QUnit.test('percentpie widget in form view with value > 50%', function (assert) {
+ assert.expect(12);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="int_field" widget="percentpie"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 3,
+ });
+
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie').length, 1,
+ "should have a pie chart");
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_pie_value').text(),
+ '80%', "should have 80% as pie value since int_field=80");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').first().attr('style'),
+ 'transform: rotate(288deg);'), "left mask should be rotated from 360*(80/100) = 288 degrees");
+ assert.ok(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').last().hasClass('o_full'),
+ "right mask should be hidden since the value > 50%");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie').length, 1,
+ "should have a pie chart");
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_pie_value').text(),
+ '80%', "should have 80% as pie value since int_field=80");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').first().attr('style'),
+ 'transform: rotate(288deg);'), "left mask should be rotated from 360*(80/100) = 288 degrees");
+ assert.ok(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').last().hasClass('o_full'),
+ "right mask should be hidden since the value > 50%");
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie').length, 1,
+ "should have a pie chart");
+ assert.strictEqual(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_pie_value').text(),
+ '80%', "should have 80% as pie value since int_field=80");
+ assert.ok(_.str.include(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').first().attr('style'),
+ 'transform: rotate(288deg);'), "left mask should be rotated from 360*(80/100) = 288 degrees");
+ assert.ok(form.$('.o_form_field_percent_pie.o_form_field .o_pie .o_mask').last().hasClass('o_full'),
+ "right mask should be hidden since the value > 50%");
+
+ form.destroy();
+ });
+
+ // TODO: This test would pass without any issue since all the classes and
+ // custom style attributes are correctly set on the widget in list
+ // view, but since the less itself for this widget currently only
+ // applies inside the form view, the widget is unusable. This test can
+ // be uncommented when we refactor the less files so that this widget
+ // stylesheet applies in both form and list view.
+ // QUnit.test('percentpie widget in editable list view', function(assert) {
+ // assert.expect(10);
+ //
+ // var list = createView({
+ // View: ListView,
+ // model: 'partner',
+ // data: this.data,
+ // arch: '<tree editable="bottom">' +
+ // '<field name="foo"/>' +
+ // '<field name="int_field" widget="percentpie"/>' +
+ // '</tree>',
+ // });
+ //
+ // assert.strictEqual(list.$('.o_form_field_percent_pie .o_pie').length, 5,
+ // "should have five pie charts");
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_pie_value').first().text(),
+ // '10%', "should have 10% as pie value since int_field=10");
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_mask').first().attr('style'),
+ // 'transform: rotate(180deg);', "left mask should be covering the whole left side of the pie");
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_mask').last().attr('style'),
+ // 'transform: rotate(36deg);', "right mask should be rotated from 360*(10/100) = 36 degrees");
+ //
+ // // switch to edit mode and check the result
+ // list.$('tbody td:not(.o_list_record_selector)').first().click();
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_pie_value').first().text(),
+ // '10%', "should have 10% as pie value since int_field=10");
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_mask').first().attr('style'),
+ // 'transform: rotate(180deg);', "left mask should be covering the whole right side of the pie");
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_mask').last().attr('style'),
+ // 'transform: rotate(36deg);', "right mask should be rotated from 360*(10/100) = 36 degrees");
+ //
+ // // save
+ // list.$buttons.find('.o_list_button_save').click();
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_pie_value').first().text(),
+ // '10%', "should have 10% as pie value since int_field=10");
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_mask').first().attr('style'),
+ // 'transform: rotate(180deg);', "left mask should be covering the whole right side of the pie");
+ // assert.strictEqual(list.$('.o_form_field_percent_pie:first .o_pie .o_mask').last().attr('style'),
+ // 'transform: rotate(36deg);', "right mask should be rotated from 360*(10/100) = 36 degrees");
+ //
+ // list.destroy();
+ // });
+
+
+ QUnit.module('FieldDomain');
+
+ QUnit.test('basic domain field usage is ok', function (assert) {
+ assert.expect(6);
+
+ this.data.partner.records[0].foo = "[]";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form>' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo" widget="domain" options="{\'model\': \'partner_type\'}"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // As the domain is empty, there should be a button to add the first
+ // domain part
+ var $domain = form.$(".o_form_field_domain");
+ var $domainAddFirstNodeButton = $domain.find(".o_domain_add_first_node_button");
+ assert.equal($domainAddFirstNodeButton.length, 1,
+ "there should be a button to create first domain element");
+
+ // Clicking on the button should add the [["id", "=", "1"]] domain, so
+ // there should be a field selector in the DOM
+ $domainAddFirstNodeButton.click();
+ var $fieldSelector = $domain.find(".o_field_selector");
+ assert.equal($fieldSelector.length, 1,
+ "there should be a field selector");
+
+ // Focusing the field selector input should open the field selector
+ // popover
+ $fieldSelector.trigger('focusin');
+ var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
+ assert.ok($fieldSelectorPopover.is(":visible"),
+ "field selector popover should be visible");
+
+ // The popover should contain the list of partner_type fields and so
+ // there should be the "Color index" field
+ var $lis = $fieldSelectorPopover.find("li");
+ var $colorIndex = $();
+ $lis.each(function () {
+ var $li = $(this);
+ if ($li.html().indexOf("Color index") >= 0) {
+ $colorIndex = $li;
+ }
+ });
+ assert.equal($colorIndex.length, 1,
+ "field selector popover should contain 'Color index' field");
+
+ // Clicking on this field should close the popover, then changing the
+ // associated value should reveal one matched record
+ $colorIndex.click();
+ $domain.find(".o_domain_leaf_value_input").val(2).change();
+ assert.equal($domain.find(".o_domain_show_selection_button").text().trim().substr(0, 2), "1 ",
+ "changing color value to 2 should reveal only one record");
+
+ // Saving the form view should show a readonly domain containing the
+ // "color" field
+ form.$buttons.find('.o_form_button_save').click();
+ $domain = form.$(".o_form_field_domain");
+ assert.ok($domain.html().indexOf("Color index") >= 0,
+ "field selector readonly value should now contain 'Color index'");
+ form.destroy();
+ });
+
+ QUnit.test('domain field is correctly reset on every view change', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.records[0].foo = '[["id","=",1]]';
+ this.data.partner.fields.bar.type = "char";
+ this.data.partner.records[0].bar = "product";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form>' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="bar"/>' +
+ '<field name="foo" widget="domain" options="{\'model\': \'bar\'}"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // As the domain is equal to [["id", "=", 1]] there should be a field
+ // selector to change this
+ var $domain = form.$(".o_form_field_domain");
+ var $fieldSelector = $domain.find(".o_field_selector");
+ assert.equal($fieldSelector.length, 1,
+ "there should be a field selector");
+
+ // Focusing its input should open the field selector popover
+ $fieldSelector.trigger('focusin');
+ var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
+ assert.ok($fieldSelectorPopover.is(":visible"),
+ "field selector popover should be visible");
+
+ // As the value of the "bar" field is "product", the field selector
+ // popover should contain the list of "product" fields
+ var $lis = $fieldSelectorPopover.find("li");
+ var $sampleLi = $();
+ $lis.each(function () {
+ var $li = $(this);
+ if ($li.html().indexOf("Product Name") >= 0) {
+ $sampleLi = $li;
+ }
+ });
+ assert.strictEqual($lis.length, 1,
+ "field selector popover should contain only one field");
+ assert.strictEqual($sampleLi.length, 1,
+ "field selector popover should contain 'Product Name' field");
+
+ // Now change the value of the "bar" field to "partner_type"
+ form.$(".o_field_widget.o_form_input").click().val("partner_type").trigger("input");
+
+ // Refocusing the field selector input should open the popover again
+ $fieldSelector = form.$(".o_field_selector");
+ $fieldSelector.trigger('focusin');
+ $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
+ assert.ok($fieldSelectorPopover.is(":visible"),
+ "field selector popover should be visible");
+
+ // Now the list of fields should be the ones of the "partner_type" model
+ $lis = $fieldSelectorPopover.find("li");
+ $sampleLi = $();
+ $lis.each(function () {
+ var $li = $(this);
+ if ($li.html().indexOf("Color index") >= 0) {
+ $sampleLi = $li;
+ }
+ });
+ assert.strictEqual($lis.length, 2,
+ "field selector popover should contain two fields");
+ assert.strictEqual($sampleLi.length, 1,
+ "field selector popover should contain 'Color index' field");
+ form.destroy();
+ });
+});
+});
+});
diff --git a/addons/web/static/tests/fields/field_utils_tests.js b/addons/web/static/tests/fields/field_utils_tests.js
new file mode 100644
index 00000000000..5b3eb267714
--- /dev/null
+++ b/addons/web/static/tests/fields/field_utils_tests.js
@@ -0,0 +1,81 @@
+odoo.define('web.field_utils_tests', function (require) {
+"use strict";
+
+var core = require('web.core');
+var fieldUtils = require('web.field_utils');
+
+QUnit.module('fields', {}, function () {
+
+QUnit.module('field_utils');
+
+QUnit.test('format integer', function(assert) {
+ assert.expect(5);
+
+ var originalGrouping = core._t.database.parameters.grouping;
+
+ core._t.database.parameters.grouping = [3, 3, 3, 3];
+ assert.strictEqual(fieldUtils.format.integer(1000000), '1,000,000');
+
+ core._t.database.parameters.grouping = [3, 2, -1];
+ assert.strictEqual(fieldUtils.format.integer(106500), '1,06,500');
+
+ core._t.database.parameters.grouping = [1, 2, -1];
+ assert.strictEqual(fieldUtils.format.integer(106500), '106,50,0');
+
+ assert.strictEqual(fieldUtils.format.integer(0), "0");
+ assert.strictEqual(fieldUtils.format.integer(undefined), "");
+
+ core._t.database.parameters.grouping = originalGrouping;
+});
+
+QUnit.test('format float', function(assert) {
+ assert.expect(4);
+
+ var originalParameters = $.extend(true, {}, core._t.database.parameters);
+
+ core._t.database.parameters.grouping = [3, 3, 3, 3];
+ assert.strictEqual(fieldUtils.format.float(1000000), '1,000,000.00');
+
+ core._t.database.parameters.grouping = [3, 2, -1];
+ assert.strictEqual(fieldUtils.format.float(106500), '1,06,500.00');
+
+ core._t.database.parameters.grouping = [1, 2, -1];
+ assert.strictEqual(fieldUtils.format.float(106500), '106,50,0.00');
+
+ _.extend(core._t.database.parameters, {
+ grouping: [3, 0],
+ decimal_point: ',',
+ thousands_sep: '.'
+ });
+ assert.strictEqual(fieldUtils.format.float(6000), '6.000,00');
+
+ core._t.database.parameters = originalParameters;
+});
+
+QUnit.test("format_datetime", function (assert) {
+ assert.expect(1);
+
+ var date_string = "2009-05-04 12:34:23";
+ var date = fieldUtils.parse.datetime(date_string);
+ var str = fieldUtils.format.datetime(date);
+ assert.strictEqual(str, moment(date).format("MM/DD/YYYY HH:mm:ss"));
+});
+
+QUnit.test("format_many2one", function (assert) {
+ assert.expect(2);
+
+ assert.strictEqual('', fieldUtils.format.many2one(null));
+ assert.strictEqual('A M2O value', fieldUtils.format.many2one({
+ data: { display_name: 'A M2O value' },
+ }));
+});
+
+
+QUnit.test('formatting chars', function(assert) {
+ assert.expect(1);
+
+ assert.strictEqual(fieldUtils.format.char(), '',
+ "undefined char should be formatted as an empty string");
+});
+});
+});
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
new file mode 100644
index 00000000000..86e569fdbb5
--- /dev/null
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -0,0 +1,3742 @@
+odoo.define('web.relational_fields_tests', function (require) {
+"use strict";
+
+var FormView = require('web.FormView');
+var ListView = require('web.ListView');
+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'},
+ 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',
+ },
+ date: {string: "Some Date", type: "date"},
+ datetime: {string: "Datetime Field", type: 'datetime'},
+ user_id: {string: "User", type: 'many2one', relation: 'user'},
+ },
+ 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,
+ }, {
+ 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: "int"},
+ },
+ 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", default: "My little Foo Value"},
+ turtle_bar: {string: "Bar", type: "boolean", default: true},
+ turtle_int: {string: "int", type: "integer", sortable: true},
+ turtle_qux: {string: "Qux", type: "float", digits: [16,1], required: true, default: 1.5},
+ turtle_description: {string: "Description", type: "text"},
+ turtle_trululu: {string: "Trululu", type: "many2one", relation: '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",
+ turtle_bar: false,
+ turtle_foo: "kawa",
+ turtle_int: 21,
+ turtle_qux: 9.8,
+ partner_ids: [],
+ }],
+ },
+ user: {
+ fields: {
+ name: {string: "Name", type: "char"}
+ },
+ records: [{
+ id: 17,
+ name: "Aline",
+ }, {
+ id: 19,
+ name: "Christine",
+ }]
+ },
+ };
+ }
+}, function () {
+
+ QUnit.module('FieldMany2One');
+
+ QUnit.test('many2ones in form views', function (assert) {
+ assert.expect(4);
+ 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,
+ mockRPC: function (route, args) {
+ if (args.method === 'get_formview_action') {
+ assert.deepEqual(args.args[0], [4], "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], [4], "should call get_formview_id with correct id");
+ return $.when(false);
+ }
+ 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");
+ });
+ testUtils.intercept(form, 'load_views', function (event) {
+ // temporarily prevent the fields_view from being loaded (to prevent a crash)
+ // TODO: specify a fields_view and test that we can edit the record in the dialog
+ event.stopPropagation();
+ });
+
+ assert.strictEqual(form.$('a.o_form_uri:contains(aaa)').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();
+
+ form.$('.o_external_button').click(); // click on the external button (should do an RPC)
+
+ // TODO: test that we can edit the record in the dialog, and that the value is correctly
+ // updated on close
+ form.destroy();
+ });
+
+ QUnit.test('many2one readonly fields with option "no_open"', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="trululu" options="{&quot;no_open&quot;: True}" />' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('a.o_form_uri').length, 0, "should not have an anchor");
+ form.destroy();
+ });
+
+ QUnit.test('many2one in edit mode', function (assert) {
+ assert.expect(16);
+
+ // create 10 partners to have the 'Search More' option in the autocomplete dropdown
+ for (var i=0; i<10; i++) {
+ var id = 20 + i;
+ this.data.partner.records.push({id: id, display_name: "Partner " + id});
+ }
+
+ 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 string="Partners"><field name="display_name"/></tree>',
+ 'partner,false,search': '<search string="Partners">' +
+ '<field name="display_name" string="Name"/>' +
+ '</search>',
+ },
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/write') {
+ assert.strictEqual(args.args[1].trululu, 20, "should write the correct id");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // the SelectCreateDialog requests the session, so intercept its custom
+ // event to specify a fake session to prevent it from crashing
+ testUtils.intercept(form, 'get_session', function (event) {
+ event.data.callback({user_context: {}});
+ });
+
+ form._toEditMode();
+ var $dropdown = form.$('.o_form_field_many2one input').autocomplete('widget');
+
+ form.$('.o_form_field_many2one input').click();
+ assert.ok($dropdown.is(':visible'),
+ 'clicking on the m2o input should open the dropdown if it is not open yet');
+ assert.strictEqual($dropdown.find('li:not(.o_m2o_dropdown_option)').length, 7,
+ 'autocomplete should contains 7 suggestions');
+ assert.strictEqual($dropdown.find('li.o_m2o_dropdown_option').length, 2,
+ 'autocomplete should contain "Search More" and Create and Edit..."');
+
+ form.$('.o_form_field_many2one input').click();
+ assert.ok(!$dropdown.is(':visible'),
+ 'clicking on the m2o input should close the dropdown if it is open');
+
+ // change the value of the m2o with a suggestion of the dropdown
+ form.$('.o_form_field_many2one input').click();
+ $dropdown.find('li:first()').click();
+ assert.ok(!$dropdown.is(':visible'), 'clicking on a value should close the dropdown');
+ assert.strictEqual(form.$('.o_form_field_many2one input').val(), 'first record',
+ 'value of the m2o should have been correctly updated');
+
+ // change the value of the m2o with a record in the 'Search More' modal
+ form.$('.o_form_field_many2one input').click();
+ // click on 'Search More' (mouseenter required by ui-autocomplete)
+ $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click();
+ assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal");
+ assert.ok(!$('.modal .o_list_view .o_list_record_selector').length,
+ "there should be no record selector in the list view");
+ assert.ok(!$('.modal .modal-footer .o_select_button').length,
+ "there should be no 'Select' button in the footer");
+ assert.ok($('.modal tbody tr').length > 10, "list should contain more than 10 records");
+ // filter the list using the searchview
+ $('.modal .o_searchview_input').trigger({type: 'keypress', which: 80}); // P
+ $('.modal .o_searchview_input').trigger({type: 'keydown', which: 13}); // enter
+ assert.strictEqual($('.modal tbody tr').length, 10,
+ "list should be restricted to records containing a P (10 records)");
+ // choose a record
+ $('.modal tbody tr:contains(Partner 20)').click(); // choose record 'Partner 20'
+ assert.ok(!$('.modal').length, "should have closed the modal");
+ assert.ok(!$dropdown.is(':visible'), 'should have closed the dropdown');
+ assert.strictEqual(form.$('.o_form_field_many2one input').val(), 'Partner 20',
+ 'value of the m2o should have been correctly updated');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('a.o_form_uri').text(), 'Partner 20',
+ "should display correct value after save");
+
+ form.destroy();
+ });
+
+ QUnit.test('many2one field with option always_reload', function (assert) {
+ assert.expect(4);
+ var count = 0;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="trululu" options="{\'always_reload\': True}"/>' +
+ '</form>',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ if (args.method === 'name_get') {
+ count++;
+ return $.when([[1, "first record\nand some address"]]);
+ }
+ return this._super(route, args);
+ },
+ });
+
+ assert.strictEqual(count, 1, "an extra name_get should have been done");
+ assert.ok(form.$('a:contains(and some address)').length,
+ "should display additional result");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('input').val(), "first record",
+ "actual field value should be displayed to be edited");
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.ok(form.$('a:contains(and some address)').length,
+ "should still display additional result");
+ form.destroy();
+ });
+
+ // QUnit.test('onchange on a many2one to a different model', function (assert) {
+ // This test is commented because the mock server does not give the correct response.
+ // It should return a couple [id, display_name], but I don't know the logic used
+ // by the server, so it's hard to emulate it correctly
+ // assert.expect(2);
+
+ // this.data.partner.records[0].product_id = 41;
+ // this.data.partner.onchanges = {
+ // foo: function(obj) {
+ // obj.product_id = 37;
+ // },
+ // };
+
+ // var form = createView({
+ // View: FormView,
+ // model: 'partner',
+ // data: this.data,
+ // arch: '<form>' +
+ // '<field name="foo"/>' +
+ // '<field name="product_id"/>' +
+ // '</form>',
+ // res_id: 1,
+ // });
+ // form.$buttons.find('.o_form_button_edit').click();
+ // assert.strictEqual(form.$('input').eq(1).val(), 'xpad', "initial product_id val should be xpad");
+
+ // form.$('input').eq(0).val("let us trigger an onchange").trigger('input');
+
+ // assert.strictEqual(form.$('input').eq(1).val(), 'xphone', "onchange should have been applied");
+ // });
+
+ QUnit.test('autocompletion in a many2one, in form view with a domain', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="product_id"/>' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ domain: [['trululu', '=', 4]]
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'name_search') {
+ assert.deepEqual(args.kwargs.args, [], "should not have a domain");
+ }
+ return this._super(route, args);
+ }
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('input').click();
+ form.destroy();
+ });
+
+ QUnit.test('autocompletion in a many2one, in form view with a date field', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="bar"/>' +
+ '<field name="date"/>' +
+ '<field name="trululu" domain="[(\'bar\',\'=\',True)]"/>' +
+ '</form>',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ if (args.method === 'name_search') {
+ assert.deepEqual(args.kwargs.args, [["bar", "=", true]], "should not have a domain");
+ }
+ return this._super(route, args);
+ },
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('input:eq(2)').click();
+ form.destroy();
+ });
+
+ QUnit.test('creating record with many2one with option always_reload', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.fields.trululu.default = 1;
+ this.data.partner.onchanges = {
+ trululu: function (obj) {
+ obj.trululu = 2; //[2, "second record"];
+ },
+ };
+
+ var count = 0;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="trululu" options="{\'always_reload\': True}"/>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ count++;
+ if (args.method === 'name_get' && args.args[0][0] === 2) {
+ return $.when([[2, "hello world\nso much noise"]]);
+ }
+ return this._super(route, args);
+ },
+ });
+
+ assert.strictEqual(count, 3, "should have done 3 rpcs (default_get, onchange, name_get)");
+ assert.strictEqual(form.$('input').val(), 'hello world',
+ "should have taken the correct display name");
+ form.destroy();
+ });
+
+ QUnit.test('selecting a many2one, then discarding', function (assert) {
+ assert.expect(3);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="product_id"/>' +
+ '</form>',
+ res_id: 1,
+ });
+ assert.strictEqual(form.$('a').text(), '', 'the tag a should be empty');
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('.o_form_field_many2one input').click();
+ form.$('.o_form_field_many2one input').autocomplete('widget').find('a').first().click();
+
+
+ assert.strictEqual(form.$('input').val(), "xphone", "should have selected xphone");
+
+ form.$buttons.find('.o_form_button_cancel').click();
+ assert.strictEqual(form.$('a').text(), '', 'the tag a should be empty');
+ form.destroy();
+ });
+
+ QUnit.test('domain and context are correctly used when doing a name_search in a m2o', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form string="Partners">' +
+ '<field name="product_id" ' +
+ 'domain="[[\'foo\', \'=\', \'bar\'], [\'foo\', \'=\', foo]]" ' +
+ 'context="{\'hello\': \'world\', \'test\': foo}"/>' +
+ '<field name="foo"/>' +
+ '</form>',
+ res_id: 1,
+ session: {user_context: {hey: "ho"}},
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/product/name_search') {
+ assert.deepEqual(
+ args.kwargs.args,
+ [['foo', '=', 'bar'], ['foo', '=', 'yop']],
+ 'the field attr domain should have been used for the RPC (and evaluated)');
+ assert.deepEqual(
+ args.kwargs.context,
+ {hey: "ho", hello: "world", test: "yop"},
+ 'the field attr context should have been used for the '
+ + 'RPC (evaluated and merged with the session one)');
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('input').first().click();
+
+ form.destroy();
+ });
+
+ QUnit.module('FieldOne2Many');
+
+ QUnit.test('one2many basic properties', function (assert) {
+ assert.expect(6);
+
+ this.data.partner.records[0].p = [2];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<notebook>' +
+ '<page string="Partner page">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</page>' +
+ '</notebook>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+
+ assert.strictEqual(form.$('td.o_list_record_selector').length, 0,
+ "embedded one2many should not have a selector");
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ "embedded one2many should not be editable");
+ assert.ok(!form.$('td.o_list_record_delete').length,
+ "embedded one2many records should not have a trash icon");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(form.$('.o_form_field_x2many_list_row_add').length,
+ "embedded one2many should now be editable");
+
+ assert.strictEqual(form.$('.o_form_field_x2many_list_row_add').attr('colspan'), "2",
+ "should have colspan 2 (one for field foo, one for being below trash icon)");
+
+ assert.ok(form.$('td.o_list_record_delete').length,
+ "embedded one2many records should have a trash icon");
+ form.destroy();
+ });
+
+ QUnit.test('one2many with date and datetime', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].p = [2];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<notebook>' +
+ '<page string="Partner page">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="date"/>' +
+ '<field name="datetime"/>' +
+ '</tree>' +
+ '</field>' +
+ '</page>' +
+ '</notebook>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+ // FIXME: this test is locale dependant. we need to do it right.
+ assert.strictEqual(form.$('td:contains(01/25/2017)').length, 1,
+ "should have formatted the date");
+ assert.strictEqual(form.$('td:contains(12/12/2016 10:55:05)').length, 1,
+ "should have formatted the datetime");
+ form.destroy();
+ });
+
+ QUnit.test('rendering with embedded one2many', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].p = [2];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<notebook>' +
+ '<page string="P page">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="foo"/>' +
+ '<field name="bar"/>' +
+ '</tree>' +
+ '</field>' +
+ '</page>' +
+ '</notebook>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('th:contains(Foo)').length, 1,
+ "embedded one2many should have a column titled according to foo");
+ assert.strictEqual(form.$('td:contains(blip)').length, 1,
+ "embedded one2many should have a cell with relational value");
+ form.destroy();
+ });
+
+ QUnit.test('embedded one2many with widget', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.records[0].p = [2];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<notebook>' +
+ '<page string="P page">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="int_field" widget="handle"/>' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</page>' +
+ '</notebook>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('span.o_row_handle').length, 1, "should have 1 handles");
+ form.destroy();
+ });
+
+ QUnit.test('one2many field when using the pager', function (assert) {
+ assert.expect(13);
+
+ 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.slice(0, 42);
+ this.data.partner.records[1].p = ids.slice(42);
+
+ var count = 0;
+ 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><t t-esc="record.display_name"/></div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>' +
+ '</field>' +
+ '</form>',
+ viewOptions: {
+ ids: [1, 2],
+ index: 0,
+ },
+ mockRPC: function () {
+ count++;
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ });
+
+ // we are on record 1, which has 90 related record (first 40 should be
+ // displayed), 2 RPCs (read) should have been done, one on the main record
+ // and one for the O2M
+ assert.strictEqual(count, 2, 'two RPCs should have been done');
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'one2many kanban should contain 40 cards for record 1');
+
+ // move to record 2, which has 3 related records (and shouldn't contain the
+ // related records of record 1 anymore). Two additional RPCs should have
+ // been done
+ form.pager.next();
+ assert.strictEqual(count, 4, 'two RPCs should have been done');
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 3,
+ 'one2many kanban should contain 3 cards for record 2');
+
+ // move back to record 1, which should contain again its first 40 related
+ // records
+ form.pager.previous();
+ assert.strictEqual(count, 6, 'two RPCs should have been done');
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
+ 'one2many kanban should contain 40 cards for record 1');
+
+ // move to the second page of the o2m: 1 RPC should have been done to fetch
+ // the 2 subrecords of page 2, and those records should now be displayed
+ form.$('.o_x2m_control_panel .o_pager_next').click();
+ assert.strictEqual(count, 7, 'one RPC should have been done');
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 2,
+ 'one2many kanban should contain 2 cards for record 1 at page 2');
+
+ // move to record 2 again and check that everything is correctly updated
+ form.pager.next();
+ assert.strictEqual(count, 9, 'two RPCs should have been done');
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 3,
+ 'one2many kanban should contain 3 cards for record 2');
+
+ // move back to record 1 and move to page 2 again: all data should have
+ // been correctly reloaded
+ form.pager.previous();
+ assert.strictEqual(count, 11, 'two RPCs should have been done');
+ form.$('.o_x2m_control_panel .o_pager_next').click();
+ assert.strictEqual(count, 12, 'one RPC should have been done');
+ assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 2,
+ 'one2many kanban should contain 2 cards for record 1 at page 2');
+ form.destroy();
+ });
+
+ QUnit.test('sorting one2many fields', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.fields.foo.sortable = true;
+ this.data.partner.records.push({id: 23, foo: "abc"});
+ this.data.partner.records.push({id: 24, foo: "xyz"});
+ this.data.partner.records.push({id: 25, foo: "def"});
+ this.data.partner.records[0].p = [23,24,25];
+
+ var rpcCount = 0;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function () {
+ rpcCount++;
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ rpcCount = 0;
+ assert.ok(form.$('table tbody tr:eq(2) td:contains(def)').length,
+ "the 3rd record is the one with 'def' value");
+ form.renderer._render = function () {
+ throw "should not render the whole form";
+ };
+
+ form.$('table thead th:contains(Foo)').click();
+ assert.strictEqual(rpcCount, 0,
+ 'sort should be in memory, no extra RPCs should have been done');
+ assert.ok(form.$('table tbody tr:eq(2) td:contains(xyz)').length,
+ "the 3rd record is the one with 'xyz' value");
+
+ form.$('table thead th:contains(Foo)').click();
+ assert.ok(form.$('table tbody tr:eq(2) td:contains(abc)').length,
+ "the 3rd record is the one with 'abc' value");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list field edition', function (assert) {
+ assert.expect(6);
+
+ this.data.partner.records.push({
+ id: 3,
+ display_name: "relational record 1",
+ });
+ this.data.partner.records[1].p = [3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ // edit the first line of the o2m
+ assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'relational record 1',
+ "display name of first record in o2m list should be 'relational record 1'");
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_field_one2many tbody td').first().click();
+ assert.ok(form.$('.o_field_one2many tbody td').first().hasClass('o_edit_mode'),
+ "first row of o2m should be in edition");
+ form.$('.o_field_one2many tbody td').first().find('input').val("new value").trigger('input');
+ assert.ok(form.$('.o_field_one2many tbody td').first().hasClass('o_edit_mode'),
+ "first row of o2m should still be in edition");
+
+ // // leave o2m edition
+ form.$el.click();
+ assert.ok(!form.$('.o_field_one2many tbody td').first().hasClass('o_edit_mode'),
+ "first row of o2m should be readonly again");
+
+ // discard changes
+ form.$buttons.find('.o_form_button_cancel').click();
+ assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'new value',
+ "changes shouldn't have been discarded yet, waiting for user confirmation");
+ $('.modal .modal-footer .btn-primary').click();
+ assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'relational record 1',
+ "display name of first record in o2m list should be 'relational record 1'");
+
+ // edit again and save
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_field_one2many tbody td').first().click();
+ form.$('.o_field_one2many tbody td').first().find('input').val("new value").trigger('input');
+ form.$el.click();
+ form.$buttons.find('.o_form_button_save').click();
+ // FIXME: this next test doesn't pass as the save of updates of
+ // relational data is temporarily disabled
+ // assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'new value',
+ // "display name of first record in o2m list should be 'new value'");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list: create action disabled', function (assert) {
+ assert.expect(2);
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="p">' +
+ '<tree create="0">' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" link should not be available in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" link should not be available in readonly');
+ form.destroy();
+ });
+
+ QUnit.test('one2many list: deleting one record', function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].p = [2, 4];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/write') {
+ var commands = args.args[1].p;
+ assert.strictEqual(commands.length, 2,
+ 'should have generated two commands');
+ assert.ok(commands[0][0] === 4 && commands[0][1] === 4,
+ 'should have generated the command 4 (LINK_TO) with id 4');
+ assert.ok(commands[1][0] === 2 && commands[1][1] === 2,
+ 'should have generated the command 2 (DELETE) with id 2');
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('td.o_list_record_delete span').length, 2,
+ "should have 2 delete buttons");
+
+ form.$('td.o_list_record_delete span').first().click();
+
+ assert.strictEqual(form.$('td.o_list_record_delete span').length, 1,
+ "should have 1 delete button (a record is supposed to have been deleted)");
+
+ // save and check that the correct command has been generated
+ form.$buttons.find('.o_form_button_save').click();
+
+ // FIXME: it would be nice to test that the view is re-rendered correctly,
+ // but as the relational data isn't re-fetched, the rendering is ok even
+ // if the changes haven't been saved
+ form.destroy();
+ });
+
+ QUnit.test('one2many kanban: edition', function (assert) {
+ assert.expect(14);
+
+ this.data.partner.records[0].p = [2];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<kanban>' +
+ // color will be in the kanban but not in the form
+ '<field name="color"/>' +
+ '<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>' +
+ '<span><t t-esc="record.color.value"/></span>' +
+ '</div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>' +
+ '<form string="Partners">' +
+ '<field name="display_name"/>' +
+ // foo will be in the form but not in the kanban
+ '<field name="foo"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(!form.$('.o_kanban_view .delete_icon').length,
+ 'delete icon should not be visible in readonly');
+ assert.ok(!form.$('.o_form_field_one2many .o-kanban-button-new').length,
+ '"Create" button should not be visible in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1,
+ 'should contain 1 record');
+ assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'second record',
+ 'display_name of subrecord should be the one in DB');
+ assert.strictEqual(form.$('.o_kanban_record span:nth(1)').text(), 'Red',
+ 'color of subrecord should be the one in DB');
+ assert.ok(form.$('.o_kanban_view .delete_icon').length,
+ 'delete icon should be visible in edit');
+ assert.ok(form.$('.o_form_field_one2many .o-kanban-button-new').length,
+ '"Create" button should be visible in edit');
+
+ // edit existing subrecord
+ form.$('.oe_kanban_global_click').click();
+
+ $('.modal .o_form_view .o_form_input').val('new name').trigger('input');
+ $('.modal .modal-footer .btn-primary').click(); // save
+ assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'new name',
+ 'value of subrecord should have been updated');
+
+ // create a new subrecord
+ form.$('.o-kanban-button-new').click();
+ $('.modal .o_form_view .o_form_input').val('new subrecord 1').trigger('input');
+ $('.modal .modal-footer .btn-primary').click(); // save and close
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2,
+ 'should contain 2 records');
+ assert.strictEqual(form.$('.o_kanban_record:nth(1) span').text(), 'new subrecord 1',
+ 'value of newly created subrecord should be "new subrecord 1"');
+
+ // create two new subrecords
+ form.$('.o-kanban-button-new').click();
+ $('.modal .o_form_view .o_form_input').val('new subrecord 2').trigger('input');
+ $('.modal .modal-footer .btn-primary:nth(1)').click(); // save and new
+ $('.modal .o_form_view .o_form_input').val('new subrecord 3').trigger('input');
+ $('.modal .modal-footer .btn-primary').click(); // save and close
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4,
+ 'should contain 4 records');
+
+ // delete subrecords
+ form.$('.o_kanban_view .delete_icon:first()').click();
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3,
+ 'should contain 3 records');
+ form.$('.o_kanban_view .delete_icon:first()').click();
+ form.$('.o_kanban_view .delete_icon:first()').click();
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1,
+ 'should contain 1 records');
+ assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'new subrecord 3',
+ 'the remaining subrecord should be "new subrecord 3"');
+ form.destroy();
+ });
+
+ QUnit.test('one2many kanban: create action disabled', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records[0].p = [4];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="p">' +
+ '<kanban create="0">' +
+ '<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>',
+ res_id: 1,
+ });
+
+ assert.ok(!form.$('.o-kanban-button-new').length,
+ '"Add" button should not be available in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(!form.$('.o-kanban-button-new').length,
+ '"Add" button should not be available in edit');
+ assert.ok(form.$('.o_kanban_view .delete_icon').length,
+ 'delete icon should be visible in edit');
+ form.destroy();
+ });
+
+ QUnit.test('one2many list (non editable): edition', function (assert) {
+ assert.expect(12);
+
+ var nbWrite = 0;
+ this.data.partner.records[0].p = [2, 4];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="display_name"/><field name="qux"/>' +
+ '</tree>' +
+ '<form string="Partners">' +
+ '<field name="display_name"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ nbWrite++;
+ assert.deepEqual(args.args[1], {
+ p: [[1, 2, {display_name: 'new name'}], [2, 4, false]]
+ }, "should have sent the correct commands");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.ok(!form.$('.o_list_record_delete').length,
+ 'delete icon should not be visible in readonly');
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" should not be visible in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2,
+ 'should contain 2 records');
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'second record',
+ 'display_name of first subrecord should be the one in DB');
+ assert.ok(form.$('.o_list_record_delete').length,
+ 'delete icon should be visible in edit');
+ assert.ok(form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" should not visible in edit');
+
+ // edit existing subrecord
+ form.$('.o_list_view tbody tr:first() td:eq(1)').click();
+
+ $('.modal .o_form_view .o_form_input').val('new name').trigger('input');
+ $('.modal .modal-footer .btn-primary').click(); // save
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
+ 'value of subrecord should have been updated');
+ assert.strictEqual(nbWrite, 0, "should not have write anything in DB");
+
+ // create new subrecords
+ // TODO when 'Add an item' will be implemented
+
+ // delete subrecords
+ form.$('.o_list_record_delete:nth(1)').click();
+ assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 1,
+ 'should contain 1 subrecord');
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
+ 'the remaining subrecord should be "new name"');
+
+ form.$buttons.find('.o_form_button_save').click(); // save the record
+ assert.strictEqual(nbWrite, 1, "should have write the changes in DB");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list (editable): edition', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.records[0].p = [2, 4];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="display_name"/><field name="qux"/>' +
+ '</tree>' +
+ '<form string="Partners">' +
+ '<field name="display_name"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" link should not be available in readonly');
+
+ form.$('.o_list_view tbody td:first()').click();
+ assert.ok($('.modal .o_form_readonly').length,
+ 'in readonly, clicking on a subrecord should open it in readonly in a dialog');
+ $('.modal .o_form_button_cancel').click(); // close the dialog
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" link should be available in edit');
+
+ // edit existing subrecord
+ form.$('.o_list_view tbody td:first()').click();
+ assert.strictEqual($('.modal').length, 0,
+ 'in edit, clicking on a subrecord should not open a dialog');
+ assert.ok(form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'),
+ 'first row should be in edition');
+ form.$('.o_list_view .o_form_input:first()').val('new name').trigger('input');
+
+ form.$('.o_list_view tbody tr:nth(1) td:first').click(); // click on second record to validate the first one
+ assert.ok(!form.$('.o_list_view tbody tr:first').hasClass('o_selected_row'),
+ 'first row should not be in edition anymore');
+ assert.strictEqual(form.$('.o_list_view tbody td:first').text(), 'new name',
+ 'value of subrecord should have been updated');
+
+ // create new subrecords
+ // TODO when 'Add an item' will be implemented
+ form.destroy();
+ });
+
+ QUnit.test('one2many list (editable): edition, part 2', function (assert) {
+ assert.expect(8);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].p[0][0], 0,
+ "should send a 0 command for field p");
+ assert.strictEqual(args.args[1].p[1][0], 0,
+ "should send a second 0 command for field p");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // edit mode, then click on Add an item and enter a value
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ form.$('td.o_edit_mode input').val('kartoffel').trigger('input');
+
+ // click again on Add an item
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('td:contains(kartoffel)').length, 1,
+ "should have one td with the new value");
+ assert.strictEqual(form.$('td.o_edit_mode input').length, 1,
+ "should have one other new td");
+ assert.strictEqual(form.$('tr.o_data_row').length, 2, "should have 2 data rows");
+
+ // enter another value and save
+ form.$('td.o_edit_mode input').val('gemuse').trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('tr.o_data_row').length, 2, "should have 2 data rows");
+ assert.strictEqual(form.$('td:contains(kartoffel)').length, 1,
+ "should have one td with the new value");
+ assert.strictEqual(form.$('td:contains(gemuse)').length, 1,
+ "should have one td with the new value");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list (editable): edition, part 3', function (assert) {
+ assert.expect(3);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="turtle_foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ // edit mode, then click on Add an item 2 times
+ assert.strictEqual(form.$('tr.o_data_row').length, 1,
+ "should have 1 data rows");
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('tr.o_data_row').length, 3,
+ "should have 3 data rows");
+
+ // cancel the edition
+ form.$buttons.find('.o_form_button_cancel').click();
+ $('.modal-footer button.btn-primary').first().click();
+ assert.strictEqual(form.$('tr.o_data_row').length, 1,
+ "should have 1 data rows");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list (editable): edition, part 4', function (assert) {
+ assert.expect(3);
+
+ this.data.turtle.onchanges = {
+ turtle_trululu: function (obj) {
+ if (obj.turtle_trululu) {
+ obj.turtle_description = "Some Description";
+ }
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="turtle_trululu"/>' +
+ '<field name="turtle_description"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ // edit mode, then click on Add an item
+ assert.strictEqual(form.$('tr.o_data_row').length, 0,
+ "should have 0 data rows");
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('textarea').val(), "",
+ "field turtle_description should be empty");
+
+ // add a value in the turtle_trululu field to trigger an onchange
+ var $dropdown = form.$('.o_form_field_many2one[name=turtle_trululu] input')
+ .autocomplete('widget');
+ form.$('.o_form_field_many2one[name=turtle_trululu] input').click();
+ $dropdown.find('a:contains(first record)').mouseenter().click();
+ assert.strictEqual(form.$('textarea').val(), "Some Description",
+ "field turtle_description should be set to the result of the onchange");
+ form.destroy();
+ });
+
+ QUnit.test('one2many list (editable): discarding required empty data', function (assert) {
+ assert.expect(7);
+
+ this.data.turtle.fields.turtle_foo.required = true;
+ delete this.data.turtle.fields.turtle_foo.default;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="turtle_foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ if (args.method) {
+ assert.step(args.method);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // edit mode, then click on Add an item, then click elsewhere
+ assert.strictEqual(form.$('tr.o_data_row').length, 0,
+ "should have 0 data rows");
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ form.$('label.o_form_label').first().click();
+ assert.strictEqual(form.$('tr.o_data_row').length, 0,
+ "should still have 0 data rows");
+
+ // click on Add an item again, then click on save
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('tr.o_data_row').length, 0,
+ "should still have 0 data rows");
+
+ assert.verifySteps(['read', 'default_get', 'default_get']);
+ form.destroy();
+ });
+
+ QUnit.test('unselecting a line with missing required data', function (assert) {
+ assert.expect(5);
+
+ this.data.turtle.fields.turtle_foo.required = true;
+ delete this.data.turtle.fields.turtle_foo.default;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_int"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ // edit mode, then click on Add an item, then click elsewhere
+ assert.strictEqual(form.$('tr.o_data_row').length, 0,
+ "should have 0 data rows");
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('tr.o_data_row').length, 1,
+ "should have 1 data rows");
+
+ // adding a value in the non required field, so it is dirty, but with
+ // a missing required field
+ form.$('input[name="turtle_int"]').val('12345').trigger('input');
+
+ // click elsewhere,
+ form.$('label.o_form_label').click();
+ assert.strictEqual($('.modal').length, 1,
+ 'a confirmation model should be opened');
+
+ // click on cancel, the line should still be selected
+ $('.modal .modal-footer button.btn-default').click();
+ assert.strictEqual(form.$('tr.o_data_row.o_selected_row').length, 1,
+ "should still have 1 selected data row");
+
+ // click elsewhere, and click on ok (on the confirmation dialog)
+ form.$('label.o_form_label').click();
+ $('.modal .modal-footer button.btn-primary').click();
+ assert.strictEqual(form.$('tr.o_data_row').length, 0,
+ "should have 0 data rows (invalid line has been discarded");
+
+ form.destroy();
+ });
+
+ QUnit.test('editing a o2m, with required field and onchange', function (assert) {
+ assert.expect(12);
+
+ this.data.turtle.fields.turtle_foo.required = true;
+ delete this.data.turtle.fields.turtle_foo.default;
+ this.data.turtle.onchanges = {
+ turtle_foo: function (obj) {
+ obj.turtle_int = obj.turtle_foo.length;
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_int"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ if (args.method) {
+ assert.step(args.method);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // edit mode, then click on Add an item
+ assert.strictEqual(form.$('tr.o_data_row').length, 0,
+ "should have 0 data rows");
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+
+ // input some text in required turtle_foo field
+ form.$('input[name="turtle_foo"]').val('aubergine').trigger('input');
+ assert.strictEqual(form.$('input[name="turtle_int"]').val(), "9",
+ "onchange should have been triggered");
+
+ // save and check everything is fine
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_data_row td:contains(aubergine)').length, 1,
+ "should have one row with turtle_foo value");
+ assert.strictEqual(form.$('.o_data_row td:contains(9)').length, 1,
+ "should have one row with turtle_int value");
+
+ assert.verifySteps(['read', 'default_get', 'onchange', 'onchange', 'write', 'read', 'read']);
+ form.destroy();
+ });
+
+ QUnit.test('onchange in a one2many', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.records.push({
+ id: 3,
+ foo: "relational record 1",
+ });
+ this.data.partner.records[1].p = [3];
+ this.data.partner.onchanges = {p: true};
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ return $.when({value: { p: [
+ [5], // delete all
+ [0, 0, {foo: "from onchange"}], // create new
+ ]}});
+ }
+ return this._super(route, args);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_field_one2many tbody td').first().click();
+ form.$('.o_field_one2many tbody td').first().find('input').val("new value").trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'from onchange',
+ "display name of first record in o2m list should be 'new value'");
+ form.destroy();
+ });
+
+ QUnit.test('one2many, default_get and onchange (basic)', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.fields.p.default = [
+ [6, 0, []], // replace with zero ids
+ ];
+ this.data.partner.onchanges = {p: true};
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ return $.when({value: { p: [
+ [5], // delete all
+ [0, 0, {foo: "from onchange"}], // create new
+ ]}});
+ }
+ return this._super(route, args);
+ },
+ });
+
+ assert.ok(form.$('td:contains(from onchange)').length,
+ "should have 'from onchange' value in one2many");
+ form.destroy();
+ });
+
+ QUnit.test('one2many list (editable): readonly domain is evaluated', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].p = [2, 4];
+ this.data.partner.records[1].product_id = false;
+ this.data.partner.records[2].product_id = 37;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="display_name" attrs=\'{"readonly": [["product_id", "=", false]]}\'/>' +
+ '<field name="product_id"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(form.$('.o_list_view tbody tr:eq(0) td:first').hasClass('o_readonly'),
+ "first record should have display_name in readonly mode");
+
+ assert.notOk(form.$('.o_list_view tbody tr:eq(1) td:first').hasClass('o_readonly'),
+ "second record should not have display_name in readonly mode");
+ form.destroy();
+ });
+
+ QUnit.test('pager of one2many field in new record', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].p = [];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'partner,false,form':
+ '<form string="Partner"><field name="foo"/></form>',
+ },
+ });
+
+ assert.ok(!form.$('.o_x2m_control_panel .o_cp_pager div').is(':visible'),
+ 'o2m pager should be hidden');
+
+ // click to create a subrecord
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+ $('.modal .o_form_input').val('new record').trigger('input');
+ $('.modal .modal-footer button:eq(0)').click(); // save and close
+
+ assert.ok(!form.$('.o_x2m_control_panel .o_cp_pager div').is(':visible'),
+ 'o2m pager should be hidden');
+ form.destroy();
+ });
+
+ QUnit.test('one2many list with a many2one', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records[0].p = [2];
+ this.data.partner.records[1].product_id = 37;
+ this.data.partner.onchanges.p = function (obj) {
+ obj.p = [
+ [5], // delete all
+ [1, 2, {product_id: [37, "xphone"]}], // update existing record
+ [0, 0, {product_id: [41, "xpad"]}]
+ ];
+ //
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="product_id"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ archs: {
+ 'partner,false,form':
+ '<form string="Partner"><field name="product_id"/></form>',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ assert.deepEqual(args.args[1].p, [[4, 2, false], [0, false, {product_id: 41}]],
+ "should trigger onchange with correct parameters");
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.strictEqual(form.$('tbody td:contains(xphone)').length, 1,
+ "should have properly fetched the many2one nameget");
+ assert.strictEqual(form.$('tbody td:contains(xpad)').length, 0,
+ "should not display 'xpad' anywhere");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+
+ $('.modal .o_form_field_many2one input').click();
+
+ var $dropdown = $('.modal .o_form_field_many2one input').autocomplete('widget');
+
+ $dropdown.find('li:eq(1) a').mouseenter();
+ $dropdown.find('li:eq(1) a').click();
+
+ $('.modal .modal-footer button:eq(0)').click(); // save and close
+
+ assert.strictEqual(form.$('tbody td:contains(xpad)').length, 1,
+ "should display 'xpad' on a td");
+ assert.strictEqual(form.$('tbody td:contains(xphone)').length, 1,
+ "should still display xphone");
+ form.destroy();
+ });
+
+ QUnit.test('one2many list with inline form view', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records[0].p = [];
+
+ var rpcCount = 0;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<form string="Partner">' +
+ '<field name="product_id"/>' +
+ '<field name="int_field"/>' +
+ '</form>' +
+ '<tree>' +
+ '<field name="product_id"/>' +
+ '<field name="foo"/>' + // don't remove this, it is
+ // useful to make sure the foo fieldwidget
+ // does not crash because the foo field
+ // is not in the form view
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ rpcCount++;
+ if (args.method === 'write') {
+ assert.deepEqual(args.args[1].p, [[0, false, {
+ display_name: false, int_field: 123, product_id: 41
+ }]]);
+ }
+ return this._super(route, args);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+
+ // write in the many2one field, value = 37 (xphone)
+ $('.modal .o_form_field_many2one input').click();
+ var $dropdown = $('.modal .o_form_field_many2one input').autocomplete('widget');
+ $dropdown.find('li:eq(0) a').mouseenter();
+ $dropdown.find('li:eq(0) a').click();
+
+ // write in the integer field
+ $('.modal .modal-body input.o_field_widget').val('123').trigger('input');
+
+ // save and close
+ $('.modal .modal-footer button:eq(0)').click();
+
+ assert.strictEqual(form.$('tbody td:contains(xphone)').length, 1,
+ "should display 'xphone' in a td");
+
+ // reopen the record in form view
+ form.$('tbody td:contains(xphone)').click();
+
+ assert.strictEqual($('.modal .modal-body input').val(), "xphone",
+ "should display 'xphone' in an input");
+
+ $('.modal .modal-body input.o_field_widget').val('456').trigger('input');
+
+ // discard
+ $('.modal .modal-footer span:contains(Discard)').click();
+
+ // reopen the record in form view
+ form.$('tbody td:contains(xphone)').click();
+
+ assert.strictEqual($('.modal .modal-body input.o_field_widget').val(), "123",
+ "should display 123 (previous change has been discarded)");
+
+ // write in the many2one field, value = 41 (xpad)
+ $('.modal .o_form_field_many2one input').click();
+ $dropdown = $('.modal .o_form_field_many2one input').autocomplete('widget');
+ $dropdown.find('li:eq(1) a').mouseenter();
+ $dropdown.find('li:eq(1) a').click();
+
+ // save and close
+ $('.modal .modal-footer button:eq(0)').click();
+
+ assert.strictEqual(form.$('tbody td:contains(xpad)').length, 1,
+ "should display 'xpad' in a td");
+
+ // save the record
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+
+ QUnit.test('one2many list with inline form view with context with parent key', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].p = [2];
+ this.data.partner.records[0].product_id = 41;
+ this.data.partner.records[1].product_id = 37;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="foo"/>' +
+ '<field name="product_id"/>' +
+ '<field name="p">' +
+ '<form string="Partner">' +
+ '<field name="product_id" context="{\'partner_foo\':parent.foo, \'lalala\': parent.product_id}"/>' +
+ '</form>' +
+ '<tree>' +
+ '<field name="product_id"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'name_search') {
+ assert.strictEqual(args.kwargs.context.partner_foo, "yop",
+ "should have correctly evaluated parent foo field");
+ assert.strictEqual(args.kwargs.context.lalala, 41,
+ "should have correctly evaluated parent product_id field");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ // open a modal
+ form.$('tr.o_data_row:eq(0) td:contains(xphone)').click();
+
+ // write in the many2one field
+ $('.modal .o_form_field_many2one input').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list, editable, with many2one and with context with parent key', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.records[0].p = [2];
+ this.data.partner.records[1].product_id = 37;
+
+ 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="product_id" context="{\'partner_foo\':parent.foo}"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'name_search') {
+ assert.strictEqual(args.kwargs.context.partner_foo, "yop",
+ "should have correctly evaluated parent foo field");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('tr.o_data_row:eq(0) td:contains(xphone)').click();
+
+ // trigger a name search
+ form.$('table td input.o_form_input').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list edition, some basic functionality', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.fields.foo.default = false;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+
+ assert.strictEqual(form.$('td input.o_field_widget.o_form_input').length, 1,
+ "should have created a row in edit mode");
+
+ form.$('td input.o_field_widget.o_form_input').val('a').trigger('input');
+
+ assert.strictEqual(form.$('td input.o_field_widget.o_form_input').length, 1,
+ "should not have unselected the row after edition");
+
+ form.$('td input.o_field_widget.o_form_input').val('abc').trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('td:contains(abc)').length, 1,
+ "should have a row with the correct value");
+ form.destroy();
+ });
+
+ QUnit.test('one2many list, the context is properly evaluated and sent', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="int_field"/>' +
+ '<field name="p" context="{\'hello\': \'world\', \'abc\': int_field}">' +
+ '<tree editable="top">' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'default_get') {
+ var context = args.kwargs.context;
+ assert.strictEqual(context.hello, "world");
+ assert.strictEqual(context.abc, 10);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+ form.destroy();
+ });
+
+ QUnit.test('one2many with many2many widget: create', function (assert) {
+ assert.expect(10);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="turtles" widget="many2many">' +
+ '<tree>' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_qux"/>' +
+ '<field name="turtle_int"/>' +
+ '<field name="product_id"/>' +
+ '</tree>' +
+ '<form>' +
+ '<group>' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_bar"/>' +
+ '<field name="turtle_int"/>' +
+ '<field name="product_id"/>' +
+ '</group>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'turtle,false,list': '<tree><field name="display_name"/><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></tree>',
+ 'turtle,false,search': '<search><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></search>',
+ },
+ session: {},
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/turtle/create') {
+ assert.ok(args.args, "should write on the turtle record");
+ }
+ if (route === '/web/dataset/call_kw/partner/write') {
+ assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1");
+ assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+
+ assert.strictEqual($('.modal .o_data_row').length, 2,
+ "sould have 2 records in the select view (the last one is not displayed because it is already selected)");
+
+ $('.modal .o_data_row:first .o_list_record_selector input').click();
+ $('.modal .o_select_button').click();
+ $('.o_form_button_save').click();
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+
+ assert.strictEqual($('.modal .o_data_row').length, 1,
+ "sould have 1 record in the select view");
+
+ $('.modal-footer button:eq(1)').click();
+ $('.modal input.o_form_field[name="turtle_foo"]').val('tototo').trigger('input');
+ $('.modal input.o_form_field[name="turtle_int"]').val(50).trigger('input');
+ var $many2one = $('.modal [name="product_id"] input').click();
+ var $dropdown = $many2one.autocomplete('widget');
+ $dropdown.find('li:first a').mouseenter();
+ $dropdown.find('li:first a').click();
+
+ $('.modal-footer button:contains(&):first').click();
+
+ assert.strictEqual($('.modal').length, 0, "sould close the modals");
+
+ assert.strictEqual(form.$('.o_data_row').length, 3,
+ "sould have 3 records in one2many list");
+ assert.strictEqual(form.$('.o_data_row').text(), "blip1.59yop1.5tototo1.550xphone",
+ "sould display the record values in one2many list");
+
+ $('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many with many2many widget: edition', function (assert) {
+ assert.expect(6);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="turtles" widget="many2many">' +
+ '<tree>' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_qux"/>' +
+ '<field name="turtle_int"/>' +
+ '<field name="product_id"/>' +
+ '</tree>' +
+ '<form>' +
+ '<group>' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_bar"/>' +
+ '<field name="turtle_int"/>' +
+ '<field name="turtle_trululu"/>' +
+ '<field name="product_id"/>' +
+ '</group>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'turtle,false,list': '<tree><field name="display_name"/><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></tree>',
+ 'turtle,false,search': '<search><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></search>',
+ },
+ session: {},
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/turtle/write') {
+ assert.strictEqual(args.args[0].length, 1, "should write on the turtle record");
+ assert.deepEqual(args.args[1], {"product_id":37}, "should write only the product_id on the turtle record");
+ }
+ if (route === '/web/dataset/call_kw/partner/write') {
+ assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1");
+ assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$('.o_data_row:first').click();
+ $('.modal .o_form_button_cancel').click();
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // edit the first one2many record
+ form.$('.o_data_row:first').click();
+ var $many2one = $('.modal [name="product_id"] input').click();
+ var $dropdown = $many2one.autocomplete('widget');
+ $dropdown.find('li:first a').mouseenter();
+ $dropdown.find('li:first a').click();
+ $('.modal-footer button:first').click();
+
+ $('.o_form_button_save').click(); // don't save anything because the one2many does not change
+
+ // add a one2many record
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field_x2many_list_row_add a').click();
+ $('.modal .o_data_row:first .o_list_record_selector input').click();
+ $('.modal .o_select_button').click();
+
+ // edit the second one2many record
+ form.$('.o_data_row:eq(1)').click();
+ $many2one = $('.modal [name="product_id"] input').click();
+ $dropdown = $many2one.autocomplete('widget');
+ $dropdown.find('li:first a').mouseenter();
+ $dropdown.find('li:first a').click();
+ $('.modal-footer button:first').click();
+
+ $('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('new record, the context is properly evaluated and sent', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.fields.int_field.default = 17;
+ var n = 0;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="int_field"/>' +
+ '<field name="p" context="{\'hello\': \'world\', \'abc\': int_field}">' +
+ '<tree editable="top">' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'default_get') {
+ n++;
+ if (n === 2) {
+ var context = args.kwargs.context;
+ assert.strictEqual(context.hello, "world");
+ assert.strictEqual(context.abc, 17);
+ }
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+ form.destroy();
+ });
+
+ QUnit.test('parent data is properly sent on an onchange rpc', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.onchanges = {bar: function () {}};
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="foo"/>' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="bar"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ var fieldValues = args.args[1];
+ assert.strictEqual(fieldValues.trululu.foo, "yop",
+ "should have properly sent the parent foo value");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+ form.destroy();
+ });
+
+ QUnit.test('parent data is properly sent on an onchange rpc, new record', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.onchanges = {bar: function () {}};
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="foo"/>' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="bar"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ var fieldValues = args.args[1];
+ assert.strictEqual(fieldValues.trululu.foo, "My little Foo Value",
+ "should have properly sent the parent foo value");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+ form.destroy();
+ });
+
+ QUnit.test('sub form view with a required field', function (assert) {
+ assert.expect(2);
+ this.data.partner.fields.foo.required = true;
+ this.data.partner.fields.foo.default = null;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<form string="Partner">' +
+ '<group><field name="foo"/></group>' +
+ '</form>' +
+ '<tree>' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('tbody td.o_form_field_x2many_list_row_add a').click();
+ $('.modal-footer button.btn-primary').first().click();
+
+ assert.strictEqual($('.modal').length, 1, "should still have an open modal");
+ assert.strictEqual($('.modal tbody label.o_form_invalid').length, 1,
+ "should have displayed invalid fields");
+ form.destroy();
+ });
+
+ QUnit.test('one2many list with action button', 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">' +
+ '<field name="int_field"/>' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="foo"/>' +
+ '<button name="method_name" type="object" icon="fa-plus"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ intercepts: {
+ execute_action: function (event) {
+ assert.strictEqual(event.data.record_id, 2,
+ 'should call with correct id');
+ assert.strictEqual(event.data.model, 'partner',
+ 'should call with correct model');
+ assert.strictEqual(event.data.action_data.name, 'method_name',
+ "should call correct method");
+ assert.strictEqual(event.data.action_data.type, 'object',
+ 'should have correct type');
+ },
+ },
+ });
+
+ form.$('.o_list_button button').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many kanban with action button', 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">' +
+ '<field name="p">' +
+ '<kanban>' +
+ '<field name="foo"/>' +
+ '<templates>' +
+ '<t t-name="kanban-box">' +
+ '<div>' +
+ '<span><t t-esc="record.foo.value"/></span>' +
+ '<button name="method_name" type="object" class="fa fa-plus"/>' +
+ '</div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ intercepts: {
+ execute_action: function (event) {
+ assert.strictEqual(event.data.record_id, 2,
+ 'should call with correct id');
+ assert.strictEqual(event.data.model, 'partner',
+ 'should call with correct model');
+ assert.strictEqual(event.data.action_data.name, 'method_name',
+ "should call correct method");
+ assert.strictEqual(event.data.action_data.type, 'object',
+ 'should have correct type');
+ },
+ },
+ });
+
+ form.$('.oe_kanban_action_button').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many without inline tree arch', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].turtles = [2,3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="p" widget="many2many_tags"/>' + // check if the view don not call load view (widget without useSubview)
+ '<field name="turtles"/>' +
+ '<field name="timmy" invisible="1"/>' + // check if the view don not call load view in invisible
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ archs: {
+ "turtle,false,list": '<tree string="Turtles"><field name="turtle_bar"/><field name="display_name"/><field name="partner_ids"/></tree>',
+ }
+ });
+
+ assert.strictEqual(form.$('.o_form_field[name="turtles"] .o_list_view').length, 1,
+ 'should display one2many list view in the modal');
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ 'should display the 2 turtles');
+
+ form.destroy();
+ });
+
+ QUnit.test('many2one and many2many in one2many', function (assert) {
+ assert.expect(11);
+
+ this.data.turtle.records[1].product_id = 37;
+ this.data.partner.records[0].turtles = [2, 3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="int_field"/>' +
+ '<field name="turtles">' +
+ '<form string="Turtles">' +
+ '<group>' +
+ '<field name="product_id"/>' +
+ '</group>' +
+ '</form>' +
+ '<tree editable="top">' +
+ '<field name="display_name"/>' +
+ '<field name="product_id"/>' +
+ '<field name="partner_ids" widget="many2many_tags"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ var commands = args.args[1].turtles;
+ assert.strictEqual(commands.length, 2,
+ "should have generated 2 commands");
+ assert.deepEqual(commands[0], [1, 2, {
+ partner_ids: [[6, false, [2, 1]]],
+ product_id: 41,
+ }], "generated commands should be correct");
+ assert.deepEqual(commands[1], [4, 3, false],
+ "generated commands should be correct");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ 'should display the 2 turtles');
+ assert.strictEqual(form.$('.o_data_row:first td:nth(1)').text(), 'xphone',
+ "should correctly display the m2o");
+ assert.strictEqual(form.$('.o_data_row:first td:nth(2) .badge').length, 2,
+ "m2m should contain two tags");
+ assert.strictEqual(form.$('.o_data_row:first td:nth(2) .badge:first span').text(),
+ 'second record', "m2m values should have been correctly fetched");
+
+ form.$('.o_data_row:first').click();
+
+ assert.strictEqual($('.modal .o_form_field').text(), "xphone",
+ 'should display the form view dialog with the many2one value');
+ $('.modal-footer button').click(); // close the modal
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // edit the m2m of first row
+ form.$('.o_list_view tbody td:first()').click();
+ // remove a tag
+ form.$('.o_form_field_many2manytags .badge:contains(aaa) .o_delete').click();
+ assert.strictEqual(form.$('.o_selected_row .o_form_field_many2manytags .o_badge_text:contains(aaa)').length, 0,
+ "tag should have been correctly removed");
+ // add a tag
+ var $m2mInput = form.$('.o_selected_row .o_form_field_many2manytags input');
+ $m2mInput.click();
+ $m2mInput.autocomplete('widget').find('li:first()').click();
+ assert.strictEqual(form.$('.o_selected_row .o_form_field_many2manytags .o_badge_text:contains(first record)').length, 1,
+ "tag should have been correctly added");
+
+ // edit the m2o of first row
+ var $m2oInput = form.$('.o_selected_row .o_form_field_many2one:first input');
+ $m2oInput.click();
+ $m2oInput.autocomplete('widget').find('li:contains(xpad)').mouseover().click();
+ assert.strictEqual(form.$('.o_selected_row .o_form_field_many2one:first input').val(), 'xpad',
+ "m2o value should have been updated");
+
+ // save (should correctly generate the commands)
+ form.$buttons.find('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('load view for x2many in one2many', function (assert) {
+ assert.expect(2);
+
+ this.data.turtle.records[1].product_id = 37;
+ this.data.partner.records[0].turtles = [2,3];
+ this.data.partner.records[2].turtles = [1,3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="int_field"/>' +
+ '<field name="turtles">' +
+ '<form string="Turtles">' +
+ '<group>' +
+ '<field name="product_id"/>' +
+ '<field name="partner_ids"/>' +
+ '</group>' +
+ '</form>' +
+ '<tree>' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ archs: {
+ "partner,false,list": '<tree string="Partners"><field name="display_name"/></tree>',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ 'should display the 2 turtles');
+
+ form.$('.o_data_row:first').click();
+
+ assert.strictEqual($('.modal .o_form_field[name="partner_ids"] .o_list_view').length, 1,
+ 'should display many2many list view in the modal');
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many (who contains a one2many) with tree view and without form view', function (assert) {
+ assert.expect(1);
+
+ // avoid error in _postprocess
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree>' +
+ '<field name="partner_ids"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ archs: {
+ "turtle,false,form": '<form string="Turtles"><field name="turtle_foo"/></form>',
+ },
+ });
+
+ form.$('.o_data_row:first').click();
+
+ assert.strictEqual($('.modal .o_form_field[name="turtle_foo"]').text(), 'blip',
+ 'should open the modal and display the form field');
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many (who contains display_name) with tree view and without form view', function (assert) {
+ assert.expect(1);
+
+ // avoid error in _fetchX2Manys
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree>' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ archs: {
+ "turtle,false,form": '<form string="Turtles"><field name="turtle_foo"/></form>',
+ },
+ });
+
+ form.$('.o_data_row:first').click();
+
+ assert.strictEqual($('.modal .o_form_field[name="turtle_foo"]').text(), 'blip',
+ 'should open the modal and display the form field');
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many field with virtual ids', function (assert) {
+ assert.expect(11);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<notebook>' +
+ '<page>' +
+ '<field name="p" mode="kanban">' +
+ '<kanban>' +
+ '<templates>' +
+ '<t t-name="kanban-box">' +
+ '<div class="oe_kanban_details">' +
+ '<div class="o_test_id">' +
+ '<field name="id"/>' +
+ '</div>' +
+ '<div class="o_test_foo">' +
+ '<field name="foo"/>' +
+ '</div>' +
+ '</div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>' +
+ '</field>' +
+ '</page>' +
+ '</notebook>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ archs: {
+ 'partner,false,form': '<form string="Associated partners">' +
+ '<field name="foo"/>' +
+ '</form>',
+ },
+ res_id: 4,
+ });
+
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view').length, 1,
+ "should have one inner kanban view for the one2many field");
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 0,
+ "should not have kanban records yet");
+
+ // // switch to edit mode and create a new kanban record
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_form_field .o-kanban-button-new').click();
+
+ // save & close the modal
+ assert.strictEqual($('.modal-content input.o_form_field').val(), 'My little Foo Value',
+ "should already have the default value for field foo");
+ $('.modal-content .btn-primary').first().click();
+
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view').length, 1,
+ "should have one inner kanban view for the one2many field");
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 1,
+ "should now have one kanban record");
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_id').text(),
+ '', "should not have a value for the id field");
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_foo').text(),
+ 'My little Foo Value', "should have a value for the foo field");
+
+ // save the view to force a create of the new record in the one2many
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view').length, 1,
+ "should have one inner kanban view for the one2many field");
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 1,
+ "should now have one kanban record");
+ assert.notEqual(form.$('.o_form_field .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_id').text(),
+ '', "should now have a value for the id field");
+ assert.strictEqual(form.$('.o_form_field .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_foo').text(),
+ 'My little Foo Value', "should still have a value for the foo field");
+
+ form.destroy();
+ });
+
+ QUnit.test('focusing fields in one2many list', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_int"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '<field name="foo"/>' +
+ '</form>',
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ form.$('.o_data_row:first td:first').click();
+ assert.strictEqual(form.$('.o_form_input[name="turtle_foo"]')[0], document.activeElement,
+ "turtle foo field should have focus");
+
+ form.$('.o_form_input[name="turtle_foo"]').trigger({type: 'keydown', which: $.ui.keyCode.TAB});
+ assert.strictEqual(form.$('.o_form_input[name="turtle_int"]')[0], document.activeElement,
+ "turtle int field should have focus");
+ form.destroy();
+ });
+
+ QUnit.test('one2many list editable = top', function (assert) {
+ assert.expect(6);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="top">' +
+ '<field name="turtle_foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ var commands = args.args[1].turtles;
+ assert.strictEqual(commands[0][0], 0,
+ "first command is a create");
+ assert.strictEqual(commands[1][0], 4,
+ "second command is a link to");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should start with one data row");
+
+ form.$('.o_form_field_x2many_list_row_add a').click();
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ "should have 2 data rows");
+ assert.ok(form.$('tr.o_data_row:first:contains(My little Foo Value)').length, 1,
+ "first row should be the new value");
+ assert.ok(form.$('tr.o_data_row:first').hasClass('o_selected_row'),
+ "first row should be selected");
+
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+
+ QUnit.test('one2many list editable = bottom', function (assert) {
+ assert.expect(6);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="bottom">' +
+ '<field name="turtle_foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ var commands = args.args[1].turtles;
+ assert.strictEqual(commands[0][0], 4,
+ "first command is a link to");
+ assert.strictEqual(commands[1][0], 0,
+ "second command is a create");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should start with one data row");
+
+ form.$('.o_form_field_x2many_list_row_add a').click();
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ "should have 2 data rows");
+ assert.ok(form.$('tr.o_data_row:eq(1):contains(My little Foo Value)').length, 1,
+ "second row should be the new value");
+ assert.ok(form.$('tr.o_data_row:eq(1)').hasClass('o_selected_row'),
+ "second row should be selected");
+
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+
+ QUnit.module('FieldMany2Many');
+
+ QUnit.test('many2many kanban: edition', function (assert) {
+ assert.expect(28);
+
+ this.data.partner.records[0].timmy = [12, 14];
+ this.data.partner_type.records.push({id: 15, display_name: "red", color: 6});
+ this.data.partner_type.records.push({id: 18, display_name: "yellow", color: 4});
+ this.data.partner_type.records.push({id: 21, display_name: "blue", color: 1});
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="timmy">' +
+ '<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>' +
+ '<form string="Partners">' +
+ '<field name="display_name"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'partner_type,false,form': '<form string="Types"><field name="display_name"/></form>',
+ 'partner_type,false,list': '<tree string="Types"><field name="display_name"/></tree>',
+ 'partner_type,false,search': '<search string="Types">' +
+ '<field name="name" string="Name"/>' +
+ '</search>',
+ },
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner_type/write') {
+ assert.strictEqual(args.args[1].display_name, "new name", "should write 'new_name'");
+ }
+ if (route === '/web/dataset/call_kw/partner_type/create') {
+ assert.strictEqual(args.args[0].display_name, "A new type", "should create 'A new type'");
+ }
+ if (route === '/web/dataset/call_kw/partner/write') {
+ var commands = args.args[1].timmy;
+ assert.strictEqual(commands.length, 1, "should have generated one command");
+ assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH");
+ // get the created type's id
+ var createdType = _.findWhere(this.data.partner_type.records, {
+ display_name: "A new type"
+ });
+ var ids = _.sortBy([12, 15, 18, 21].concat(createdType.id), _.identity.bind(_));
+ assert.ok(_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), ids),
+ "new value should be " + ids);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // the SelectCreateDialog requests the session, so intercept its custom
+ // event to specify a fake session to prevent it from crashing
+ testUtils.intercept(form, 'get_session', function (event) {
+ event.data.callback({user_context: {}});
+ });
+
+ assert.ok(!form.$('.o_kanban_view .delete_icon').length,
+ 'delete icon should not be visible in readonly');
+ assert.ok(!form.$('.o_form_field_many2many .o-kanban-button-new').length,
+ '"Add" button should not be visible in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2,
+ 'should contain 2 records');
+ assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'gold',
+ 'display_name of subrecord should be the one in DB');
+ assert.ok(form.$('.o_kanban_view .delete_icon').length,
+ 'delete icon should be visible in edit');
+ assert.ok(form.$('.o_form_field_many2many .o-kanban-button-new').length,
+ '"Add" button should be visible in edit');
+
+ // edit existing subrecord
+ form.$('.oe_kanban_global_click:first()').click();
+
+ $('.modal .o_form_view .o_form_input').val('new name').trigger('input');
+ $('.modal .modal-footer .btn-primary').click(); // save
+ assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'new name',
+ 'value of subrecord should have been updated');
+
+ // add subrecords
+ // -> single select
+ form.$('.o_form_field_many2many .o-kanban-button-new').click();
+ assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal");
+ assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 3,
+ "list view should contain 3 records");
+ $('.modal .o_list_view tbody tr:contains(red)').click(); // select red
+ assert.ok(!$('.modal .o_list_view').length, "should have closed the modal");
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3,
+ 'kanban should now contain 3 records');
+ assert.ok(form.$('.o_kanban_record:contains(red)').length,
+ 'record "red" should be in the kanban');
+
+ // -> multiple select
+ form.$('.o_form_field_many2many .o-kanban-button-new').click();
+ assert.ok($('.modal .o_select_button').prop('disabled'), "select button should be disabled");
+ assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 2,
+ "list view should contain 2 records");
+ $('.modal .o_list_view thead .o_list_record_selector input').click(); // select all
+ $('.modal .o_select_button').click(); // validate selection
+ assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled");
+ assert.ok(!$('.modal .o_list_view').length, "should have closed the modal");
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5,
+ 'kanban should now contain 5 records');
+ // -> created record
+ form.$('.o_form_field_many2many .o-kanban-button-new').click();
+ $('.modal .modal-footer .btn-primary:nth(1)').click(); // click on 'Create'
+ assert.ok($('.modal .o_form_view.o_form_editable').length,
+ "should have opened a form view in edit mode, in a modal");
+ $('.modal .o_form_view input').val('A new type').trigger('input');
+ $('.modal:nth(1) .modal-footer .btn-primary:first()').click(); // click on 'Save & Close'
+ assert.ok(!$('.modal').length, "should have closed both modals");
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 6,
+ 'kanban should now contain 6 records');
+ assert.ok(form.$('.o_kanban_record:contains(A new type)').length,
+ 'the newly created type should be in the kanban');
+
+ // delete subrecords
+ form.$('.o_kanban_record:contains(silver) .delete_icon').click();
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5,
+ 'should contain 5 records');
+ assert.ok(!form.$('.o_kanban_record:contains(silver)').length,
+ 'the removed record should not be in kanban anymore');
+
+ // save the record
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+
+ QUnit.test('many2many kanban: create action disabled', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.records[0].timmy = [12, 14];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="timmy">' +
+ '<kanban create="0">' +
+ '<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_type,false,list': '<tree><field name="name"/></tree>',
+ 'partner_type,false,search': '<search>' +
+ '<field name="display_name" string="Name"/>' +
+ '</search>',
+ },
+ res_id: 1,
+ session: {user_context: {}},
+ });
+
+ assert.ok(!form.$('.o-kanban-button-new').length,
+ '"Add" button should not be available in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(form.$('.o-kanban-button-new').length,
+ '"Add" button should be available in edit');
+ assert.ok(form.$('.o_kanban_view .delete_icon').length,
+ 'delete icon should be visible in edit');
+
+ form.$('.o-kanban-button-new').click(); // click on 'Add'
+ assert.strictEqual($('.modal .modal-footer .btn-primary').length, 1, // only button 'Select'
+ '"Create" button should not be available in the modal');
+
+ form.destroy();
+ });
+
+ QUnit.test('many2many list (non editable): edition', function (assert) {
+ assert.expect(9);
+
+ this.data.partner.records[0].timmy = [12, 14];
+ this.data.partner_type.fields.float_field = {string: 'Float', type: 'float'};
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="timmy">' +
+ '<tree>' +
+ '<field name="display_name"/><field name="float_field"/>' +
+ '</tree>' +
+ '<form string="Partners">' +
+ '<field name="display_name"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(!form.$('.o_list_record_delete').length,
+ 'delete icon should not be visible in readonly');
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" should not be visible in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2,
+ 'should contain 2 records');
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold',
+ 'display_name of first subrecord should be the one in DB');
+ assert.ok(form.$('.o_list_record_delete').length,
+ 'delete icon should be visible in edit');
+ assert.ok(form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" should be visible in edit');
+
+ // edit existing subrecord
+ form.$('.o_list_view tbody tr:first()').click();
+
+ $('.modal .o_form_view .o_form_input').val('new name').trigger('input');
+ $('.modal .modal-footer .btn-primary').click(); // save
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
+ 'value of subrecord should have been updated');
+
+ // create new subrecords
+ // TODO when 'Add an item' will be implemented
+
+ // delete subrecords
+ form.$('.o_list_record_delete:first()').click();
+ assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 1,
+ 'should contain 1 subrecord');
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'silver',
+ 'the remaining subrecord should be "aaa"');
+ form.destroy();
+ });
+
+ QUnit.test('many2many list (editable): edition', function (assert) {
+ assert.expect(11);
+
+ this.data.partner.records[0].timmy = [12, 14];
+ this.data.partner_type.fields.float_field = {string: 'Float', type: 'float'};
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="timmy">' +
+ '<tree editable="top">' +
+ '<field name="display_name"/><field name="float_field"/>' +
+ '</tree>' +
+ '<form string="Partners">' +
+ '<field name="display_name"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(!form.$('.o_list_record_delete').length,
+ 'delete icon should not be visible in readonly');
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" should not be visible in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2,
+ 'should contain 2 records');
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold',
+ 'display_name of first subrecord should be the one in DB');
+ assert.ok(form.$('.o_list_record_delete').length,
+ 'delete icon should be visible in edit');
+ assert.ok(form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" should not visible in edit');
+
+ // edit existing subrecord
+ form.$('.o_list_view tbody td:first()').click();
+ assert.ok(!$('.modal').length,
+ 'in edit, clicking on a subrecord should not open a dialog');
+ assert.ok(form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'),
+ 'first row should be in edition');
+ form.$('.o_list_view .o_form_input:first()').val('new name').trigger('input');
+ // FIXME: this doesn"t work for now as the x2many fields are reset on field changed
+ // form.$('.o_list_view tbody td:nth(1)').click(); // click on second record to validate the first one
+ // assert.ok(!form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'),
+ // 'first row should not be in edition anymore');
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
+ 'value of subrecord should have been updated');
+
+ // add new subrecords
+ // TODO when 'Add an item' will be implemented
+
+ // delete subrecords
+ form.$('.o_list_record_delete:first()').click();
+ assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 1,
+ 'should contain 1 subrecord');
+ assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'silver',
+ 'the remaining subrecord should be "aaa"');
+ form.destroy();
+ });
+
+ QUnit.test('many2many list: create action disabled', function (assert) {
+ assert.expect(2);
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="timmy">' +
+ '<tree create="0">' +
+ '<field name="name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" link should not be available in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.ok(!form.$('.o_form_field_x2many_list_row_add').length,
+ '"Add an item" link should not be available in edit either');
+
+ form.destroy();
+ });
+
+ 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.args[1].length;
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ });
+
+ assert.strictEqual(count, 1, 'once search_read should have been done to fetch the relational values');
+ assert.strictEqual(nb_fields_fetched, 1, 'search_read should only fetch field id');
+ assert.strictEqual(form.$('.o_statusbar_status button:not(.dropdown-toggle)').length, 2, "should have 2 status");
+ assert.strictEqual(form.$('.o_statusbar_status button:disabled').length, 2,
+ "all status should be disabled");
+ assert.ok(form.$('.o_statusbar_status button[data-value="4"]').hasClass('btn-primary'),
+ "selected status should be btn-primary");
+
+ form.destroy();
+ });
+
+ QUnit.test('static statusbar widget on many2one field with domain', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<header><field name="trululu" domain="[(\'user_id\',\'=\',uid)]" widget="statusbar"/></header>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'search_read') {
+ assert.deepEqual(args.args[0], ['|', ['id', '=', 4], ['user_id', '=', 17]],
+ "search_read should sent the correct domain");
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ session: {user_context: {uid: 17}},
+ });
+
+ form.destroy();
+ });
+
+ QUnit.test('clickable statusbar widget on many2one field', function (assert) {
+ assert.expect(3);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<header><field name="trululu" widget="statusbar" clickable="True"/></header>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(form.$('.o_statusbar_status button[data-value="4"]').hasClass('btn-primary disabled'),
+ "selected status should be btn-primary and disabled");
+ var $clickable = form.$('.o_statusbar_status button.btn-default:not(.dropdown-toggle):not(:disabled)');
+ assert.strictEqual($clickable.length, 2,
+ "other status should be btn-default and not disabled");
+ $clickable.last().click(); // (last is visually the first here (css))
+ assert.ok(form.$('.o_statusbar_status button[data-value="1"]').hasClass("btn-primary disabled"),
+ "value should have been updated");
+
+ form.destroy();
+ });
+
+ QUnit.test('statusbar with no status', function (assert) {
+ assert.expect(2);
+
+ this.data.product.records = [];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<header><field name="product_id" widget="statusbar"/></header>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.ok(form.$('.o_statusbar_status').hasClass('o_form_field_empty'),
+ 'statusbar widget should have class o_form_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>',
+ });
+
+ assert.strictEqual(form.$('.o_statusbar_status button:disabled').length, 2, "should have 2 status");
+
+ form.destroy();
+ });
+
+ QUnit.test('clickable statusbar should change m2o fetching domain in edit mode', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.fields.trululu.domain = "[('bar', '=', True)]";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form string="Partners">' +
+ '<header><field name="trululu" widget="statusbar" clickable="True"/></header>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ var $buttons = form.$('.o_statusbar_status button:not(.dropdown-toggle)');
+ assert.strictEqual($buttons.length, 3, "there should be 3 status");
+ $buttons.last().click(); // (last is visually the first here (css))
+ assert.strictEqual(form.$('.o_statusbar_status button:not(.dropdown-toggle)').length, 2,
+ "there should be 2 status left");
+
+ form.destroy();
+ });
+
+ QUnit.test('statusbar fold_field option and statusbar_visible attribute', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].bar = false;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form string="Partners">' +
+ '<header><field name="trululu" widget="statusbar" options="{\'fold_field\': \'bar\'}"/>' +
+ '<field name="color" widget="statusbar" statusbar_visible="red"/></header>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_statusbar_status:first .dropdown-menu button.disabled').length, 1, "should have 1 folded status");
+ assert.strictEqual(form.$('.o_statusbar_status:last button.disabled').length, 1, "should have 1 status (other discarded)");
+
+ form.destroy();
+ });
+
+ QUnit.test('statusbar with dynamic domain', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.fields.trululu.domain = "[('int_field', '>', qux)]";
+ this.data.partner.records[2].int_field = 0;
+
+ var rpcCount = 0;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form string="Partners">' +
+ '<header><field name="trululu" widget="statusbar"/></header>' +
+ '<field name="qux"/>' +
+ '<field name="foo"/>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'search_read') {
+ rpcCount++;
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_statusbar_status button.disabled').length, 3, "should have 3 status");
+ assert.strictEqual(rpcCount, 1, "should have done 1 search_read rpc");
+ form.$('input:first').val(9.5).trigger("input").trigger("change");
+ assert.strictEqual(form.$('.o_statusbar_status button.disabled').length, 2, "should have 2 status");
+ assert.strictEqual(rpcCount, 2, "should have done 1 more search_read rpc");
+ form.$('input:last').val("hey").trigger("input").trigger("change");
+ assert.strictEqual(rpcCount, 2, "should not have done 1 more search_read rpc");
+
+ form.destroy();
+ });
+
+ QUnit.module('FieldSelection');
+
+ QUnit.test('widget selection in a list view', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records.forEach(function (r) {
+ r.color = 'red';
+ });
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree string="Colors" editable="top">' +
+ '<field name="color"/>' +
+ '</tree>',
+ });
+
+ assert.strictEqual(list.$('td:contains(Red)').length, 3,
+ "should have 3 rows with correct value");
+ list.$('td:contains(Red):first').click();
+
+ var $td = list.$('tbody tr.o_selected_row td:not(.o_list_record_selector)');
+
+ assert.strictEqual($td.find('select').length, 1, "td should have a child 'select'");
+ assert.strictEqual($td.contents().length, 1, "select tag should be only child of td");
+ list.destroy();
+ });
+
+ QUnit.test('widget selection, edition and on many2one field', function (assert) {
+ assert.expect(15);
+
+ 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");
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('select').length, 3,
+ "should have 3 select tag in dom");
+ assert.strictEqual(form.$('select[name="product_id"] option:contains(xphone)').length, 1,
+ "should have fetched xphone option");
+ assert.strictEqual(form.$('select[name="product_id"] option:contains(xpad)').length, 1,
+ "should have fetched xpad option");
+ assert.strictEqual(form.$('select[name="product_id"]').val(), "37",
+ "should have correct product_id value");
+ assert.strictEqual(form.$('select[name="trululu"]').val(), "false",
+ "should not have any value in trululu field");
+ form.$('select[name="product_id"]').val(41).trigger('change');
+
+ assert.strictEqual(form.$('select[name="product_id"]').val(), "41",
+ "should have a value of xphone");
+
+ assert.strictEqual(form.$('select[name="color"]').val(), "\"red\"",
+ "should have correct value in color field");
+
+ assert.verifySteps(['read', 'name_search', 'name_search', 'onchange']);
+ count = 0;
+ form.reload();
+ assert.strictEqual(count, 1, "should not reload product_id relation");
+ form.destroy();
+ });
+
+ QUnit.module('FieldMany2ManyTags');
+
+ QUnit.test('fieldmany2many tags: rendering and edition', function (assert) {
+ assert.expect(17);
+
+ 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="{\'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]");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 2,
+ "should contain 2 tags");
+ assert.ok(form.$('span:contains(gold)').length,
+ 'should have fetched and rendered gold partner tag');
+ assert.ok(form.$('span:contains(silver)').length,
+ 'should have fetched and rendered silver partner tag');
+ assert.strictEqual(form.$('span:first()').data('color'), 2,
+ 'should have correctly fetched the color');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 2,
+ "should still contain 2 tags in edit mode");
+ assert.ok(form.$('.o_tag_color_2 .o_badge_text:contains(gold)').length,
+ 'first tag should still contain "gold" and be color 2 in edit mode');
+ assert.strictEqual(form.$('.o_form_field_many2manytags .o_delete').length, 2,
+ "tags should contain a delete button");
+
+ // add an other existing tag
+ var $input = form.$('.o_form_field_many2manytags input');
+ $input.click(); // opens the dropdown
+ assert.strictEqual($input.autocomplete('widget').find('li').length, 1,
+ "autocomplete dropdown should have 1 entry");
+ assert.strictEqual($input.autocomplete('widget').find('li a:contains("red")').length, 1,
+ "autocomplete dropdown should contain 'red'");
+ $input.autocomplete('widget').find('li').click(); // add 'red'
+ assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 3,
+ "should contain 3 tags");
+ assert.ok(form.$('.o_form_field_many2manytags > span:contains("red")').length,
+ "should contain newly added tag 'red'");
+ assert.ok(form.$('.o_form_field_many2manytags > span[data-color=8]:contains("red")').length,
+ "should have fetched the color of added tag");
+
+ // remove tag with id 14
+ form.$('.o_form_field_many2manytags span[data-id=14] .o_delete').click();
+ assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 2,
+ "should contain 2 tags");
+ assert.ok(!form.$('.o_form_field_many2manytags > span:contains("silver")').length,
+ "should not contain tag 'silver' anymore");
+
+ // save the record (should do the write RPC with the correct commands)
+ form.$buttons.find('.o_form_button_save').click();
+
+ // TODO: it would be nice to test the behaviors of the autocomplete dropdown
+ // (like refining the research, creating new tags...), but ui-autocomplete
+ // makes it difficult to test
+ form.destroy();
+ });
+
+ QUnit.test('fieldmany2many tags view a domain', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.fields.timmy.domain = [['id', '<', 50]];
+ this.data.partner.records[0].timmy = [12];
+ this.data.partner_type.records.push({id: 99, display_name: "red", color: 8});
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="timmy" widget="many2many_tags" options="{\'no_create_edit\': True}"/>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'name_search') {
+ assert.deepEqual(args.kwargs.args, [['id', '<', 50], ['id', 'not in', [12]]],
+ "domain sent to name_search should be correct");
+ return $.when([[14, 'silver']]);
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+ assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 1,
+ "should contain 1 tag");
+ assert.ok(form.$('span:contains(gold)').length,
+ 'should have fetched and rendered gold partner tag');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // add an other existing tag
+ var $input = form.$('.o_form_field_many2manytags input');
+ $input.click(); // opens the dropdown
+ assert.strictEqual($input.autocomplete('widget').find('li').length, 1,
+ "autocomplete dropdown should have 1 entry");
+ assert.strictEqual($input.autocomplete('widget').find('li a:contains("silver")').length, 1,
+ "autocomplete dropdown should contain 'silver'");
+ $input.autocomplete('widget').find('li').click(); // add 'silver'
+ assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 2,
+ "should contain 2 tags");
+ assert.ok(form.$('.o_form_field_many2manytags > span:contains("silver")').length,
+ "should contain newly added tag 'silver'");
+
+ form.destroy();
+ });
+
+ QUnit.test('fieldmany2many tags in a new record', function (assert) {
+ assert.expect(7);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="timmy" widget="many2many_tags"/>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/create') {
+ var commands = args.args[0].timmy;
+ assert.strictEqual(commands.length, 1, "should have generated one command");
+ assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH");
+ assert.ok(_.isEqual(commands[0][2], [12]), "new value should be [12]");
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+ assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), "form should be in edit mode");
+
+ var $input = form.$('.o_form_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_form_field_many2manytags > span').length, 1,
+ "should contain 1 tag");
+ assert.ok(form.$('.o_form_field_many2manytags > span:contains("gold")').length,
+ "should contain newly added tag 'gold'");
+
+ // save the record (should do the write RPC with the correct commands)
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+
+ QUnit.test('fieldmany2many tags: update color', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].timmy = [12, 14];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="timmy" widget="many2many_tags"/>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ // Update the color in readonly
+ form.$('span:first()').click();
+ $('.o_colorpicker span[data-color="1"]').trigger('mousedown'); // choose color 1
+ assert.strictEqual(form.$('span:first()').data('color'), 1,
+ 'should have correctly updated the color (in readonly)');
+
+ // Update the color in edit
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('span:first()').click();
+ $('.o_colorpicker span[data-color="6"]').trigger('mousedown'); // choose color 6
+ assert.strictEqual(form.$('span:first()').data('color'), 6,
+ 'should have correctly updated the color (in edit)');
+ form.destroy();
+ });
+
+ QUnit.test('fieldmany2many tags in editable list', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records[0].timmy = [12];
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch:'<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="timmy" widget="many2many_tags"/>' +
+ '</tree>',
+ });
+
+ assert.strictEqual(list.$('.o_data_row:first .o_form_field_many2manytags .badge').length, 1,
+ "m2m field should contain one tag");
+
+ // edit first row
+ list.$('.o_data_row:first td:nth(2)').click();
+
+ var $m2o = list.$('.o_data_row:first .o_form_field_many2manytags .o_form_field_many2one');
+ assert.strictEqual($m2o.length, 1, "a many2one widget should have been instantiated");
+
+ // add a tag
+ var $input = $m2o.find('input');
+ $input.click();
+ $input.autocomplete('widget').find('li:first()').click(); // adds a tag
+
+ assert.strictEqual(list.$('.o_data_row:first .o_form_field_many2manytags .badge').length, 2,
+ "m2m field should contain 2 tags");
+ assert.ok(list.$('.o_data_row:first .o_many2many_tags_cell').hasClass('o_field_dirty'),
+ "edited cell should have class o_field_dirty");
+
+ // leave edition
+ list.$('.o_data_row:nth(1) td:nth(2)').click();
+
+ assert.strictEqual(list.$('.o_data_row:first .o_form_field_many2manytags .badge').length, 2,
+ "m2m field should contain 2 tags");
+
+ list.destroy();
+ });
+
+ QUnit.module('FieldRadio');
+
+ QUnit.test('fieldradio widget on a many2one in a new record', function (assert) {
+ assert.expect(6);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="product_id" widget="radio"/>' +
+ '</form>',
+ });
+
+ assert.ok(form.$('div.o_radio_item').length, "should have rendered outer div");
+ assert.strictEqual(form.$('input.o_form_radio').length, 2, "should have 2 possible choices");
+ assert.ok(form.$('label.o_form_label:contains(xphone)').length, "one of them should be xphone");
+ assert.strictEqual(form.$('input:checked').length, 0, "none of the input should be checked");
+
+ form.$("input.o_form_radio:first").click();
+
+ assert.strictEqual(form.$('input:checked').length, 1, "one of the input should be checked");
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ var newRecord = _.last(this.data.partner.records);
+ assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value");
+ form.destroy();
+ });
+
+ QUnit.test('fieldradio change value by onchange', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.onchanges = {bar: function (obj) {
+ obj.product_id = obj.bar ? 41 : 37;
+ obj.color = obj.bar ? 'red' : 'black';
+ }};
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="bar"/>' +
+ '<field name="product_id" widget="radio"/>' +
+ '<field name="color" widget="radio"/>' +
+ '</form>',
+ });
+
+ form.$("input[type='checkbox']").click();
+ assert.strictEqual(form.$('input.o_form_radio[data-value="37"]:checked').length, 1, "one of the input should be checked");
+ assert.strictEqual(form.$('input.o_form_radio[data-value="black"]:checked').length, 1, "the other of the input should be checked");
+ form.$("input[type='checkbox']").click();
+ assert.strictEqual(form.$('input.o_form_radio[data-value="41"]:checked').length, 1, "the other of the input should be checked");
+ assert.strictEqual(form.$('input.o_form_radio[data-value="red"]:checked').length, 1, "one of the input should be checked");
+
+ form.destroy();
+ });
+
+
+ QUnit.test('fieldradio widget on a selection in a new record', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="color" widget="radio"/>' +
+ '</form>',
+ });
+
+
+ assert.ok(form.$('div.o_radio_item').length, "should have rendered outer div");
+ assert.strictEqual(form.$('input.o_form_radio').length, 2, "should have 2 possible choices");
+ assert.ok(form.$('label.o_form_label:contains(Red)').length, "one of them should be Red");
+
+ // click on 2nd option
+ form.$("input.o_form_radio").eq(1).click();
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ var newRecord = _.last(this.data.partner.records);
+ assert.strictEqual(newRecord.color, 'black', "should have saved record with correct value");
+ form.destroy();
+ });
+
+ QUnit.module('FieldMany2ManyCheckBoxes');
+
+ QUnit.test('widget many2many_checkboxes', function (assert) {
+ assert.expect(6);
+
+ this.data.partner.records[0].timmy = [12];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<group><field name="timmy" widget="many2many_checkboxes"/></group>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('div.o_form_field div.o_checkbox').length, 2,
+ "should have fetched and displayed the 2 values of the many2many");
+
+ assert.ok(form.$('div.o_form_field div.o_checkbox input').eq(0).prop('checked'),
+ "first checkbox should be checked");
+ assert.notOk(form.$('div.o_form_field div.o_checkbox input').eq(1).prop('checked'),
+ "second checkbox should not be checked");
+
+ assert.ok(form.$('div.o_form_field div.o_checkbox input').prop('disabled'),
+ "the checkboxes should be disabled");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.notOk(form.$('div.o_form_field div.o_checkbox input').prop('disabled'),
+ "the checkboxes should not be disabled");
+
+ form.$('div.o_form_field div.o_checkbox input').eq(1).click();
+ form.$buttons.find('.o_form_button_save').click();
+ assert.deepEqual(this.data.partner.records[0].timmy, [12, 14],
+ "should have added the second element to the many2many");
+
+ form.destroy();
+ });
+});
+});
+});