Files
odoo_source/addons/web/static/tests/fields/basic_fields_tests.js
T
40dd121938 [REF] web: move ControlPanel inside controllers/actions
This branch introduces a large-scale reorganization of the
component tree generated by the web client.  The short version is
that now, the control panel is a child of the view controller and
no longer a sibling. Graphically (and simplified), we go from
this:

                  webClient
                 /    |    \
               ...   ...  actionManager
                           /         \
                   controlPanel   viewController
                                    /       \

to this:

           	  webClient
                 /    |    \
               ...   ...  actionManager
                               |
                          viewController
                            /   |   \
          ControlPanelController
              /           \
         CPRenderer     CPModel

The motivation is that this work moves the code where it should be.
Before this commit, it was kind of weird to have code in the
controllers to render buttons outside of their root node (in
renderButtons).  Also, the action manager had to take care of
coordinating search view states between view transitions.

So, this work simplifies the code.  It also makes it easier to
extend. We see day after day that Odoo needs to take care of more
complex UI needs, and in many cases, these needs were quite
difficult to implement (we prefer spaghettis in our plates, not in
our code).  The changes in this branch should open the way to
implement these features.

For example,
- it will now be easy to add the possibility of views (for example,
  the search view or a new ControlPanel view) to add custom buttons
  (of type action or object) in the control panel.
- Another need will be to serialize/ restore the state of the
  search view across action boundaries (needed by the dashboard).
- Another example is the possibility for views to customize easily
  the presence/absence of sub menus (filters/groupbys/favorites/
  time range/...)
- Another need is an easier way for views/client action to customize
  their control panel.

This commit also contains a large rewrite of the search view (so it
is more inline with our architecture and easier to maintain).

Part of task 1893568

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: Martin Geubelle <mge@odoo.com>
2018-12-07 13:16:06 +00:00

5477 lines
225 KiB
JavaScript

odoo.define('web.basic_fields_tests', function (require) {
"use strict";
var basicFields = require('web.basic_fields');
var concurrency = require('web.concurrency');
var config = require('web.config');
var core = require('web.core');
var FormView = require('web.FormView');
var KanbanView = require('web.KanbanView');
var ListView = require('web.ListView');
var session = require('web.session');
var testUtils = require('web.test_utils');
var field_registry = require('web.field_registry');
var createView = testUtils.createView;
var createAsyncView = testUtils.createAsyncView;
var DebouncedField = basicFields.DebouncedField;
var JournalDashboardGraph = basicFields.JournalDashboardGraph;
var _t = core._t;
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, trim: true},
bar: {string: "Bar", type: "boolean", default: true, searchable: true},
txt: {string: "txt", type: "text", default: "My little txt Value\nHo-ho-hoooo Merry Christmas"},
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']]},
document: {string: "Binary", type: "binary"},
},
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',
document: 'coucou==\n',
}, {
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('DebouncedField');
QUnit.test('debounced fields do not trigger call _setValue once destroyed', function (assert) {
var done = assert.async();
assert.expect(4);
var def = $.Deferred();
var _doAction = DebouncedField.prototype._doAction;
DebouncedField.prototype._doAction = function () {
_doAction.apply(this, arguments);
def.resolve();
};
var _setValue = DebouncedField.prototype._setValue;
DebouncedField.prototype._setValue = function () {
assert.step('_setValue');
_setValue.apply(this, arguments);
};
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,
fieldDebounce: 3,
viewOptions: {
mode: 'edit',
},
});
// change the value
testUtils.fields.editInput(form.$('input[name=foo]'), 'new value');
assert.verifySteps([], "_setValue shouldn't have been called yet");
// save
testUtils.form.clickSave(form);
assert.verifySteps(['_setValue'], "_setValue should have been called once");
// destroy the form view
def = $.Deferred();
form.destroy();
// wait for the debounced callback to be called
def.then(function () {
assert.verifySteps(['_setValue'],
"_setValue should not have been called after widget destruction");
DebouncedField.prototype._doAction = _doAction;
DebouncedField.prototype._setValue = _setValue;
done();
});
});
QUnit.module('FieldBoolean');
QUnit.test('boolean field in form view', function (assert) {
assert.expect(12);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form><label for="bar" string="Awesome checkbox"/><field name="bar"/></form>',
res_id: 1,
});
assert.containsOnce(form, '.o_field_boolean input:checked',
"checkbox should be checked");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_boolean input:checked',
"checkbox should still be checked");
// uncheck the checkbox
testUtils.dom.click(form.$('.o_field_boolean input:checked'));
assert.containsNone(form, '.o_field_boolean input:checked',
"checkbox should no longer be checked");
// save
testUtils.form.clickSave(form);
assert.containsNone(form, '.o_field_boolean input:checked',
"checkbox should still no longer be checked");
// switch to edit mode and test the opposite change
testUtils.form.clickEdit(form);
assert.containsNone(form, '.o_field_boolean input:checked',
"checkbox should still be unchecked");
// check the checkbox
testUtils.dom.click(form.$('.o_field_boolean input'));
assert.containsOnce(form, '.o_field_boolean input:checked',
"checkbox should now be checked");
// uncheck it back
testUtils.dom.click(form.$('.o_field_boolean input'));
assert.containsNone(form, '.o_field_boolean input:checked',
"checkbox should now be unchecked");
// check the checkbox by clicking on label
testUtils.dom.click(form.$('.o_form_view label:first'));
assert.containsOnce(form, '.o_field_boolean input:checked',
"checkbox should now be checked");
// uncheck it back
testUtils.dom.click(form.$('.o_form_view label:first'));
assert.containsNone(form, '.o_field_boolean input:checked',
"checkbox should now 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.containsOnce(form, '.o_field_boolean input:checked',
"checkbox should now be checked");
// blindly press enter again, it should uncheck the checkbox
$(document.activeElement).trigger({type: "keydown", which: $.ui.keyCode.ENTER});
assert.containsNone(form, '.o_field_boolean input:checked',
"checkbox should not be checked");
// blindly press enter again, it should check the checkbox back
$(document.activeElement).trigger({type: "keydown", which: $.ui.keyCode.ENTER});
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.o_field_boolean input:checked',
"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) .custom-checkbox input').length, 5,
"should have 5 checkboxes");
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input:checked').length, 4,
"should have 4 checked input");
// Edit a line
var $cell = list.$('tr.o_data_row:has(.custom-checkbox input:checked) td:not(.o_list_record_selector)').first();
assert.ok($cell.find('.custom-checkbox input:checked').prop('disabled'),
"input should be disabled in readonly mode");
testUtils.dom.click($cell);
assert.ok(!$cell.find('.custom-checkbox input:checked').prop('disabled'),
"input should not have the disabled property in edit mode");
testUtils.dom.click($cell.find('.custom-checkbox input:checked'));
// save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
$cell = list.$('tr.o_data_row:has(.custom-checkbox input:not(:checked)) td:not(.o_list_record_selector)').first();
assert.ok($cell.find('.custom-checkbox input:not(:checked)').prop('disabled'),
"input should be disabled again");
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input').length, 5,
"should still have 5 checkboxes");
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input:checked').length, 3,
"should now have only 3 checked input");
// Re-Edit the line and fake-check the checkbox
testUtils.dom.click($cell);
testUtils.dom.click($cell.find('.custom-checkbox input'));
testUtils.dom.click($cell.find('.custom-checkbox input'));
// Save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input').length, 5,
"should still have 5 checkboxes");
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input:checked').length, 3,
"should still have only 3 checked input");
// Re-Edit the line to check the checkbox back but this time click on
// the checkbox directly in readonly mode !
$cell = list.$('tr.o_data_row:has(.custom-checkbox input:not(:checked)) td:not(.o_list_record_selector)').first();
testUtils.dom.click($cell.find('.custom-checkbox .custom-control-label'));
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input').length, 5,
"should still have 5 checkboxes");
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-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('FieldBooleanToggle');
QUnit.test('use boolean toggle widget in form view', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form><field name="bar" widget="boolean_toggle"/></form>',
res_id: 2,
});
assert.containsOnce(form, ".custom-checkbox.o_boolean_toggle", "Boolean toggle widget applied to boolean field");
form.destroy();
});
QUnit.module('FieldToggleButton');
QUnit.test('use toggle_button in list view', function (assert) {
assert.expect(6);
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree>' +
'<field name="bar" widget="toggle_button" ' +
'options="{&quot;active&quot;: &quot;Reported in last payslips&quot;, &quot;inactive&quot;: &quot;To Report in Payslip&quot;}"/>' +
'</tree>',
});
assert.containsN(list, 'button i.fa.fa-circle.o_toggle_button_success', 4,
"should have 4 green buttons");
assert.containsOnce(list, 'button i.fa.fa-circle.text-muted',
"should have 1 muted button");
assert.hasAttrValue(list.$('.o_list_view button').first(), 'title',
"Reported in last payslips", "active buttons should have proper tooltip");
assert.hasAttrValue(list.$('.o_list_view button').last(), 'title',
"To Report in Payslip", "inactive buttons should have proper tooltip");
// clicking on first button to check the state is properly changed
testUtils.dom.click(list.$('.o_list_view button').first());
assert.containsN(list, 'button i.fa.fa-circle.o_toggle_button_success', 3,
"should have 3 green buttons");
testUtils.dom.click(list.$('.o_list_view button').first());
assert.containsN(list, 'button i.fa.fa-circle.o_toggle_button_success', 4,
"should have 4 green buttons");
list.destroy();
});
QUnit.test('toggle_button in form view (edit mode)', function (assert) {
assert.expect(6);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="bar" widget="toggle_button" ' +
'options="{\'active\': \'Active value\', \'inactive\': \'Inactive value\'}"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.step('write');
}
return this._super.apply(this, arguments);
},
res_id: 2,
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual(form.$('.o_field_widget[name=bar] i.o_toggle_button_success:not(.text-muted)').length,
1, "should be green");
// click on the button to toggle the value
testUtils.dom.click(form.$('.o_field_widget[name=bar]'));
assert.strictEqual(form.$('.o_field_widget[name=bar] i.text-muted:not(.o_toggle_button_success)').length,
1, "should be gray");
assert.verifySteps([]);
// save
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget[name=bar] i.text-muted:not(.o_toggle_button_success)').length,
1, "should still be gray");
assert.verifySteps(['write']);
form.destroy();
});
QUnit.test('toggle_button in form view (readonly mode)', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="bar" widget="toggle_button" ' +
'options="{\'active\': \'Active value\', \'inactive\': \'Inactive value\'}"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.step('write');
}
return this._super.apply(this, arguments);
},
res_id: 2,
});
assert.strictEqual(form.$('.o_field_widget[name=bar] i.o_toggle_button_success:not(.text-muted)').length,
1, "should be green");
// click on the button to toggle the value
testUtils.dom.click(form.$('.o_field_widget[name=bar]'));
assert.strictEqual(form.$('.o_field_widget[name=bar] i.text-muted:not(.o_toggle_button_success)').length,
1, "should be gray");
assert.verifySteps(['write']);
form.destroy();
});
QUnit.module('FieldFloat');
QUnit.test('float field when unset', function (assert) {
assert.expect(2);
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.doesNotHaveClass(form.$('.o_field_widget'), 'o_field_empty',
'Non-set float field should be considered as 0.');
assert.strictEqual(form.$('.o_field_widget').text(), "0.000",
'Non-set float field should be considered as 0.');
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_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.doesNotHaveClass(form.$('.o_field_widget'), 'o_field_empty',
'Float field should be considered set for value 0.');
assert.strictEqual(form.$('.o_field_widget').first().text(), '0.000',
'The value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('input[name=qux]').val(), '0.000',
'The value should be rendered with correct precision.');
testUtils.fields.editInput(form.$('input[name=qux]'), '108.2458938598598');
assert.strictEqual(form.$('input[name=qux]').val(), '108.2458938598598',
'The value should not be formated yet.');
testUtils.fields.editInput(form.$('input[name=qux]'), '18.8958938598598');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').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.o_data_cell').filter(function () {return $(this).text() === '';});
assert.strictEqual(zeroValues.length, 1,
'Unset float values should be rendered as empty strings.');
// switch to edit mode
var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first();
testUtils.dom.click($cell);
assert.containsOnce(list, 'input[name="qux"]',
'The view should have 1 input for editable float.');
testUtils.fields.editInput(list.$('input[name="qux"]'), '108.2458938598598');
assert.strictEqual(list.$('input[name="qux"]').val(), '108.2458938598598',
'The value should not be formated yet.');
testUtils.fields.editInput(list.$('input[name="qux"]'), '18.8958938598598');
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
assert.strictEqual(list.$('.o_field_widget').first().text(), '18.896',
'The new value should be rounded properly.');
list.destroy();
});
QUnit.test('do not trigger a field_changed if they have not changed', function (assert) {
assert.expect(2);
this.data.partner.records[1].qux = false;
this.data.partner.records[1].int_field = false;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<field name="qux" widget="float" digits="[5,3]"/>' +
'<field name="int_field"/>' +
'</sheet>' +
'</form>',
res_id: 2,
mockRPC: function (route, args) {
assert.step(args.method);
return this._super.apply(this, arguments);
}
});
testUtils.form.clickEdit(form);
testUtils.form.clickSave(form);
assert.verifySteps(['read']); // should not have save as nothing changed
form.destroy();
});
QUnit.test('float widget on monetary field', function (assert) {
assert.expect(1);
this.data.partner.fields.monetary = {string: "Monetary", type: 'monetary'};
this.data.partner.records[0].monetary = 9.99;
this.data.partner.records[0].currency_id = 1;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<field name="monetary" widget="float"/>' +
'<field name="currency_id" invisible="1"/>' +
'</sheet>' +
'</form>',
res_id: 1,
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
assert.strictEqual(form.$('.o_field_widget[name=monetary]').text(), '9.99',
'value should be correctly formatted (with the float formatter)');
form.destroy();
});
QUnit.test('float field with monetary widget and decimal precision', function (assert) {
assert.expect(5);
this.data.partner.records = [{
id: 1,
qux: -8.89859,
currency_id: 1,
}];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<field name="qux" widget="monetary" options="{\'field_digits\': True}"/>' +
'<field name="currency_id" invisible="1"/>' +
'</sheet>' +
'</form>',
res_id: 1,
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
// Non-breaking space between the currency and the amount
assert.strictEqual(form.$('.o_field_widget').first().text(), '$\u00a0-8.9',
'The value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '-8.9',
'The input should be rendered without the currency symbol.');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().first().text(), '$',
'The input should be preceded by a span containing the currency symbol.');
testUtils.fields.editInput(form.$('.o_field_monetary input'), '109.2458938598598');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '109.2458938598598',
'The value should not be formated yet.');
testUtils.form.clickSave(form);
// Non-breaking space between the currency and the amount
assert.strictEqual(form.$('.o_field_widget').first().text(), '$\u00a0109.2',
'The new value should be rounded properly.');
form.destroy();
});
QUnit.test('float field with type number option', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="qux" options="{\'type\': \'number\'}"/>' +
'</form>',
res_id: 4,
translateParameters: {
thousands_sep: ",",
grouping: [3, 0],
},
});
testUtils.form.clickEdit(form);
assert.ok(form.$('.o_field_widget')[0].hasAttribute('type'),
'Float field with option type must have a type attribute.');
assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'number',
'Float field with option type must have a type attribute equals to "number".');
testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890');
testUtils.form.clickSave(form);
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget').val(), '123456.789',
'Float value must be not formatted if input type is number.');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), '123,456.8',
'Float value must be formatted in readonly view even if the input type is number.');
form.destroy();
});
QUnit.test('float field without type number option', function (assert) {
assert.expect(2);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="qux"/>' +
'</form>',
res_id: 4,
translateParameters: {
thousands_sep: ",",
grouping: [3, 0],
},
});
testUtils.form.clickEdit(form);
assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'text',
'Float field with option type must have a text type (default type).');
testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890');
testUtils.form.clickSave(form);
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget').val(), '123,456.8',
'Float value must be formatted if input type isn\'t number.');
form.destroy();
});
QUnit.module('FieldEmail');
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_field_widget.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.hasAttrValue($mailtoLink, 'href', 'mailto:yop',
"should have proper mailto prefix");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, 'input[type="text"].o_field_widget',
"should have an input for the email field");
assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop',
"input should contain field value in edit mode");
// change value in edit mode
testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new');
// save
testUtils.form.clickSave(form);
$mailtoLink = form.$('a.o_form_uri.o_field_widget.o_text_overflow');
assert.strictEqual($mailtoLink.text(), 'new',
"new value should be displayed properly");
assert.hasAttrValue($mailtoLink, '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.hasAttrValue($mailtoLink.first(), '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();
testUtils.dom.click($cell);
assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode');
assert.strictEqual($cell.find('input').val(), 'yop',
'should have the corect value in internal input');
testUtils.fields.editInput($cell.find('input'), 'new');
// save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
$cell = list.$('tbody td:not(.o_list_record_selector)').first();
assert.doesNotHaveClass($cell.parent(), 'o_selected_row', '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.hasAttrValue($mailtoLink.first(), '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 = _.find(form.renderer.allFieldWidgets)[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_field_widget').text(), 'yop',
"the value should be displayed properly");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, 'input[type="text"].o_field_widget',
"should have an input for the char field");
assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop',
"input should contain field value in edit mode");
// change value in edit mode
testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'limbo');
// save
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), 'limbo',
'the new value should be displayed');
form.destroy();
});
QUnit.test('setting a char field to empty string is saved as a false value', function (assert) {
assert.expect(1);
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,
viewOptions: {mode: 'edit'},
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.strictEqual(args.args[1].foo, false,
'the foo value should be false');
}
return this._super.apply(this, arguments);
}
});
testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), '');
// save
testUtils.form.clickSave(form);
form.destroy();
});
QUnit.test('char field with size attribute', function (assert) {
assert.expect(1);
this.data.partner.fields.foo.size = 5; // max length
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<group><field name="foo"/></group>' +
'</sheet>' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
assert.hasAttrValue(form.$('input.o_field_widget'), 'maxlength', '5',
"maxlength attribute should have been set correctly on the input");
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();
testUtils.dom.click($cell);
assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode');
assert.strictEqual($cell.find('input').val(), 'yop',
'should have the corect value in internal input');
testUtils.fields.editInput($cell.find('input'), 'brolo');
// save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
$cell = list.$('tbody td:not(.o_list_record_selector)').first();
assert.doesNotHaveClass($cell.parent(), 'o_selected_row', '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.test('char field translatable', function (assert) {
assert.expect(3);
this.data.partner.fields.foo.translate = true;
var multiLang = _t.database.multi_lang;
_t.database.multi_lang = true;
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,
mockRPC: function (route, args) {
if (route === "/web/dataset/call_button" && args.method === 'translate_fields') {
assert.deepEqual(args.args, ["partner",1,"foo",{}], 'should call "call_button" route');
return $.when();
}
return this._super.apply(this, arguments);
},
});
testUtils.form.clickEdit(form);
var $button = form.$('input[type="text"].o_field_char + .o_field_translate');
assert.strictEqual($button.length, 1, "should have a translate button");
testUtils.dom.click($button);
form.destroy();
form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="foo"/>' +
'</group>' +
'</sheet>' +
'</form>',
});
$button = form.$('input[type="text"].o_field_char + .o_field_translate');
assert.strictEqual($button.length, 0, "should not have a translate button in create mode");
form.destroy();
_t.database.multi_lang = multiLang;
});
QUnit.test('char field does not allow html injections', function (assert) {
assert.expect(1);
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,
viewOptions: {
mode: 'edit',
},
});
testUtils.fields.editInput(form.$('input[name=foo]'), '<script>throw Error();</script>');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), '<script>throw Error();</script>',
'the value should have been properly escaped');
form.destroy();
});
QUnit.test('char field trim (or not) characters', function (assert) {
assert.expect(2);
this.data.partner.fields.foo2 = {string: "Foo2", type: "char", trim: false};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="foo"/>' +
'<field name="foo2"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
testUtils.fields.editInput(form.$('input[name="foo"]'), ' abc ');
testUtils.fields.editInput(form.$('input[name="foo2"]'), ' def ');
testUtils.form.clickSave(form);
// edit mode
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('input[name="foo"]').val(), 'abc', 'Foo value should have been trimmed');
assert.strictEqual(form.$('input[name="foo2"]').val(), ' def ', 'Foo2 value should not have been trimmed');
form.destroy();
});
QUnit.test('input field: change value before pending onchange returns', function (assert) {
assert.expect(3);
this.data.partner.onchanges = {
product_id: function () {},
};
var def;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<field name="p">' +
'<tree editable="bottom">' +
'<field name="product_id"/>' +
'<field name="foo"/>' +
'</tree>' +
'</field>' +
'</sheet>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === "onchange") {
return $.when(def).then(function () {
return result;
});
} else {
return result;
}
},
viewOptions: {
mode: 'edit',
},
});
testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
assert.strictEqual(form.$('input[name="foo"]').val(), 'My little Foo Value',
'should contain the default value');
def = $.Deferred();
testUtils.fields.many2one.clickOpenDropdown('product_id');
testUtils.fields.many2one.clickHighlightedItem('product_id');
// set foo before onchange
testUtils.fields.editInput(form.$('input[name="foo"]'), "tralala");
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
'input should contain tralala');
// complete the onchange
def.resolve();
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
'input should contain the same value as before onchange');
form.destroy();
});
QUnit.test('input field: change value before pending onchange renaming', function (assert) {
assert.expect(3);
this.data.partner.onchanges = {
product_id: function (obj) {
obj.foo = 'on change value';
},
};
var def = $.Deferred();
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<field name="product_id"/>' +
'<field name="foo"/>' +
'</sheet>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
if (args.method === "onchange") {
return $.when(def).then(function () {
return result;
});
} else {
return result;
}
},
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual(form.$('input[name="foo"]').val(), 'yop',
'should contain the correct value');
testUtils.fields.many2one.clickOpenDropdown('product_id');
testUtils.fields.many2one.clickHighlightedItem('product_id');
// set foo before onchange
testUtils.fields.editInput(form.$('input[name="foo"]'), "tralala");
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
'input should contain tralala');
// complete the onchange
def.resolve();
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
'input should contain the same value as before onchange');
form.destroy();
});
QUnit.test('input field: change password value', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="foo" password="True"/>' +
'</form>',
res_id: 1,
});
assert.notEqual(form.$('.o_field_char').text(), "yop",
"password field value should not be visible in read mode");
assert.strictEqual(form.$('.o_field_char').text(), "***",
"password field value should be hidden with '*' in read mode");
testUtils.form.clickEdit(form);
assert.hasAttrValue(form.$('input.o_field_char'), 'type', 'password',
"password field input should be with type 'password' in edit mode");
assert.strictEqual(form.$('input.o_field_char').val(), 'yop',
"password field input value should be the (non-hidden) password value");
form.destroy();
});
QUnit.test('input field: empty password', function (assert) {
assert.expect(3);
this.data.partner.records[0].foo = false;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="foo" password="True"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_field_char').text(), "",
"password field value should be empty in read mode");
testUtils.form.clickEdit(form);
assert.hasAttrValue(form.$('input.o_field_char'), 'type', 'password',
"password field input should be with type 'password' in edit mode");
assert.strictEqual(form.$('input.o_field_char').val(), '',
"password field input value should be the (non-hidden, empty) password value");
form.destroy();
});
QUnit.module('UrlWidget');
QUnit.test('url widget in form view', function (assert) {
assert.expect(9);
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.containsOnce(form, 'a.o_form_uri.o_field_widget.o_text_overflow',
"should have a anchor with correct classes");
assert.hasAttrValue(form.$('a.o_form_uri.o_field_widget.o_text_overflow'), 'href', 'yop',
"should have proper href link");
assert.hasAttrValue(form.$('a.o_form_uri.o_field_widget.o_text_overflow'), 'target', '_blank',
"should have target attribute set to _blank");
assert.strictEqual(form.$('a.o_form_uri.o_field_widget.o_text_overflow').text(), 'yop',
"the value should be displayed properly");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, 'input[type="text"].o_field_widget',
"should have an input for the char field");
assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop',
"input should contain field value in edit mode");
// change value in edit mode
testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'limbo');
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, 'a.o_form_uri.o_field_widget.o_text_overflow',
"should still have a anchor with correct classes");
assert.hasAttrValue(form.$('a.o_form_uri.o_field_widget.o_text_overflow'), 'href', 'limbo',
"should have proper new href link");
assert.strictEqual(form.$('a.o_form_uri.o_field_widget.o_text_overflow').text(), 'limbo',
'the new value should be displayed');
form.destroy();
});
QUnit.test('url widget takes text from proper attribute', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="foo" widget="url" text="kebeclibre"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('a[name="foo"]').text(), 'kebeclibre',
"url text should come from the text attribute");
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.containsN(list, 'a.o_form_uri.o_field_widget.o_text_overflow', 5,
"should have 5 anchors with correct classes");
assert.hasAttrValue(list.$('a.o_form_uri.o_field_widget.o_text_overflow').first(), '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();
testUtils.dom.click($cell);
assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode');
assert.strictEqual($cell.find('input').val(), 'yop',
'should have the corect value in internal input');
testUtils.fields.editInput($cell.find('input'), 'brolo');
// save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
$cell = list.$('tbody td:not(.o_list_record_selector)').first();
assert.doesNotHaveClass($cell.parent(), 'o_selected_row', 'should not be in edit mode anymore');
assert.containsN(list, 'a.o_form_uri.o_field_widget.o_text_overflow', 5,
"should still have 5 anchors with correct classes");
assert.hasAttrValue(list.$('a.o_form_uri.o_field_widget.o_text_overflow').first(), '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('copy_clipboard', {
beforeEach: function () {
this.data = {
partner: {
fields: {
script_external: {string: "Script External", type: "text"},
web_page: {string: "Web page link", type: "char"}
},
records: [{
id: 1,
script_external:'Random Text',
web_page: 'web page links'
},],
},
};
}
});
QUnit.test('Char & Text Fields: Copy to clipboard button', function (assert) {
assert.expect(2);
var done = assert.async();
testUtils.createAsyncView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<div>' +
'<field name="script_external" widget="CopyClipboardText"/>' +
'<field name="web_page" widget="CopyClipboardChar"/>' +
'</div>' +
'</sheet>' +
'</form>',
}).then(function (form) {
assert.containsOnce(form, '.o_clipboard_button.o_btn_text_copy',"Should have copy button on text type field");
assert.containsOnce(form, '.o_clipboard_button.o_btn_char_copy',"Should have copy button on char type field");
form.destroy();
done();
});
});
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.$('.o_field_text').length, "should have a text area");
assert.strictEqual(form.$('.o_field_text').text(), 'yop', 'should be "yop" in readonly');
testUtils.form.clickEdit(form);
var $textarea = form.$('textarea.o_field_text');
assert.ok($textarea.length, "should have a text area");
assert.strictEqual($textarea.val(), 'yop', 'should still be "yop" in edit');
testUtils.fields.editInput($textarea, 'hello');
assert.strictEqual($textarea.val(), 'hello', 'should be "hello" after first edition');
testUtils.fields.editInput($textarea, 'hello world');
assert.strictEqual($textarea.val(), 'hello world', 'should be "hello world" after second edition');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_text').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.$('.o_field_text');
assert.strictEqual($field.outerHeight(), $field[0].scrollHeight,
"text field should not have a scroll bar");
testUtils.form.clickEdit(form);
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 fields in edit mode, no vertical resize', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="txt"/>' +
'</form>',
res_id: 1,
});
testUtils.form.clickEdit(form);
var $textarea = form.$('textarea:first');
assert.strictEqual($textarea.css('resize'), 'none',
"should not have vertical resize");
form.destroy();
});
QUnit.test('text fields in editable list have correct height', function (assert) {
assert.expect(2);
this.data.partner.records[0].txt = "a\nb\nc\nd\ne\nf";
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<list editable="top">' +
'<field name="foo"/>' +
'<field name="txt"/>' +
'</list>',
});
// Click to enter edit: in this test we specifically do not set
// the focus on the textarea by clicking on another column.
// The main goal is to test the resize is actually triggered in this
// particular case.
testUtils.dom.click(list.$('.o_data_cell:first'));
var $textarea = list.$('textarea:first');
// make sure the correct data is there
assert.strictEqual($textarea.val(), this.data.partner.records[0].txt);
// make sure there is no scroll bar
assert.strictEqual($textarea.innerHeight(), $textarea[0].scrollHeight,
"textarea should not have a scroll bar");
list.destroy();
});
QUnit.test('text fields in edit mode should resize on reset', function (assert) {
assert.expect(1);
this.data.partner.fields.foo.type = 'text';
this.data.partner.onchanges = {
bar: function (obj) {
obj.foo = 'a\nb\nc\nd\ne\nf';
},
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="bar"/>' +
'<field name="foo"/>' +
'</form>',
res_id: 1,
});
// edit the form
// trigger a textarea reset (through onchange) by clicking the box
// then check there is no scroll bar
testUtils.form.clickEdit(form);
testUtils.dom.click(form.$('div[name="bar"] input'));
var $textarea = form.$('textarea:first');
assert.strictEqual($textarea.innerHeight(), $textarea[0].scrollHeight,
"textarea should not have a scroll bar");
form.destroy();
});
QUnit.test('text field translatable', function (assert) {
assert.expect(3);
this.data.partner.fields.txt.translate = true;
var multiLang = _t.database.multi_lang;
_t.database.multi_lang = true;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="txt"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (route === "/web/dataset/call_button" && args.method === 'translate_fields') {
assert.deepEqual(args.args, ["partner",1,"txt",{}], 'should call "call_button" route');
return $.when();
}
return this._super.apply(this, arguments);
},
});
testUtils.form.clickEdit(form);
var $button = form.$('textarea + .o_field_translate');
assert.strictEqual($button.length, 1, "should have a translate button");
testUtils.dom.click($button);
form.destroy();
form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="txt"/>' +
'</group>' +
'</sheet>' +
'</form>',
});
$button = form.$('textarea + .o_field_translate');
assert.strictEqual($button.length, 0, "should not have a translate button in create mode");
form.destroy();
_t.database.multi_lang = multiLang;
});
QUnit.test('go to next line (and not the next row) when pressing enter', function (assert) {
assert.expect(4);
this.data.partner.fields.foo.type = 'text';
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<list editable="top">' +
'<field name="int_field"/>' +
'<field name="foo"/>' +
'<field name="qux"/>' +
'</list>',
});
testUtils.dom.click(list.$('tbody tr:first .o_list_text'));
var $textarea = list.$('textarea.o_field_text');
assert.strictEqual($textarea.length, 1, "should have a text area");
assert.strictEqual($textarea.val(), 'yop', 'should still be "yop" in edit');
assert.strictEqual(list.$('textarea').get(0), document.activeElement,
"text area should have the focus");
// click on enter
list.$('textarea')
.trigger({type: "keydown", which: $.ui.keyCode.ENTER})
.trigger({type: "keyup", which: $.ui.keyCode.ENTER});
assert.strictEqual(list.$('textarea').first().get(0), document.activeElement,
"text area should still have the focus");
list.destroy();
});
// Firefox-specific
// Copying from <div style="white-space:pre-wrap"> does not keep line breaks
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1390115
QUnit.test('copying text fields in RO mode should preserve line breaks', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="txt"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
// Copying from a div tag with white-space:pre-wrap doesn't work in Firefox
assert.strictEqual(form.$('[name="txt"]').prop("tagName").toLowerCase(), 'span',
"the field contents should be surrounded by a span tag");
form.destroy();
});
QUnit.module('FieldBinary');
QUnit.test('binary fields are correctly rendered', function (assert) {
assert.expect(16);
// save the session function
var oldGetFile = session.get_file;
session.get_file = function (option) {
assert.strictEqual(option.data.field, 'document',
"we should download the field document");
assert.strictEqual(option.data.data, 'coucou==\n',
"we should download the correct data");
option.complete();
return $.when();
};
this.data.partner.records[0].foo = 'coucou.txt';
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="document" filename="foo"/>' +
'<field name="foo"/>' +
'</form>',
res_id: 1,
});
assert.containsOnce(form, 'a.o_field_widget[name="document"] > .fa-download',
"the binary field should be rendered as a downloadable link in readonly");
assert.strictEqual(form.$('a.o_field_widget[name="document"]').text().trim(), 'coucou.txt',
"the binary field should display the name of the file in the link");
assert.strictEqual(form.$('.o_field_char').text(), 'coucou.txt',
"the filename field should have the file name as value");
testUtils.dom.click(form.$('a.o_field_widget[name="document"]'));
testUtils.form.clickEdit(form);
assert.containsNone(form, 'a.o_field_widget[name="document"] > .fa-download',
"the binary field should not be rendered as a downloadable link in edit");
assert.strictEqual(form.$('div.o_field_binary_file[name="document"] > input').val(), 'coucou.txt',
"the binary field should display the file name in the input edit mode");
assert.hasAttrValue(form.$('.o_field_binary_file > input'), 'readonly', 'readonly',
"the input should be readonly");
assert.containsOnce(form, '.o_field_binary_file > .o_clear_file_button',
"there shoud be a button to clear the file");
assert.strictEqual(form.$('input.o_field_char').val(), 'coucou.txt',
"the filename field should have the file name as value");
testUtils.dom.click(form.$('.o_field_binary_file > .o_clear_file_button'));
assert.isNotVisible(form.$('.o_field_binary_file > input'),
"the input should be hidden");
assert.strictEqual(form.$('.o_field_binary_file > .o_select_file_button:not(.o_hidden)').length, 1,
"there shoud be a button to upload the file");
assert.strictEqual(form.$('input.o_field_char').val(), '',
"the filename field should be empty since we removed the file");
testUtils.form.clickSave(form);
assert.containsNone(form, 'a.o_field_widget[name="document"] > .fa-download',
"the binary field should not render as a downloadable link since we removed the file");
assert.strictEqual(form.$('a.o_field_widget[name="document"]').text().trim(), '',
"the binary field should not display a filename in the link since we removed the file");
assert.strictEqual(form.$('.o_field_char').text().trim(), '',
"the filename field should be empty since we removed the file");
form.destroy();
// restore the session function
session.get_file = oldGetFile;
});
QUnit.test('binary fields that are readonly in create mode do not download', function (assert) {
assert.expect(2);
// save the session function
var oldGetFile = session.get_file;
session.get_file = function (option) {
assert.step('We shouldn\'t be getting the file.');
return oldGetFile.bind(session)(option);
};
this.data.partner.onchanges = {
product_id: function (obj) {
obj.document = "onchange==\n";
},
};
this.data.partner.fields.document.readonly = true;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="product_id"/>' +
'<field name="document" filename="\'yooo\'"/>' +
'</form>',
res_id: 1,
});
testUtils.form.clickCreate(form);
testUtils.fields.many2one.clickOpenDropdown('product_id');
testUtils.fields.many2one.clickHighlightedItem('product_id');
assert.containsOnce(form, 'a.o_field_widget[name="document"] > .fa-download',
'The link to download the binary should be present');
testUtils.dom.click(form.$('a.o_field_widget[name="document"]'));
assert.verifySteps([]); // We shoudln't have passed through steps
form.destroy();
session.get_file = oldGetFile;
});
QUnit.module('FieldPdfViewer');
QUnit.test("pdf_viewer without data", function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form>' +
'<field name="document" widget="pdf_viewer"/>' +
'</form>',
});
assert.hasClass(form.$('.o_field_widget'), 'o_field_pdfviewer');
assert.strictEqual(form.$('.o_select_file_button:not(.o_hidden)').length, 1,
"there should be a visible 'Upload' button");
assert.isNotVisible(form.$('.o_field_widget iframe.o_pdfview_iframe'),
"there should be an invisible iframe");
assert.containsOnce(form, 'input[type="file"]',
"there should be one input");
form.destroy();
});
QUnit.test("pdf_viewer: basic rendering", function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
res_id: 1,
arch:
'<form>' +
'<field name="document" widget="pdf_viewer"/>' +
'</form>',
mockRPC: function (route) {
if (route.indexOf('/web/static/lib/pdfjs/web/viewer.html') !== -1) {
return $.when();
}
return this._super.apply(this, arguments);
}
});
assert.hasClass(form.$('.o_field_widget'), 'o_field_pdfviewer');
assert.strictEqual(form.$('.o_select_file_button:not(.o_hidden)').length, 0,
"there should not be a any visible 'Upload' button");
assert.isVisible(form.$('.o_field_widget iframe.o_pdfview_iframe'),
"there should be an visible iframe");
assert.hasAttrValue(form.$('.o_field_widget iframe.o_pdfview_iframe'), 'data-src',
'/web/static/lib/pdfjs/web/viewer.html?file=%2Fweb%2Fimage%3Fmodel%3Dpartner%26field%3Ddocument%26id%3D1#page=1',
"the src attribute should be correctly set on the iframe");
form.destroy();
});
QUnit.test("pdf_viewer: upload rendering", function (assert) {
assert.expect(6);
testUtils.mock.patch(field_registry.map.pdf_viewer, {
on_file_change: function (ev) {
ev.target = {files: [new Blob()]};
this._super.apply(this, arguments);
},
_getURI: function (fileURI) {
var res = this._super.apply(this, arguments);
assert.step('_getURI');
assert.ok(_.str.startsWith(fileURI, 'blob:'));
this.PDFViewerApplication = {
open: function (URI) {
assert.step('open');
assert.ok(_.str.startsWith(URI, 'blob:'));
},
};
return 'about:blank';
},
});
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form>' +
'<field name="document" widget="pdf_viewer"/>' +
'</form>',
});
// first upload initialize iframe
form.$('input[type="file"]').trigger('change');
assert.verifySteps(['_getURI']);
// second upload call pdfjs method inside iframe
form.$('input[type="file"]').trigger('change');
assert.verifySteps(['_getURI', 'open']);
testUtils.mock.unpatch(field_registry.map.pdf_viewer);
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>',
});
testUtils.dom.click(list.$buttons.find('.o_list_button_add'));
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();
};
testUtils.form.clickEdit(form);
testUtils.fields.editInput(form.$('textarea').first(), "1");
assert.strictEqual(nbNotifyChanges, 0,
"no event should have been triggered");
testUtils.fields.editInput(form.$('textarea').first(), "12");
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
testUtils.fields.editInput(form.$('textarea').first(), "123");
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('FieldImage');
QUnit.test('image fields are correctly rendered', function (assert) {
assert.expect(7);
this.data.partner.records[0].__last_update = '2017-02-08 10:00:00';
this.data.partner.records[0].document = 'myimage';
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="document" widget="image" options="{\'size\': [90, 90]}"/> ' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/read') {
assert.deepEqual(args.args[1], ['document', '__last_update', 'display_name'], "The fields document, display_name and __last_update should be present when reading an image");
}
if (route === 'data:image/png;base64,myimage') {
assert.ok(true, "should called the correct route");
return $.when('wow');
}
return this._super.apply(this, arguments);
},
});
assert.hasClass(form.$('div[name="document"]'),'o_field_image',
"the widget should have the correct class");
assert.containsOnce(form, 'div[name="document"] > img',
"the widget should contain an image");
assert.hasClass(form.$('div[name="document"] > img'),'img-fluid',
"the image should have the correct class");
assert.hasAttrValue(form.$('div[name="document"] > img'), 'width', "90",
"the image should correctly set its attributes");
assert.strictEqual(form.$('div[name="document"] > img').css('max-width'), "90px",
"the image should correctly set its attributes");
form.destroy();
});
QUnit.test('image fields in subviews are loaded correctly', function (assert) {
assert.expect(6);
this.data.partner.records[0].__last_update = '2017-02-08 10:00:00';
this.data.partner.records[0].document = 'myimage';
this.data.partner_type.fields.image = {name: 'image', type: 'binary'};
this.data.partner_type.records[0].image = 'product_image';
this.data.partner.records[0].timmy = [12];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="document" widget="image" options="{\'size\': [90, 90]}"/>' +
'<field name="timmy" widget="many2many">' +
'<tree>' +
'<field name="display_name"/>' +
'</tree>' +
'<form>' +
'<field name="image" widget="image"/>' +
'</form>' +
'</field>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (route === 'data:image/png;base64,myimage') {
assert.step("The view's image should have been fetched");
return $.when('wow');
}
if (route === 'data:image/png;base64,product_image') {
assert.step("The dialog's image should have been fetched");
return $.when();
}
return this._super.apply(this, arguments);
},
});
assert.verifySteps(["The view's image should have been fetched"]);
assert.containsOnce(form, 'tr.o_data_row',
'There should be one record in the many2many');
// Actual flow: click on an element of the m2m to get its form view
testUtils.dom.click(form.$('tbody td:contains(gold)'));
assert.strictEqual($('.modal').length, 1,
'The modal should have opened');
assert.verifySteps([
"The view's image should have been fetched",
"The dialog's image should have been fetched",
]);
form.destroy();
});
QUnit.test('image fields in x2many list are loaded correctly', function (assert) {
assert.expect(2);
this.data.partner_type.fields.image = {name: 'image', type: 'binary'};
this.data.partner_type.records[0].image = 'product_image';
this.data.partner.records[0].timmy = [12];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="timmy" widget="many2many">' +
'<tree>' +
'<field name="image" widget="image"/>' +
'</tree>' +
'</field>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (route === 'data:image/png;base64,product_image') {
assert.ok(true, "The list's image should have been fetched");
return $.when();
}
return this._super.apply(this, arguments);
},
});
assert.containsOnce(form, 'tr.o_data_row',
'There should be one record in the many2many');
form.destroy();
});
QUnit.test('image fields with required attribute', function (assert) {
assert.expect(2);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="document" required="1" widget="image"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'create') {
throw new Error("Should not do a create RPC with unset required image field");
}
return this._super.apply(this, arguments);
},
});
testUtils.form.clickSave(form);
assert.hasClass(form.$('.o_form_view'),'o_form_editable',
"form view should still be editable");
assert.hasClass(form.$('.o_field_widget'),'o_field_invalid',
"image field should be displayed as invalid");
form.destroy();
});
QUnit.module('JournalDashboardGraph', {
beforeEach: function () {
_.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,
}]);
},
});
QUnit.test('graph dashboard widget attach/detach callbacks', function (assert) {
// This widget is rendered with nvd3, and nvd3 renders the graphs when
// the svg is in the DOM. The intent of this test is to determine when
// the field widgets are in the DOM, so that we can tell nvd3 to render
// them.
var done = assert.async();
assert.expect(6);
testUtils.mock.patch(JournalDashboardGraph, {
on_attach_callback: function () {
assert.step('on_attach_callback');
},
on_detach_callback: function () {
assert.step('on_detach_callback');
},
});
createAsyncView({
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]]],
}).then(function (kanban) {
assert.verifySteps([
'on_attach_callback',
'on_attach_callback'
]);
kanban.on_detach_callback();
assert.verifySteps([
'on_attach_callback',
'on_attach_callback',
'on_detach_callback',
'on_detach_callback'
]);
kanban.destroy();
testUtils.mock.unpatch(JournalDashboardGraph);
done();
});
});
QUnit.test('graph dashboard widget is rendered correctly', function (assert) {
var done = assert.async();
assert.expect(4);
var graph_key = JSON.parse(this.data.partner.records[0].graph_data)[0].key;
createAsyncView({
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]]],
}).then(function (kanban) {
// 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.
concurrency.delay(0).then(function () {
return concurrency.delay(0);
}).then(function () {
assert.strictEqual(kanban.$('.o_kanban_record:first() .o_graph_barchart svg').length, 1,
"graph of first record should be a barchart");
assert.strictEqual(kanban.$('.o_kanban_record:nth(1) .o_graph_linechart svg').length, 1,
"graph of second record should be a linechart");
var evt = document.createEvent("MouseEvents"); //taken ref from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent
evt.initMouseEvent("mouseover", true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
$('.discreteBar')[0].dispatchEvent(evt);
var tooltip = $('.nvtooltip').find('table').find('.key')[0].innerText.trim();
assert.equal(tooltip, graph_key, "graph tooltip should be generated ");
$('.nvtooltip').remove();
// force a re-rendering of the first record (to check if the
// previous rendered graph is correctly removed from the DOM)
var firstRecordState = kanban.model.get(kanban.handle).data[0];
return kanban.renderer.updateRecord(firstRecordState);
}).then(function () {
return concurrency.delay(0); // one graph is re-rendered
}).then(function () {
assert.strictEqual(kanban.$('.o_kanban_record:first() svg').length, 1,
"there should be only one rendered graph by record");
kanban.destroy();
done();
});
});
});
QUnit.test('graph dashboard widget does not need nv to be destroyed', function (assert) {
// this test ensures that the JournalDashboardGraph widget doesn't crash
// when being destroyed before nv has been loaded
assert.expect(2);
testUtils.mock.patch(basicFields.JournalDashboardGraph, {
destroy: function () {
assert.step('destroy');
var nv = window.nv;
delete window.nv;
this._super.apply(this, arguments);
window.nv = nv;
}
});
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]]],
});
kanban.destroy();
testUtils.mock.unpatch(basicFields.JournalDashboardGraph);
assert.verifySteps(['destroy']);
// Wait nvd3 to fully render the graph. If ommited, may slow down following tests.
return concurrency.delay(0);
});
QUnit.test('graph dashboard widget can be destroyed when nv is partially loaded', function (assert) {
// this test ensures that the JournalDashboardGraph widget doesn't crash
// when being destroyed before nv has been completely loaded
assert.expect(2);
testUtils.mock.patch(basicFields.JournalDashboardGraph, {
destroy: function () {
assert.step('destroy');
// nv is fully loaded only when nvd3.js has been loaded
// which happens sequentially after nv.d3.js
// we simulate this race condition with:
var offWindowResize = window.nv.utils.offWindowResize;
window.nv.utils.offWindowResize = undefined;
this._super.apply(this, arguments);
window.nv.utils.offWindowResize = offWindowResize;
},
});
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]]],
});
kanban.destroy();
testUtils.mock.unpatch(basicFields.JournalDashboardGraph);
assert.verifySteps(['destroy']);
// Wait nvd3 to fully render the graph. If ommited, may slow down following tests.
return concurrency.delay(0);
});
QUnit.test('rendering of a field with dashboard_graph widget in an updated kanban view (ungrouped)', function (assert) {
var done = assert.async();
assert.expect(2);
createAsyncView({
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]]],
}).then(function (kanban) {
concurrency.delay(0).then(function () {
return concurrency.delay(0);
}).then(function () {
assert.containsN(kanban, '.o_dashboard_graph svg', 2, "there should be two graph rendered");
return kanban.update({});
}).then(function () {
return concurrency.delay(0); // one graph is re-rendered
}).then(function () {
return concurrency.delay(0); // one graph is re-rendered
}).then(function () {
assert.containsN(kanban, '.o_dashboard_graph svg', 2, "there should be one graph rendered");
kanban.destroy();
done();
});
});
});
QUnit.test('rendering of a field with dashboard_graph widget in an updated kanban view (grouped)', function (assert) {
var done = assert.async();
assert.expect(2);
createAsyncView({
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]]],
}).then(function (kanban) {
concurrency.delay(0).then(function () {
return concurrency.delay(0);
}).then(function () {
assert.containsN(kanban, '.o_dashboard_graph svg', 2, "there should be two graph rendered");
return kanban.update({groupBy: ['selection'], domain: [['int_field', '=', 10]]});
}).then(function () {
return concurrency.delay(0);
}).then(function () {
assert.containsOnce(kanban, '.o_dashboard_graph svg', "there should be one graph rendered");
kanban.destroy();
done();
});
});
});
QUnit.module('AceEditor');
QUnit.test('ace widget on text fields works', function (assert) {
assert.expect(2);
var done = assert.async();
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 be loaded");
assert.ok(form.$('div.ace_content').length, "should have rendered something with ace editor");
form.destroy();
done();
});
});
QUnit.module('HandleWidget');
QUnit.test('handle widget in x2m', function (assert) {
assert.expect(6);
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.containsN(form, 'span.o_row_handle', 2, "should have 2 handles");
testUtils.form.clickEdit(form);
assert.hasClass(form.$('td:first'),'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");
testUtils.dom.click(form.$('td').eq(1));
assert.containsOnce(form, 'td:first span.o_row_handle',
"content of the cell should have been replaced");
form.destroy();
});
QUnit.test('handle widget with falsy values', function (assert) {
assert.expect(1);
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree>' +
'<field name="sequence" widget="handle"/>' +
'<field name="display_name"/>' +
'</tree>',
});
assert.containsN(list, '.o_row_handle:visible', this.data.partner.records.length,
'there should be a visible handle for each record');
list.destroy();
});
QUnit.module('FieldDate');
QUnit.test('date field: toggle datepicker', function (assert) {
assert.expect(3);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form><field name="foo"/><field name="date"/></form>',
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
},
});
assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 0,
"datepicker should be closed initially");
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 1,
"datepicker should be opened");
// focus another field
testUtils.dom.click(form.$('.o_field_widget[name=foo]').focus().mouseenter());
assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 0,
"datepicker should close itself when the user clicks outside");
form.destroy();
});
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_field_widget');
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 value should not set on first click', 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,
});
testUtils.form.clickEdit(form);
// open datepicker and select a date
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
assert.strictEqual(form.$('.o_datepicker_input').val(), '', "date field's input should be empty on first click");
testUtils.dom.click($('.day:contains(22)'));
// re-open datepicker
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
assert.strictEqual($('.day.active').text(), '22',
"datepicker should be highlight with 22nd day of month");
form.destroy();
});
QUnit.test('date field in form view (with positive time zone offset)', function (assert) {
assert.expect(8);
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',
},
session: {
getTZOffset: function () {
return 120; // Should be ignored by date fields
},
},
});
assert.strictEqual(form.$('.o_field_date').text(), '02/03/2017',
'the date should be correctly displayed in readonly');
// switch to edit mode
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017',
'the date should be correct in edit mode');
// open datepicker and select another value
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
assert.strictEqual($('.day.active').data('day'), '02/03/2017', 'datepicker should be highlight February 3');
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)').first());
testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(1));
testUtils.dom.click($('.day:contains(22)'));
assert.ok(!$('.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
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_date').text(), '02/22/2017',
'the selected date should be displayed after saving');
form.destroy();
});
QUnit.test('date field in form view (with negative time zone offset)', 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: 1,
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
},
session: {
getTZOffset: function () {
return -120; // Should be ignored by date fields
},
},
});
assert.strictEqual(form.$('.o_field_date').text(), '02/03/2017',
'the date should be correctly displayed in readonly');
// switch to edit mode
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017',
'the date should be correct in edit mode');
form.destroy();
});
QUnit.test('date field with warn_future option', function (assert) {
assert.expect(2);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="date" options="{\'datepicker\': {\'warn_future\': true}}"/>' +
'</form>',
res_id: 4,
});
// switch to edit mode
testUtils.form.clickEdit(form);
// open datepicker and select another value
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .year').eq(11));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(11));
testUtils.dom.click($('.day:contains(31)'));
var $warn = form.$('.o_datepicker_warning:visible');
assert.strictEqual($warn.length, 1, "should have a warning in the form view");
testUtils.fields.editSelect(form.$('.o_field_widget[name=date] input'), ''); // remove the value
$warn = form.$('.o_datepicker_warning:visible');
assert.strictEqual($warn.length, 0, "the warning in the form view should be hidden");
form.destroy();
});
QUnit.test('date field with warn_future option: do not overwrite datepicker option', function (assert) {
assert.expect(2);
// Making sure we don't have a legit default value
// or any onchange that would set the value
this.data.partner.fields.date.default = undefined;
this.data.partner.onchanges = {};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="foo" />' + // Do not let the date field get the focus in the first place
'<field name="date" options="{\'datepicker\': {\'warn_future\': true}}"/>' +
'</form>',
res_id: 1,
});
// switch to edit mode
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('input[name="date"]').val(), '02/03/2017',
'The existing record should have a value for the date field');
// save with no changes
form.$buttons.find('.o_form_button_save').click();
//Create a new record
form.$buttons.find('.o_form_button_create').click();
assert.notOk(form.$('input[name="date"]').val(),
'The new record should not have a value that the framework would have set');
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',
},
session: {
getTZOffset: function () {
return 0;
},
},
});
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');
testUtils.dom.click($cell);
assert.containsOnce(list, 'input.o_datepicker_input',
"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');
// open datepicker and select another value
testUtils.dom.openDatepicker(list.$('.o_datepicker'));
assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(1));
testUtils.dom.click($('.day:contains(22)'));
assert.ok(!$('.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
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
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.test('date field remove value', function (assert) {
assert.expect(4);
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, false, 'the correct value should be saved');
}
return this._super.apply(this, arguments);
},
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
},
});
// switch to edit mode
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017',
'the date should be correct in edit mode');
testUtils.fields.editAndTrigger(form.$('.o_datepicker_input'), '', ['input', 'change', 'focusout']);
assert.strictEqual(form.$('.o_datepicker_input').val(), '',
'should have correctly removed the value');
// save
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_date').text(), '',
'the selected date should be displayed after saving');
form.destroy();
});
QUnit.test('do not trigger a field_changed for datetime field with date widget', function (assert) {
assert.expect(3);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners"><field name="datetime" widget="date"/></form>',
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
time_format: '%H:%M:%S',
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
mockRPC: function (route, args) {
assert.step(args.method);
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.$('.o_datepicker_input').val(), '02/08/2017',
'the date should be correct');
testUtils.fields.editAndTrigger(form.$('input[name="datetime"]'),'02/08/2017', ['input', 'change', 'focusout']);
testUtils.form.clickSave(form);
assert.verifySteps(['read']); // should not have save as nothing changed
form.destroy();
});
QUnit.test('field date should select its content onclick when there is one', function (assert) {
assert.expect(2);
var done = assert.async();
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="display_name" /> ' + // Do not focus on the date field right away
'<field name="date" />' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
form.$el.on({
'show.datetimepicker': function () {
assert.ok($('.bootstrap-datetimepicker-widget').is(':visible'),
'bootstrap-datetimepicker is visible');
assert.strictEqual(window.getSelection().toString(), "02/03/2017",
'The whole input of the date field should have been selected');
done();
}
});
form.$('.o_input[name="date"]').mouseenter().trigger('focus');
form.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',
},
session: {
getTZOffset: function () {
return 120;
},
},
});
var expectedDateString = "02/08/2017 12:00:00"; // 10:00:00 without timezone
assert.strictEqual(form.$('.o_field_date').text(), expectedDateString,
'the datetime should be correctly displayed in readonly');
// switch to edit mode
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_datepicker_input').val(), expectedDateString,
'the datetime should be correct in edit mode');
// select 22 February at 8:23:33
assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)'));
assert.ok(!$('.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
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_date').text(), newExpectedDateString,
'the selected date should be displayed after saving');
form.destroy();
});
QUnit.test('datetime field not visible in form view should not capture the focus on keyboard navigation', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners"><field name="txt"/>' +
'<field name="datetime" invisible="True"/></form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
form.$el.find('textarea[name=txt]').trigger($.Event('keydown', {
which: $.ui.keyCode.TAB,
keyCode: $.ui.keyCode.TAB,
}));
assert.strictEqual(document.activeElement, form.$buttons.find('.o_form_button_save')[0],
"the save button should be selected, because the datepicker did not capture the focus");
form.destroy();
});
QUnit.test('datetime field with datetime formatted without second', function (assert) {
assert.expect(2);
this.data.partner.fields.datetime.default = "2017-08-02 12:00:05";
this.data.partner.fields.datetime.required = true;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners"><field name="datetime"/></form>',
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
time_format: '%H:%M',
},
});
var expectedDateString = "08/02/2017 12:00"; // 10:00:00 without timezone
assert.strictEqual(form.$('.o_field_date input').val(), expectedDateString,
'the datetime should be correctly displayed in readonly');
testUtils.form.clickDiscard(form);
assert.strictEqual($('.modal').length, 0,
"there should not be a Warning dialog");
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',
},
session: {
getTZOffset: function () {
return 120;
},
},
});
var expectedDateString = "02/08/2017 12:00:00"; // 10:00:00 without timezone
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
testUtils.dom.click($cell);
assert.containsOnce(list, 'input.o_datepicker_input',
"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');
// select 22 February at 8:23:33
assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second'));
testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)'));
assert.ok(!$('.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
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
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.test('datetime field remove value', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners"><field name="datetime"/></form>',
res_id: 1,
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/write') {
assert.strictEqual(args.args[1].datetime, false, 'the correct value should be saved');
}
return this._super.apply(this, arguments);
},
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
time_format: '%H:%M:%S',
},
session: {
getTZOffset: function () {
return 120;
},
},
});
// switch to edit mode
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_datepicker_input').val(), '02/08/2017 12:00:00',
'the date time should be correct in edit mode');
testUtils.fields.editAndTrigger($('.o_datepicker_input'), '', ['input', 'change', 'focusout']);
assert.strictEqual(form.$('.o_datepicker_input').val(), '',
"should have an empty input");
// save
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_date').text(), '',
'the selected date should be displayed after saving');
form.destroy();
});
QUnit.test('datetime field with date/datetime widget (with day change)', function (assert) {
assert.expect(2);
this.data.partner.records[0].p = [2];
this.data.partner.records[1].datetime = "2017-02-08 02:00:00"; // UTC
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="p">' +
'<tree>' +
'<field name="datetime"/>' +
'</tree>' +
'<form>' +
'<field name="datetime" widget="date"/>' +
'</form>' +
'</field>' +
'</form>',
res_id: 1,
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
time_format: '%H:%M:%S',
},
session: {
getTZOffset: function () {
return -240;
},
},
});
var expectedDateString = "02/07/2017 22:00:00"; // local time zone
assert.strictEqual(form.$('.o_field_widget[name=p] .o_data_cell').text(), expectedDateString,
'the datetime (datetime widget) should be correctly displayed in tree view');
// switch to form view
testUtils.dom.click(form.$('.o_field_widget[name=p] .o_data_row'));
assert.strictEqual($('.modal .o_field_date[name=datetime]').text(), '02/07/2017',
'the datetime (date widget) should be correctly displayed in form view');
form.destroy();
});
QUnit.test('datetime field with date/datetime widget (without day change)', function (assert) {
assert.expect(2);
this.data.partner.records[0].p = [2];
this.data.partner.records[1].datetime = "2017-02-08 10:00:00"; // without timezone
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="p">' +
'<tree>' +
'<field name="datetime"/>' +
'</tree>' +
'<form>' +
'<field name="datetime" widget="date"/>' +
'</form>' +
'</field>' +
'</form>',
res_id: 1,
translateParameters: { // Avoid issues due to localization formats
date_format: '%m/%d/%Y',
time_format: '%H:%M:%S',
},
session: {
getTZOffset: function () {
return -240;
},
},
});
var expectedDateString = "02/08/2017 06:00:00"; // with timezone
assert.strictEqual(form.$('.o_field_widget[name=p] .o_data_cell').text(), expectedDateString,
'the datetime (datetime widget) should be correctly displayed in tree view');
// switch to form view
testUtils.dom.click(form.$('.o_field_widget[name=p] .o_data_row'));
assert.strictEqual($('.modal .o_field_date[name=datetime]').text(), '02/08/2017',
'the datetime (date widget) should be correctly displayed in form view');
form.destroy();
});
QUnit.test('datepicker option: daysOfWeekDisabled', function (assert) {
assert.expect(42);
this.data.partner.fields.datetime.default = "2017-08-02 12:00:05";
this.data.partner.fields.datetime.required = true;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="datetime" ' +
'options=\'{"datepicker": {"daysOfWeekDisabled": [0, 6]}}\'/>' +
'</form>',
res_id: 1,
});
testUtils.form.clickCreate(form);
$.each($('.day:last-child(),.day:nth-child(2)'), function (index, value) {
assert.hasClass(value, 'disabled', 'first and last days must be disabled');
});
// the assertions below could be replaced by a single hasClass classic on the jQuery set using the idea
// All not <=> not Exists. But we want to be sure that the set is non empty. We don't have an helper
// function for that.
$.each($('.day:not(:last-child()):not(:nth-child(2))'), function (index, value) {
assert.doesNotHaveClass(value, 'disabled', 'other days must stay clickable');
});
form.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_field_widget').first().text(), '$\u00a09.10',
'The value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '9.10',
'The input should be rendered without the currency symbol.');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().first().text(), '$',
'The input should be preceded by a span containing the currency symbol.');
testUtils.fields.editInput(form.$('.o_field_monetary input'), '108.2458938598598');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '108.2458938598598',
'The value should not be formated yet.');
testUtils.form.clickSave(form);
// Non-breaking space between the currency and the amount
assert.strictEqual(form.$('.o_field_widget').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_field_widget').first().text(), '0.00\u00a0€',
'The value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '0.00',
'The input should be rendered without the currency symbol.');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().eq(1).text(), '€',
'The input should be followed by a span containing the currency symbol.');
testUtils.fields.editInput(form.$('.o_field_widget[name=qux] input'), '108.2458938598598');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '108.2458938598598',
'The value should not be formated yet.');
testUtils.form.clickSave(form);
// Non-breaking space between the currency and the amount
assert.strictEqual(form.$('.o_field_widget').first().text(), '108.25\u00a0€',
'The new value should be rounded properly.');
form.destroy();
});
QUnit.test('monetary field with currency digits != 2', function (assert) {
assert.expect(5);
this.data.partner.records = [{
id: 1,
bar: false,
foo: "pouet",
int_field: 68,
qux: 99.1234,
currency_id: 1,
}];
this.data.currency.records = [{
id: 1,
display_name: "VEF",
symbol: "Bs.F",
position: "after",
digits: [16, 4],
}];
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: 1,
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
// Non-breaking space between the currency and the amount
assert.strictEqual(form.$('.o_field_widget').first().text(), '99.1234\u00a0Bs.F',
'The value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '99.1234',
'The input should be rendered without the currency symbol.');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().eq(1).text(), 'Bs.F',
'The input should be followed by a span containing the currency symbol.');
testUtils.fields.editInput(form.$('.o_field_widget[name=qux] input'), '99.111111111');
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '99.111111111',
'The value should not be formated yet.');
testUtils.form.clickSave(form);
// Non-breaking space between the currency and the amount
assert.strictEqual(form.$('.o_field_widget').first().text(), '99.1111\u00a0Bs.F',
'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.o_data_cell').filter(function () {return $(this).text() === '';});
assert.strictEqual(zeroValues.length, 1,
'Unset float values should be rendered as empty strings.');
// switch to edit mode
var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector):contains($)');
testUtils.dom.click($cell);
assert.strictEqual($cell.children().length, 1,
'The cell td should only contain the special div of monetary widget.');
assert.containsOnce(list, '[name="qux"] input',
'The view should have 1 input for editable monetary float.');
assert.strictEqual(list.$('[name="qux"] input').val(), '9.10',
'The input should be rendered without the currency symbol.');
assert.strictEqual(list.$('[name="qux"] input').parent().children().first().text(), '$',
'The input should be preceded by a span containing the currency symbol.');
testUtils.fields.editInput(list.$('[name="qux"] input'), '108.2458938598598');
assert.strictEqual(list.$('[name="qux"] input').val(), '108.2458938598598',
'The typed value should be correctly displayed.');
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
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(7);
this.data.partner.fields.qux.type = "monetary";
this.data.partner.fields.quux = {
string: "Quux", type: "monetary", digits: [16,1], searchable: true, readonly: true,
};
(_.find(this.data.partner.records, function (record) { return record.id === 5 })).quux = 4.2;
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="quux"/>' +
'<field name="currency_id"/>' +
'<field name="bar"/>' +
'</sheet>' +
'</form>',
res_id: 5,
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
assert.strictEqual(form.$('.o_field_monetary').first().html(), "$&nbsp;9.10",
"readonly value should contain the currency");
assert.strictEqual(form.$('.o_field_monetary').first().next().html(), "$&nbsp;4.20",
"readonly value should contain the currency");
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_monetary > input').val(), "9.10",
"input value in edition should only contain the value, without the currency");
testUtils.dom.click(form.$('input[type="checkbox"]'));
assert.containsOnce(form, '.o_field_monetary > input',
"After the onchange, the monetary <input/> should not have been duplicated");
assert.containsOnce(form, '.o_field_monetary[name=quux]',
"After the onchange, the monetary readonly field should not have been duplicated");
testUtils.fields.many2one.clickOpenDropdown('currency_id');
testUtils.fields.many2one.clickItem('currency_id','€');
assert.strictEqual(form.$('.o_field_monetary > span').html(), "€",
"After currency change, the monetary field currency should have been updated");
assert.strictEqual(form.$('.o_field_monetary').first().next().html(), "4.20&nbsp;€",
"readonly value should contain the updated currency");
form.destroy();
});
QUnit.test('monetary field with monetary field given in options', function (assert) {
assert.expect(1);
this.data.partner.fields.qux.type = "monetary";
this.data.partner.fields.company_currency_id = {
string: "Company Currency", type: "many2one", relation: "currency",
};
this.data.partner.records[4].company_currency_id = 2;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<field name="qux" options="{\'currency_field\': \'company_currency_id\'}"/>' +
'<field name="company_currency_id"/>' +
'</sheet>' +
'</form>',
res_id: 5,
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
assert.strictEqual(form.$('.o_field_monetary').html(), "9.10&nbsp;€",
"field monetary should be formatted with correct currency");
form.destroy();
});
QUnit.test('should keep the focus when being edited in x2many lists', function (assert) {
assert.expect(6);
this.data.partner.fields.currency_id.default = 1;
this.data.partner.fields.m2m = {
string: "m2m", type: "many2many", relation: 'partner', default: [[6, false, [2]]],
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<field name="p"/>' +
'<field name="m2m"/>' +
'</sheet>' +
'</form>',
archs: {
'partner,false,list': '<tree editable="bottom">' +
'<field name="qux" widget="monetary"/>' +
'<field name="currency_id" invisible="1"/>' +
'</tree>',
},
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
// test the monetary field inside the one2many
var $o2m = form.$('.o_field_widget[name=p]');
testUtils.dom.click($o2m.find('.o_field_x2many_list_row_add a'));
testUtils.fields.editInput($o2m.find('.o_field_widget input'), "22");
assert.strictEqual($o2m.find('.o_field_widget input').get(0), document.activeElement,
"the focus should still be on the input");
assert.strictEqual($o2m.find('.o_field_widget input').val(), "22",
"the value should not have been formatted yet");
testUtils.dom.click(form.$el);
assert.strictEqual($o2m.find('.o_field_widget[name=qux]').html(), "$&nbsp;22.00",
"the value should have been formatted after losing the focus");
// test the monetary field inside the many2many
var $m2m = form.$('.o_field_widget[name=m2m]');
testUtils.dom.click($m2m.find('.o_data_row td:first'));
testUtils.fields.editInput($m2m.find('.o_field_widget input'), "22");
assert.strictEqual($m2m.find('.o_field_widget input').get(0), document.activeElement,
"the focus should still be on the input");
assert.strictEqual($m2m.find('.o_field_widget input').val(), "22",
"the value should not have been formatted yet");
testUtils.dom.click(form.$el);
assert.strictEqual($m2m.find('.o_field_widget[name=qux]').html(), "22.00&nbsp;€",
"the value should have been formatted after losing the focus");
form.destroy();
});
QUnit.test('monetary field with currency set by an onchange',async function (assert) {
// this test ensures that the monetary field can be re-rendered with and
// without currency (which can happen as the currency can be set by an
// onchange)
assert.expect(8);
this.data.partner.onchanges = {
int_field: function (obj) {
obj.currency_id = obj.int_field ? 2 : null;
},
};
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree editable="top">' +
'<field name="int_field"/>' +
'<field name="qux" widget="monetary"/>' +
'<field name="currency_id" invisible="1"/>' +
'</tree>',
session: {
currencies: _.indexBy(this.data.currency.records, 'id'),
},
});
testUtils.dom.click(list.$buttons.find('.o_list_button_add'));
assert.containsOnce(list, 'div.o_field_widget[name=qux] input',
"monetary field should have been rendered correctly (without currency)");
assert.containsNone(list, '.o_field_widget[name=qux] span',
"monetary field should have been rendered correctly (without currency)");
// set a value for int_field -> should set the currency and re-render qux
testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'),'7');
assert.containsOnce(list, 'div.o_field_widget[name=qux] input',
"monetary field should have been re-rendered correctly (with currency)");
assert.strictEqual(list.$('.o_field_widget[name=qux] span:contains(€)').length, 1,
"monetary field should have been re-rendered correctly (with currency)");
var $quxInput = list.$('.o_field_widget[name=qux] input');
testUtils.dom.click($quxInput);
assert.strictEqual(document.activeElement, $quxInput[0],
"focus should be on the qux field's input");
// unset the value of int_field -> should unset the currency and re-render qux
testUtils.dom.click(list.$('.o_field_widget[name=int_field]'));
testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'),'0');
$quxInput = list.$('div.o_field_widget[name=qux] input');
assert.strictEqual($quxInput.length, 1,
"monetary field should have been re-rendered correctly (without currency)");
assert.containsNone(list, '.o_field_widget[name=qux] span',
"monetary field should have been re-rendered correctly (without currency)");
await concurrency.delay(0);
testUtils.dom.click($quxInput);
assert.strictEqual(document.activeElement, $quxInput[0],
"focus should be on the qux field's input");
list.destroy();
});
QUnit.module('FieldInteger');
QUnit.test('integer field when unset', function (assert) {
assert.expect(2);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners"><field name="int_field"/></form>',
res_id: 4,
});
assert.doesNotHaveClass(form.$('.o_field_widget'), 'o_field_empty',
'Non-set integer field should be recognized as 0.');
assert.strictEqual(form.$('.o_field_widget').text(), "0",
'Non-set integer field should be recognized as 0.');
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.doesNotHaveClass(form.$('.o_field_widget'), 'o_field_empty',
'Integer field should be considered set for value 0.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('input[name=int_field]').val(), '0',
'The value should be rendered correctly in edit mode.');
testUtils.fields.editInput(form.$('input[name=int_field]'), '-18');
assert.strictEqual(form.$('input[name=int_field]').val(), '-18',
'The value should be correctly displayed in the input.');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), '-18',
'The new value should be saved and displayed properly.');
form.destroy();
});
QUnit.test('integer field in form view with virtual id', function (assert) {
assert.expect(1);
var params = {
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners"><field name="id"/></form>',
};
params.res_id = this.data.partner.records[1].id = "2-20170808020000";
var form = createView(params);
assert.strictEqual(form.$('.o_field_widget').text(), "2-20170808020000",
"Should display virtual id");
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();
testUtils.dom.click($cell);
assert.containsOnce(list, 'input[name="int_field"]',
'The view should have 1 input for editable integer.');
testUtils.fields.editInput(list.$('input[name="int_field"]'), '-28');
assert.strictEqual(list.$('input[name="int_field"]').val(), '-28',
'The value should be displayed properly in the input.');
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
assert.strictEqual(list.$('td:not(.o_list_record_selector)').first().text(), '-28',
'The new value should be saved and displayed properly.');
list.destroy();
});
QUnit.test('integer field with type number option', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="int_field" options="{\'type\': \'number\'}"/>' +
'</form>',
res_id: 4,
translateParameters: {
thousands_sep: ",",
grouping: [3, 0],
},
});
testUtils.form.clickEdit(form);
assert.ok(form.$('.o_field_widget')[0].hasAttribute('type'),
'Integer field with option type must have a type attribute.');
assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'number',
'Integer field with option type must have a type attribute equals to "number".');
testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890');
testUtils.form.clickSave(form);
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget').val(), '1234567890',
'Integer value must be not formatted if input type is number.');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), '1,234,567,890',
'Integer value must be formatted in readonly view even if the input type is number.');
form.destroy();
});
QUnit.test('integer field without type number option', function (assert) {
assert.expect(2);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="int_field"/>' +
'</form>',
res_id: 4,
translateParameters: {
thousands_sep: ",",
grouping: [3, 0],
},
});
testUtils.form.clickEdit(form);
assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'text',
'Integer field without option type must have a text type (default type).');
testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890');
testUtils.form.clickSave(form);
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget').val(), '1,234,567,890',
'Integer value must be formatted if input type isn\'t number.');
form.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_field_widget').first().text(), '09:06',
'The formatted time value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('input[name=qux]').val(), '09:06',
'The value should be rendered correctly in the input.');
testUtils.fields.editInput(form.$('input[name=qux]'), '-11:48');
assert.strictEqual(form.$('input[name=qux]').val(), '-11:48',
'The new value should be displayed properly in the input.');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').first().text(), '-11:48',
'The new value should be saved and displayed properly.');
form.destroy();
});
QUnit.module('FieldFloatFactor');
QUnit.test('float_factor field in form view', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<field name="qux" widget="float_factor" options="{\'factor\': 0.5}" digits="[16,2]"/>' +
'</sheet>' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/write') {
// 16.4 / 2 = 8.2
assert.strictEqual(args.args[1].qux, 4.6, 'the correct float value should be saved');
}
return this._super.apply(this, arguments);
},
res_id: 5,
});
assert.strictEqual(form.$('.o_field_widget').first().text(), '4.55', // 9.1 / 0.5
'The formatted value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('input[name=qux]').val(), '4.55',
'The value should be rendered correctly in the input.');
testUtils.fields.editInput(form.$('input[name=qux]'), '2.3');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').first().text(), '2.30',
'The new value should be saved and displayed properly.');
form.destroy();
});
QUnit.module('FieldFloatToggle');
QUnit.test('float_toggle 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_toggle" options="{\'factor\': 0.125, \'range\': [0, 1, 0.75, 0.5, 0.25]}" digits="[5,3]"/>' +
'</sheet>' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/partner/write') {
// 1.000 / 0.125 = 8
assert.strictEqual(args.args[1].qux, 8, 'the correct float value should be saved');
}
return this._super.apply(this, arguments);
},
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget').first().text(), '0.056',
'The formatted time value should be displayed properly.');
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('button.o_field_float_toggle').text(), '0.056',
'The value should be rendered correctly on the button.');
testUtils.dom.click(form.$('button.o_field_float_toggle'));
assert.strictEqual(form.$('button.o_field_float_toggle').text(), '1.000',
'The value should be rendered correctly on the button.');
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').first().text(), '1.000',
'The new value should be saved and displayed properly.');
form.destroy();
});
QUnit.module('PhoneWidget');
QUnit.test('phone field in form view on normal screens', function (assert) {
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: config.device.SIZES.LG,
},
},
});
var $phone = form.$('a.o_field_widget.o_form_uri');
assert.strictEqual($phone.length, 1,
"should have rendered the phone number as a link with correct classes");
assert.strictEqual($phone.text(), 'yop',
"value should be displayed properly");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, 'input[type="text"].o_field_widget',
"should have an input for the phone field");
assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop',
"input should contain field value in edit mode");
// change value in edit mode
testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new');
// save
testUtils.form.clickSave(form);
assert.strictEqual(form.$('a.o_field_widget.o_form_uri').text(), 'new',
"new value should be displayed properly");
form.destroy();
});
QUnit.test('phone field in editable list view on normal screens', function (assert) {
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: config.device.SIZES.LG,
},
},
});
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");
assert.containsN(list, 'a.o_field_widget.o_form_uri', 5,
"should have the correct classnames");
// Edit a line and check the result
var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
testUtils.dom.click($cell);
assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode');
assert.strictEqual($cell.find('input').val(), 'yop',
'should have the corect value in internal input');
testUtils.fields.editInput($cell.find('input'), 'new');
// save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
$cell = list.$('tbody td:not(.o_list_record_selector)').first();
assert.doesNotHaveClass($cell.parent(), 'o_selected_row', '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.containsN(list, 'a.o_field_widget.o_form_uri', 5,
"should still have links with correct classes");
list.destroy();
});
QUnit.test('use TAB to navigate to a phone field', function (assert) {
assert.expect(2);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="display_name"/>' +
'<field name="foo" widget="phone"/>' +
'</group>' +
'</sheet>' +
'</form>',
});
testUtils.dom.click(form.$('input[name=display_name]'));
assert.strictEqual(form.$('input[name="display_name"]')[0], document.activeElement,
"display_name should be focused");
form.$('input[name="display_name"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB}));
assert.strictEqual(form.$('input[name="foo"]')[0], document.activeElement,
"foo should be focused");
form.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_field_widget.o_priority:not(.o_field_empty)').length, 1,
"widget should be considered set, even though there is no value for this field");
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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_field_widget.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_field_widget.o_priority:not(.o_field_empty)').length, 1,
"widget should be considered set");
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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_field_widget.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_field_widget.o_priority a.o_priority_star.fa-star-o').last().trigger('mouseover');
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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_field_widget.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
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2,
"should still have two stars");
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2,
"should still have two stars");
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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
testUtils.form.clickEdit(form);
assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2,
"should still have two stars");
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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
testUtils.dom.click(form.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').last());
assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2,
"should still have two stars");
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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
testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2,
"should still have two stars");
assert.strictEqual(form.$('.o_field_widget.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_field_widget.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_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();
testUtils.dom.click($cell);
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
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
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
testUtils.dom.click(list.$('.o_priority a.o_priority_star.fa-star').first());
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
$cell = list.$('tbody td:not(.o_list_record_selector)').first();
testUtils.dom.click($cell);
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
testUtils.dom.click(list.$('.o_priority a.o_priority_star.fa-star-o').last());
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
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
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,
viewOptions: {
disable_autofocus: true,
},
});
assert.containsOnce(form, '.o_field_widget.o_selection > a span.o_status.o_status_red',
"should have one red status since selection is the second, blocked state");
assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_green',
"should not have one green status since selection is the second, blocked state");
assert.containsNone(form, '.dropdown-menu.state:visible',
"there should not be a dropdown");
// Click on the status button to make the dropdown appear
testUtils.dom.click(form.$('.o_field_widget.o_selection .o_status').first());
assert.containsOnce(form, '.dropdown-menu.state:visible',
"there should be a dropdown");
assert.containsN(form, '.dropdown-menu.state:visible .dropdown-item', 2,
"there should be two options in the dropdown");
// Click on the first option, "Normal"
testUtils.dom.click(form.$('.dropdown-menu.state:visible .dropdown-item').first());
assert.containsNone(form, '.dropdown-menu.state:visible',
"there should not be a dropdown anymore");
assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red',
"should not have one red status since selection is the first, normal state");
assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_green',
"should not have one green status since selection is the first, normal state");
assert.containsOnce(form, '.o_field_widget.o_selection > a span.o_status',
"should have one grey status since selection is the first, normal state");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsNone(form, '.dropdown-menu.state:visible',
"there should still not be a dropdown");
assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red',
"should still not have one red status since selection is the first, normal state");
assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_green',
"should still not have one green status since selection is the first, normal state");
assert.containsOnce(form, '.o_field_widget.o_selection > a span.o_status',
"should still have one grey status since selection is the first, normal state");
// Click on the status button to make the dropdown appear
testUtils.dom.click(form.$('.o_field_widget.o_selection .o_status').first());
assert.containsOnce(form, '.dropdown-menu.state:visible',
"there should be a dropdown");
assert.containsN(form, '.dropdown-menu.state:visible .dropdown-item', 2,
"there should be two options in the dropdown");
// Click on the last option, "Done"
testUtils.dom.click(form.$('.dropdown-menu.state:visible .dropdown-item').last());
assert.containsNone(form, '.dropdown-menu.state:visible',
"there should not be a dropdown anymore");
assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red',
"should not have one red status since selection is the third, done state");
assert.containsOnce(form, '.o_field_widget.o_selection > a span.o_status.o_status_green',
"should have one green status since selection is the third, done state");
// save
testUtils.form.clickSave(form);
assert.containsNone(form, '.dropdown-menu.state:visible',
"there should still not be a dropdown anymore");
assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red',
"should still not have one red status since selection is the third, done state");
assert.containsOnce(form, '.o_field_widget.o_selection > a span.o_status.o_status_green',
"should still have one green status since selection is the third, done state");
form.destroy();
});
QUnit.test('state_selection widget with readonly modifier', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form><field name="selection" widget="state_selection" readonly="1"/></form>',
res_id: 1,
});
assert.hasClass(form.$('.o_selection'), 'o_readonly_modifier');
assert.hasClass(form.$('.o_selection > a'), 'disabled');
assert.isNotVisible(form.$('.dropdown-menu.state'));
testUtils.dom.click(form.$('.o_selection > a'));
assert.isNotVisible(form.$('.dropdown-menu.state'));
form.destroy();
});
QUnit.test('state_selection widget in editable list view', function (assert) {
assert.expect(32);
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.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5,
"should have five status selection widgets");
assert.containsOnce(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_red',
"should have one red status");
assert.containsOnce(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_green',
"should have one green status");
assert.containsNone(list, '.dropdown-menu.state:visible',
"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();
testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').first());
assert.doesNotHaveClass($cell.parent(), 'o_selected_row',
'should not be in edit mode since we clicked on the state selection widget');
assert.containsOnce(list, '.dropdown-menu.state:visible',
"there should be a dropdown");
assert.containsN(list, '.dropdown-menu.state:visible .dropdown-item', 2,
"there should be two options in the dropdown");
// Click on the first option, "Normal"
testUtils.dom.click(list.$('.dropdown-menu.state:visible .dropdown-item').first());
assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5,
"should still have five status selection widgets");
assert.containsNone(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_red',
"should now have no red status");
assert.containsOnce(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_green',
"should still have one green status");
assert.containsNone(list, '.dropdown-menu.state:visible',
"there should not be a dropdown");
// switch to edit mode and check the result
$cell = list.$('tbody td.o_state_selection_cell').first();
testUtils.dom.click($cell);
assert.hasClass($cell.parent(),'o_selected_row',
'should now be in edit mode');
assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5,
"should still have five status selection widgets");
assert.containsNone(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_red',
"should now have no red status");
assert.containsOnce(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_green',
"should still have one green status");
assert.containsNone(list, '.dropdown-menu.state:visible',
"there should not be a dropdown");
// Click on the status button to make the dropdown appear
testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').first());
assert.containsOnce(list, '.dropdown-menu.state:visible',
"there should be a dropdown");
assert.containsN(list, '.dropdown-menu.state:visible .dropdown-item', 2,
"there should be two options in the dropdown");
// Click on another row
var $lastCell = list.$('tbody td.o_state_selection_cell').last();
testUtils.dom.click($lastCell);
assert.containsNone(list, '.dropdown-menu.state:visible',
"there should not be a dropdown anymore");
var $firstCell = list.$('tbody td.o_state_selection_cell').first();
assert.doesNotHaveClass($firstCell.parent(), 'o_selected_row',
'first row should not be in edit mode anymore');
assert.hasClass($lastCell.parent(),'o_selected_row',
'last row should be in edit mode');
// Click on the last status button to make the dropdown appear
testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').last());
assert.containsOnce(list, '.dropdown-menu.state:visible',
"there should be a dropdown");
assert.containsN(list, '.dropdown-menu.state:visible .dropdown-item', 2,
"there should be two options in the dropdown");
// Click on the last option, "Done"
testUtils.dom.click(list.$('.dropdown-menu.state:visible .dropdown-item').last());
assert.containsNone(list, '.dropdown-menu.state:visible',
"there should not be a dropdown anymore");
assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5,
"should still have five status selection widgets");
assert.containsNone(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_red',
"should still have no red status");
assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_green', 2,
"should now have two green status");
assert.containsNone(list, '.dropdown-menu.state:visible',
"there should not be a dropdown");
// save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5,
"should have five status selection widgets");
assert.containsNone(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_red',
"should have no red status");
assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_green', 2,
"should have two green status");
assert.containsNone(list, '.dropdown-menu.state:visible',
"there should not be a dropdown");
list.destroy();
});
QUnit.module('FavoriteWidget');
QUnit.test('favorite widget in kanban view', function (assert) {
assert.expect(4);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test">' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div>' +
'<field name="bar" widget="boolean_favorite" />' +
'</div>' +
'</t>' +
'</templates>' +
'</kanban>',
domain: [['id', '=', 1]],
});
assert.containsOnce(kanban, '.o_kanban_record .o_field_widget.o_favorite > a i.fa.fa-star',
'should be favorite');
assert.strictEqual(kanban.$('.o_kanban_record .o_field_widget.o_favorite > a').text(), ' Remove from Favorites',
'the label should say "Remove from Favorites"');
// click on favorite
testUtils.dom.click(kanban.$('.o_field_widget.o_favorite'));
assert.containsNone(kanban, '.o_kanban_record .o_field_widget.o_favorite > a i.fa.fa-star',
'should not be favorite');
assert.strictEqual(kanban.$('.o_kanban_record .o_field_widget.o_favorite > a').text(), ' Add to Favorites',
'the label should say "Add to Favorites"');
kanban.destroy();
});
QUnit.test('favorite widget in form view', function (assert) {
assert.expect(10);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="bar" widget="boolean_favorite" />' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.containsOnce(form, '.o_field_widget.o_favorite > a i.fa.fa-star',
'should be favorite');
assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Remove from Favorites',
'the label should say "Remove from Favorites"');
// click on favorite
testUtils.dom.click(form.$('.o_field_widget.o_favorite'));
assert.containsNone(form, '.o_field_widget.o_favorite > a i.fa.fa-star',
'should not be favorite');
assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Add to Favorites',
'the label should say "Add to Favorites"');
// switch to edit mode
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_widget.o_favorite > a i.fa.fa-star-o',
'should not be favorite');
assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Add to Favorites',
'the label should say "Add to Favorites"');
// click on favorite
testUtils.dom.click(form.$('.o_field_widget.o_favorite'));
assert.containsOnce(form, '.o_field_widget.o_favorite > a i.fa.fa-star',
'should be favorite');
assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Remove from Favorites',
'the label should say "Remove from Favorites"');
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.o_field_widget.o_favorite > a i.fa.fa-star',
'should be favorite');
assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Remove from Favorites',
'the label should say "Remove from Favorites"');
form.destroy();
});
QUnit.test('favorite widget in editable list view without label', function (assert) {
assert.expect(4);
var list = createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree editable="bottom">' +
'<field name="bar" widget="boolean_favorite" nolabel="1" />' +
'</tree>',
});
assert.containsOnce(list, '.o_data_row:first .o_field_widget.o_favorite > a i.fa.fa-star',
'should be favorite');
// switch to edit mode
testUtils.dom.click(list.$('tbody td:not(.o_list_record_selector)').first());
assert.containsOnce(list, '.o_data_row:first .o_field_widget.o_favorite > a i.fa.fa-star',
'should be favorite');
// click on favorite
testUtils.dom.click(list.$('.o_data_row:first .o_field_widget.o_favorite'));
assert.containsNone(list, '.o_data_row:first .o_field_widget.o_favorite > a i.fa.fa-star',
'should not be favorite');
// save
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
assert.containsOnce(list, '.o_data_row:first .o_field_widget.o_favorite > a i.fa.fa-star-o',
'should not be favorite');
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\': \'secondary\', \'blocked\': \'warning\',\'done\': \'success\'}}"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.containsOnce(form, '.o_field_widget.badge.badge-warning',
"should have a warning status label since selection is the second, blocked state");
assert.containsNone(form, '.o_field_widget.badge.badge-secondary',
"should not have a default status since selection is the second, blocked state");
assert.containsNone(form, '.o_field_widget.badge.badge-success',
"should not have a success status since selection is the second, blocked state");
assert.strictEqual(form.$('.o_field_widget.badge.badge-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
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_widget.badge.badge-warning',
"should have a warning status label since selection is the second, blocked state");
assert.containsNone(form, '.o_field_widget.badge.badge-secondary',
"should not have a default status since selection is the second, blocked state");
assert.containsNone(form, '.o_field_widget.badge.badge-success',
"should not have a success status since selection is the second, blocked state");
assert.strictEqual(form.$('.o_field_widget.badge.badge-warning').text(), 'Blocked',
"the label should say 'Blocked' since this is the label value for that state");
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.o_field_widget.badge.badge-warning',
"should have a warning status label since selection is the second, blocked state");
assert.containsNone(form, '.o_field_widget.badge.badge-secondary',
"should not have a default status since selection is the second, blocked state");
assert.containsNone(form, '.o_field_widget.badge.badge-success',
"should not have a success status since selection is the second, blocked state");
assert.strictEqual(form.$('.o_field_widget.badge.badge-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\': \'secondary\', \'blocked\': \'warning\',\'done\': \'success\'}}"/>' +
'</tree>',
});
assert.strictEqual(list.$('.o_field_widget.badge:not(:empty)').length, 3,
"should have three visible status labels");
assert.containsOnce(list, '.o_field_widget.badge.badge-warning',
"should have one warning status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-warning').text(), 'Blocked',
"the warning label should read 'Blocked'");
assert.containsOnce(list, '.o_field_widget.badge.badge-secondary',
"should have one default status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-secondary').text(), 'Normal',
"the default label should read 'Normal'");
assert.containsOnce(list, '.o_field_widget.badge.badge-success',
"should have one success status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-success').text(), 'Done',
"the success label should read 'Done'");
// switch to edit mode and check the result
testUtils.dom.click(list.$('tbody td:not(.o_list_record_selector)').first());
assert.strictEqual(list.$('.o_field_widget.badge:not(:empty)').length, 3,
"should have three visible status labels");
assert.containsOnce(list, '.o_field_widget.badge.badge-warning',
"should have one warning status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-warning').text(), 'Blocked',
"the warning label should read 'Blocked'");
assert.containsOnce(list, '.o_field_widget.badge.badge-secondary',
"should have one default status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-secondary').text(), 'Normal',
"the default label should read 'Normal'");
assert.containsOnce(list, '.o_field_widget.badge.badge-success',
"should have one success status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-success').text(), 'Done',
"the success label should read 'Done'");
// save and check the result
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
assert.strictEqual(list.$('.o_field_widget.badge:not(:empty)').length, 3,
"should have three visible status labels");
assert.containsOnce(list, '.o_field_widget.badge.badge-warning',
"should have one warning status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-warning').text(), 'Blocked',
"the warning label should read 'Blocked'");
assert.containsOnce(list, '.o_field_widget.badge.badge-secondary',
"should have one default status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-secondary').text(), 'Normal',
"the default label should read 'Normal'");
assert.containsOnce(list, '.o_field_widget.badge.badge-success',
"should have one success status label");
assert.strictEqual(list.$('.o_field_widget.badge.badge-success').text(), 'Done',
"the success label should read 'Done'");
list.destroy();
});
QUnit.module('StatInfo');
QUnit.test('statinfo widget formats decimal precision', function (assert) {
// sometimes the round method can return numbers such as 14.000001
// when asked to round a number to 2 decimals, as such is the behaviour of floats.
// we check that even in that eventuality, only two decimals are displayed
assert.expect(2);
this.data.partner.fields.monetary = {string: "Monetary", type: 'monetary'};
this.data.partner.records[0].monetary = 9.999999;
this.data.partner.records[0].currency_id = 1;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<button class="oe_stat_button" name="items" icon="fa-gear">' +
'<field name="qux" widget="statinfo"/>' +
'</button>' +
'<button class="oe_stat_button" name="money" icon="fa-money">' +
'<field name="monetary" widget="statinfo"/>' +
'</button>' +
'</form>',
res_id: 1,
});
// formatFloat renders according to this.field.digits
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').eq(0).text(),
'0.4', "Default precision should be [16,1]");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').eq(1).text(),
'10.00', "Currency decimal precision should be 2");
form.destroy();
});
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.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_text').text(),
'int_field', "should have 'int_field' as text");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should still have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should still have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_text').text(),
'int_field', "should have 'int_field' as text");
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.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.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.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
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should still have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should still have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_text').text(),
'yop', "should have 'yop' as text, since it is the value of field foo");
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.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" nolabel="1"/>' +
'</button>' +
'</div>' +
'<group>' +
'<field name="foo" invisible="1"/>' +
'<field name="bar"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_text').text(),
'', "should not have any label");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should still have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should still have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_text').text(),
'', "should not have any label");
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info',
"should have one stat button");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(),
'10', "should have 10 as value");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.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.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie',
"should have a pie chart");
assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(),
'10%', "should have 10% as pie value since int_field=10");
assert.ok(_.str.include(form.$('.o_field_percent_pie.o_field_widget .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_field_percent_pie.o_field_widget .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
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie',
"should have a pie chart");
assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(),
'10%', "should have 10% as pie value since int_field=10");
assert.ok(_.str.include(form.$('.o_field_percent_pie.o_field_widget .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_field_percent_pie.o_field_widget .o_pie .o_mask').last().attr('style'),
'transform: rotate(36deg);'), "right mask should be rotated from 360*(10/100) = 36 degrees");
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie',
"should have a pie chart");
assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(),
'10%', "should have 10% as pie value since int_field=10");
assert.ok(_.str.include(form.$('.o_field_percent_pie.o_field_widget .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_field_percent_pie.o_field_widget .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.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie',
"should have a pie chart");
assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(),
'80%', "should have 80% as pie value since int_field=80");
assert.ok(_.str.include(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_mask').first().attr('style'),
'transform: rotate(288deg);'), "left mask should be rotated from 360*(80/100) = 288 degrees");
assert.hasClass(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_mask').last(),'o_full',
"right mask should be hidden since the value > 50%");
// switch to edit mode and check the result
testUtils.form.clickEdit(form);
assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie',
"should have a pie chart");
assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(),
'80%', "should have 80% as pie value since int_field=80");
assert.ok(_.str.include(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_mask').first().attr('style'),
'transform: rotate(288deg);'), "left mask should be rotated from 360*(80/100) = 288 degrees");
assert.hasClass(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_mask').last(),'o_full',
"right mask should be hidden since the value > 50%");
// save
testUtils.form.clickSave(form);
assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie',
"should have a pie chart");
assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(),
'80%', "should have 80% as pie value since int_field=80");
assert.ok(_.str.include(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_mask').first().attr('style'),
'transform: rotate(288deg);'), "left mask should be rotated from 360*(80/100) = 288 degrees");
assert.hasClass(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_mask').last(),'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 scss itself for this widget currently only
// applies inside the form view, the widget is unusable. This test can
// be uncommented when we refactor the scss 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.containsN(list, '.o_field_percent_pie .o_pie', 5,
// "should have five pie charts");
// assert.strictEqual(list.$('.o_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_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_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
// testUtils.dom.click( list.$('tbody td:not(.o_list_record_selector)').first());
// assert.strictEqual(list.$('.o_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_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_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
// testUtils.dom.click( list.$buttons.find('.o_list_button_save'));
// assert.strictEqual(list.$('.o_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_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_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('The domain editor should not crash the view when given a dynamic filter', function (assert) {
//dynamic filters (containing variables, such as uid, parent or today)
//are not handled by the domain editor, but it shouldn't crash the view
assert.expect(1);
this.data.partner.records[0].foo = '[["int_field", "=", uid]]';
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form>' +
'<field name="foo" widget="domain" options="{\'model\': \'partner\'}"/>' +
'<field name="int_field" invisible="1"/>' +
'</form>',
res_id: 1,
session: {
user_context: {uid: 14},
},
});
assert.strictEqual(form.$('.o_read_mode').text(), "This domain is not supported.",
"The widget should not crash the view, but gracefully admit its failure.");
form.destroy();
});
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,
});
testUtils.form.clickEdit(form);
// As the domain is empty, there should be a button to add the first
// domain part
var $domain = form.$(".o_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
testUtils.dom.click($domainAddFirstNodeButton);
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
testUtils.dom.click($colorIndex);
$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
testUtils.form.clickSave(form);
$domain = form.$(".o_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,
});
testUtils.form.clickEdit(form);
// As the domain is equal to [["id", "=", 1]] there should be a field
// selector to change this
var $domain = form.$(".o_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"
testUtils.dom.click(form.$("input.o_field_widget"));
testUtils.fields.editInput(form.$("input.o_field_widget"), "partner_type");
// 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();
});
QUnit.test('domain field can be reset with a new domain (from onchange)', function (assert) {
assert.expect(2);
this.data.partner.records[0].foo = '[]';
this.data.partner.onchanges = {
display_name: function (obj) {
obj.foo = '[["id", "=", 1]]';
},
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form>' +
'<field name="display_name"/>' +
'<field name="foo" widget="domain" options="{\'model\': \'partner\'}"/>' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
assert.equal(form.$('.o_domain_show_selection_button').text().trim(), '5 record(s)',
"the domain being empty, there should be 5 records");
// update display_name to trigger the onchange and reset foo
testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value');
assert.equal(form.$('.o_domain_show_selection_button').text().trim(), '1 record(s)',
"the domain has changed, there should be only 1 record");
form.destroy();
});
QUnit.test('domain field: handle false domain as []', function (assert) {
assert.expect(3);
this.data.partner.records[0].foo = false;
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>',
mockRPC: function (route, args) {
if (args.method === 'search_count') {
assert.deepEqual(args.args[0], [], "should send a valid domain");
}
return this._super.apply(this, arguments);
},
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget[name=foo]:not(.o_field_empty)').length, 1,
"there should be a domain field, not considered empty");
testUtils.form.clickEdit(form);
var $warning = form.$('.o_field_widget[name=foo] .text-warning');
assert.strictEqual($warning.length, 0, "should not display that the domain is invalid");
form.destroy();
});
QUnit.test('basic domain field: show the selection', function (assert) {
assert.expect(2);
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>',
archs: {
'partner_type,false,list': '<tree><field name="display_name"/></tree>',
'partner_type,false,search': '<search><field name="name" string="Name"/></search>',
},
res_id: 1,
});
assert.equal(form.$(".o_domain_show_selection_button").text().trim().substr(0, 2), "2 ",
"selection should contain 2 records");
// open the selection
testUtils.dom.click(form.$(".o_domain_show_selection_button"));
assert.strictEqual($('.modal .o_list_view .o_data_row').length, 2,
"should have open a list view with 2 records in a dialog");
// click on a record -> should not open the record
// we don't actually check that it doesn't open the record because even
// if it tries to, it will crash as we don't define an arch in this test
testUtils.dom.click($('.modal .o_list_view .o_data_row:first .o_data_cell'));
form.destroy();
});
QUnit.module('FieldProgressBar');
QUnit.test('Field ProgressBar: max_value should update', function (assert) {
assert.expect(2);
this.data.partner.records = this.data.partner.records.slice(0,1);
this.data.partner.records[0].qux = 2;
this.data.partner.onchanges = {
display_name: function (obj) {
obj.int_field = 999;
obj.qux = 5;
}
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="display_name" />' +
'<field name="qux" invisible="1" />' +
'<field name="int_field" widget="progressbar" options="{\'current_value\': \'int_field\', \'max_value\': \'qux\'}" />' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual(form.$('.o_progressbar_value').text(), '10 / 2',
'The initial value of the progress bar should be correct');
// trigger the onchange
form.$('.o_input[name=display_name]').val('new name').trigger('input');
assert.strictEqual(form.$('.o_progressbar_value').text(), '999 / 5',
'The value of the progress bar should be correct after the update');
form.destroy();
});
});
});
});