9423 lines
389 KiB
JavaScript
9423 lines
389 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 testUtilsDom = require('web.test_utils_dom');
|
||
var field_registry = require('web.field_registry');
|
||
const legacyViewRegistry = require('web.view_registry');
|
||
const makeTestEnvironment = require("web.test_env");
|
||
const { makeLegacyCommandService } = require("@web/legacy/utils");
|
||
const { registry } = require("@web/core/registry");
|
||
const { getFixture, legacyExtraNextTick, triggerHotkey, nextTick, click, patchWithCleanup } = require("@web/../tests/helpers/utils");
|
||
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
|
||
const { registerCleanup } = require("@web/../tests/helpers/cleanup");
|
||
|
||
var createView = testUtils.createView;
|
||
var patchDate = testUtils.mock.patchDate;
|
||
|
||
var DebouncedField = basicFields.DebouncedField;
|
||
var JournalDashboardGraph = basicFields.JournalDashboardGraph;
|
||
var _t = core._t;
|
||
|
||
// Base64 images for testing purpose
|
||
const MY_IMAGE = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
||
const PRODUCT_IMAGE = 'R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7';
|
||
const FR_FLAG_URL = '/base/static/img/country_flags/fr.png';
|
||
const EN_FLAG_URL = '/base/static/img/country_flags/gb.png';
|
||
|
||
let target;
|
||
|
||
QUnit.module('Legacy fields', {}, function () {
|
||
|
||
QUnit.module('Legacy basic_fields', {
|
||
beforeEach: function () {
|
||
registry.category("views").remove("list"); // remove new list from registry
|
||
registry.category("views").remove("kanban"); // remove new kanban from registry
|
||
registry.category("views").remove("form"); // remove new form from registry
|
||
legacyViewRegistry.add("list", ListView); // add legacy list -> will be wrapped and added to new registry
|
||
legacyViewRegistry.add("kanban", KanbanView); // add legacy kanban -> will be wrapped and added to new registry
|
||
legacyViewRegistry.add("form", FormView); // add legacy form -> will be wrapped and added to new registry
|
||
|
||
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},
|
||
empty_string: {string: "Empty string", type: "char", default: false, searchable: true, trim: 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},
|
||
monetary: {string: "MMM", type: "monetary", 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"},
|
||
hex_color: {string: "hexadecimal color", type: "char"},
|
||
},
|
||
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',
|
||
hex_color: '#ff0000',
|
||
}, {
|
||
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},
|
||
{id: 5, bar: false, foo: "blop", int_field: -4, qux: 9.1, currency_id: 1, monetary: 99.9}],
|
||
onchanges: {},
|
||
},
|
||
team: {
|
||
fields: {
|
||
partner_ids: { string: "Partner", type: "one2many", relation: 'partner' },
|
||
},
|
||
records: [{
|
||
id: 1,
|
||
partner_ids: [5],
|
||
}],
|
||
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: {
|
||
digits: { string: "Digits" },
|
||
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",
|
||
}]
|
||
},
|
||
};
|
||
|
||
target = getFixture();
|
||
},
|
||
}, function () {
|
||
|
||
QUnit.module('DebouncedField');
|
||
|
||
QUnit.test('debounced fields do not trigger call _setValue once destroyed', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var def = testUtils.makeTestPromise();
|
||
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 = await 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
|
||
await testUtils.form.clickSave(form);
|
||
assert.verifySteps(['_setValue'], "_setValue should have been called once");
|
||
|
||
// destroy the form view
|
||
def = testUtils.makeTestPromise();
|
||
form.destroy();
|
||
await testUtils.nextMicrotaskTick();
|
||
|
||
// wait for the debounced callback to be called
|
||
assert.verifySteps([],
|
||
"_setValue should not have been called after widget destruction");
|
||
|
||
DebouncedField.prototype._doAction = _doAction;
|
||
DebouncedField.prototype._setValue = _setValue;
|
||
});
|
||
|
||
QUnit.module('FieldBoolean');
|
||
|
||
QUnit.test('boolean field in form view', async function (assert) {
|
||
assert.expect(15);
|
||
|
||
var form = await 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");
|
||
assert.containsNone(form, '.o_field_boolean input:disabled',
|
||
'checkbox should not be disabled');
|
||
|
||
// switch to edit mode and check the result
|
||
await testUtils.form.clickEdit(form);
|
||
assert.containsOnce(form, '.o_field_boolean input:checked',
|
||
"checkbox should still be checked");
|
||
assert.containsNone(form, '.o_field_boolean input:disabled',
|
||
'checkbox should not be disabled');
|
||
|
||
// uncheck the checkbox
|
||
await testUtils.dom.click(form.$('.o_field_boolean input:checked'));
|
||
assert.containsNone(form, '.o_field_boolean input:checked',
|
||
"checkbox should no longer be checked");
|
||
|
||
// save
|
||
await 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
|
||
await testUtils.form.clickEdit(form);
|
||
assert.containsNone(form, '.o_field_boolean input:checked',
|
||
"checkbox should still be unchecked");
|
||
|
||
// check the checkbox
|
||
await testUtils.dom.click(form.$('.o_field_boolean input'));
|
||
assert.containsOnce(form, '.o_field_boolean input:checked',
|
||
"checkbox should now be checked");
|
||
|
||
// uncheck it back
|
||
await 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
|
||
await testUtils.dom.click(form.$('.o_legacy_form_view label:first'));
|
||
assert.containsOnce(form, '.o_field_boolean input:checked',
|
||
"checkbox should now be checked");
|
||
|
||
// uncheck it back
|
||
await testUtils.dom.click(form.$('.o_legacy_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
|
||
await testUtils.dom.triggerEvents(form.$('.o_field_boolean input'), [
|
||
"focusin",
|
||
{type: "keydown", which: $.ui.keyCode.ENTER},
|
||
{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
|
||
await testUtils.dom.triggerEvent(document.activeElement, "keydown",
|
||
{which: $.ui.keyCode.ENTER});
|
||
assert.containsNone(form, '.o_field_boolean input:checked',
|
||
"checkbox should not be checked");
|
||
await testUtils.nextTick();
|
||
// blindly press enter again, it should check the checkbox back
|
||
await testUtils.dom.triggerEvent(document.activeElement, "keydown",
|
||
{which: $.ui.keyCode.ENTER});
|
||
assert.containsOnce(form, '.o_field_boolean input:checked',
|
||
"checkbox should still be checked");
|
||
|
||
// save
|
||
await 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', async function (assert) {
|
||
assert.expect(11);
|
||
|
||
var list = await 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) .form-check input').length, 5,
|
||
"should have 5 checkboxes");
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .form-check input:checked').length, 4,
|
||
"should have 4 checked input");
|
||
|
||
// Edit a line
|
||
var $cell = list.$('tr.o_data_row:has(.form-check input:checked) td:not(.o_list_record_selector)').first();
|
||
assert.ok($cell.find('.form-check input:checked').prop('disabled'),
|
||
"input should be disabled in readonly mode");
|
||
await testUtils.dom.click($cell);
|
||
assert.ok(!$cell.find('.form-check input:checked').prop('disabled'),
|
||
"input should not have the disabled property in edit mode");
|
||
await testUtils.dom.click($cell.find('.form-check input:checked'));
|
||
|
||
// save
|
||
await testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
|
||
$cell = list.$('tr.o_data_row:has(.form-check input:not(:checked)) td:not(.o_list_record_selector)').first();
|
||
assert.ok($cell.find('.form-check input:not(:checked)').prop('disabled'),
|
||
"input should be disabled again");
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .form-check input').length, 5,
|
||
"should still have 5 checkboxes");
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .form-check input:checked').length, 3,
|
||
"should now have only 3 checked input");
|
||
|
||
// Re-Edit the line and fake-check the checkbox
|
||
await testUtils.dom.click($cell);
|
||
await testUtils.dom.click($cell.find('.form-check input'));
|
||
await testUtils.dom.click($cell.find('.form-check input'));
|
||
|
||
// Save
|
||
await testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .form-check input').length, 5,
|
||
"should still have 5 checkboxes");
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .form-check 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(.form-check input:not(:checked)) td:not(.o_list_record_selector)').first();
|
||
await testUtils.dom.click($cell.find('.form-check .form-check-label'));
|
||
await testUtils.nextTick();
|
||
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .form-check input').length, 5,
|
||
"should still have 5 checkboxes");
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .form-check input:checked').length, 4,
|
||
"should now have 4 checked input back");
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('readonly boolean field', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="bar" attrs="{'readonly': True}"/>
|
||
</form>`,
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.containsOnce(form, '.o_field_boolean input:checked',
|
||
"checkbox should be checked");
|
||
assert.containsOnce(form, '.o_field_boolean input:disabled',
|
||
'checkbox should be disabled');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.containsOnce(form, '.o_field_boolean input:checked',
|
||
"checkbox should still be checked");
|
||
assert.containsOnce(form, '.o_field_boolean input:disabled',
|
||
'checkbox should still be disabled');
|
||
|
||
await testUtils.dom.click(form.$('.o_field_boolean label'));
|
||
|
||
assert.containsOnce(form, '.o_field_boolean input:checked',
|
||
"checkbox should still be checked");
|
||
assert.containsOnce(form, '.o_field_boolean input:disabled',
|
||
'checkbox should still be disabled');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('boolean field is editable in an editable form', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form edit="1">' +
|
||
'<field name="bar" widget="boolean"/>' +
|
||
'</form>',
|
||
});
|
||
|
||
assert.containsOnce(form, '.o_field_boolean input:enabled',
|
||
"the field should be editable");
|
||
|
||
await testUtils.form.clickSave(form);
|
||
|
||
assert.containsOnce(form, '.o_field_boolean input:enabled',
|
||
"the field should be editable");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('boolean field is not editable in a readonly form', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form edit="0">' +
|
||
'<field name="bar" widget="boolean"/>' +
|
||
'</form>',
|
||
viewOptions: {
|
||
mode: 'readonly',
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(form, '.o_field_boolean input:disabled',
|
||
"the field should not be editable");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('boolean field is not editable with a readonly modifier', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="bar" widget="boolean" readonly="1"/>' +
|
||
'</form>',
|
||
});
|
||
|
||
assert.containsOnce(form, '.o_field_boolean input:disabled',
|
||
"the field should not be editable");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldBooleanToggle');
|
||
|
||
QUnit.test('use boolean toggle widget in form view', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="bar" widget="boolean_toggle"/></form>',
|
||
res_id: 2,
|
||
});
|
||
// We don't check the exact value of the background as it change in community/enterprise,
|
||
// and it's difficult to check the value of an SVG
|
||
assert.containsOnce(form, ".form-check.o_boolean_toggle", "Boolean toggle widget applied to boolean field");
|
||
let checkElement = document.querySelector('.form-check.o_boolean_toggle .form-check-input');
|
||
assert.ok(checkElement.matches(':checked'), "Boolean toggle should be checked");
|
||
let style = window.getComputedStyle(checkElement);
|
||
const backgroundImage = style.backgroundImage;
|
||
const backgroundColor = style.backgroundColor;
|
||
assert.ok(backgroundImage && backgroundColor, "Boolean toggle should have a background image and a background color");
|
||
|
||
await testUtils.dom.click(form.$('.o_field_widget[name=bar]'));
|
||
checkElement = document.querySelector('.form-check.o_boolean_toggle .form-check-input');
|
||
assert.notOk(checkElement.matches(':checked'), "Boolean toggle shouldn't be checked");
|
||
style = window.getComputedStyle(checkElement);
|
||
const newBackgroundImage = style.backgroundImage;
|
||
const newBackgroundColor = style.backgroundColor;
|
||
assert.ok(backgroundImage && backgroundColor, "Boolean toggle should have a background image and a background color");
|
||
assert.notEqual(backgroundImage, newBackgroundImage, "Background image should be different");
|
||
assert.notEqual(backgroundColor, newBackgroundColor, "Background color should be different");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('boolean toggle widget is not disabled in readonly mode', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="bar" widget="boolean_toggle"/></form>',
|
||
res_id: 5,
|
||
});
|
||
|
||
assert.containsOnce(form, ".form-check.o_boolean_toggle", "Boolean toggle widget applied to boolean field");
|
||
assert.notOk(form.$('.o_boolean_toggle input')[0].checked);
|
||
await testUtils.dom.click(form.$('.o_boolean_toggle'));
|
||
assert.ok(form.$('.o_boolean_toggle input')[0].checked);
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('boolean toggle widget is disabled with a readonly attribute', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="bar" widget="boolean_toggle" readonly="1"/></form>',
|
||
res_id: 5,
|
||
});
|
||
|
||
assert.containsOnce(form, ".form-check.o_boolean_toggle", "Boolean toggle widget applied to boolean field");
|
||
await testUtils.dom.click(form.$buttons.find('.o_form_button_edit'));
|
||
assert.notOk(form.$('.o_boolean_toggle input')[0].checked);
|
||
await testUtils.dom.click(form.$('.o_boolean_toggle'));
|
||
assert.notOk(form.$('.o_boolean_toggle input')[0].checked);
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('boolean toggle widget is enabled in edit mode', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="bar" widget="boolean_toggle"/></form>',
|
||
res_id: 5,
|
||
});
|
||
|
||
assert.containsOnce(form, ".form-check.o_boolean_toggle", "Boolean toggle widget applied to boolean field");
|
||
await testUtils.dom.click(form.$buttons.find('.o_form_button_edit'));
|
||
|
||
assert.notOk(form.$('.o_boolean_toggle input')[0].checked);
|
||
await testUtils.dom.click(form.$('.o_boolean_toggle'));
|
||
assert.ok(form.$('.o_boolean_toggle input')[0].checked);
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldToggleButton');
|
||
|
||
QUnit.test('use toggle_button in list view', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
field_registry.add("toggle_button", basicFields.FieldToggleBoolean);
|
||
registerCleanup(() => delete field_registry.map.toggle_button);
|
||
|
||
var list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree>' +
|
||
'<field name="bar" widget="toggle_button" ' +
|
||
'options="{"active": "Reported in last payslips", "inactive": "To Report in Payslip"}"/>' +
|
||
'</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_legacy_list_view button').first(), 'title',
|
||
"Reported in last payslips", "active buttons should have proper tooltip");
|
||
assert.hasAttrValue(list.$('.o_legacy_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
|
||
await testUtils.dom.click(list.$('.o_legacy_list_view button').first());
|
||
assert.containsN(list, 'button i.fa.fa-circle.o_toggle_button_success', 3,
|
||
"should have 3 green buttons");
|
||
|
||
await testUtils.dom.click(list.$('.o_legacy_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)', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
field_registry.add("toggle_button", basicFields.FieldToggleBoolean);
|
||
registerCleanup(() => delete field_registry.map.toggle_button);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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)', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
field_registry.add("toggle_button", basicFields.FieldToggleBoolean);
|
||
registerCleanup(() => delete field_registry.map.toggle_button);
|
||
|
||
var form = await 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
|
||
await 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.test('toggle_button in form view with readonly modifiers', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
field_registry.add("toggle_button", basicFields.FieldToggleBoolean);
|
||
registerCleanup(() => delete field_registry.map.toggle_button);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<form>
|
||
<field name="bar" widget="toggle_button" options="{'active': 'Active value', 'inactive': 'Inactive value'}" readonly="True"/>
|
||
</form>`,
|
||
res_id: 2,
|
||
});
|
||
|
||
assert.strictEqual(form.$('.o_field_widget[name=bar] i.o_toggle_button_success:not(.text-muted)').length,
|
||
1, "should be green");
|
||
assert.ok(form.$('.o_field_widget[name=bar]').prop('disabled'),
|
||
"button should be disabled when readonly attribute is given");
|
||
|
||
// assert that the button has properly been disabled
|
||
assert.ok(form.$('.o_field_widget[name=bar]').get(0).disabled);
|
||
|
||
assert.strictEqual(form.$('.o_field_widget[name=bar] i.o_toggle_button_success:not(.text-muted)').length,
|
||
1, "should be green even after click");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldNumeric');
|
||
|
||
QUnit.test('numeric field: fields with keydown on numpad decimal key', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
this.data.partner.fields.float_factor_field = { string: "Float Factor", type: 'float_factor' };
|
||
this.data.partner.records[0].float_factor_field = 9.99;
|
||
|
||
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;
|
||
|
||
this.data.partner.fields.percentage = { string: "Percentage", type: 'percentage' };
|
||
this.data.partner.records[0].percentage = .99;
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form string="Partners">
|
||
<field name="float_factor_field" options="{'factor': 0.5}"/>
|
||
<field name="qux"/>
|
||
<field name="int_field"/>
|
||
<field name="monetary"/>
|
||
<field name="currency_id" invisible="1"/>
|
||
<field name="percentage"/>
|
||
</form>
|
||
`,
|
||
res_id: 1,
|
||
translateParameters: {
|
||
decimal_point: "🇧🇪",
|
||
},
|
||
});
|
||
|
||
// Record edit mode
|
||
await testUtilsDom.click(form.el.querySelector('.o_form_button_edit'));
|
||
|
||
// Get all inputs
|
||
const floatFactorField = form.el.querySelector('.o_input[name="float_factor_field"]');
|
||
const floatInput = form.el.querySelector('.o_input[name="qux"]');
|
||
const integerInput = form.el.querySelector('.o_input[name="int_field"]');
|
||
const monetaryInput = form.el.querySelector('.o_input[name="monetary"]');
|
||
const percentageInput = form.el.querySelector('.o_input[name="percentage"]');
|
||
|
||
// Dispatch numpad "dot" and numpad "comma" keydown events to all inputs and check
|
||
// Numpad "comma" is specific to some countries (Brazil...)
|
||
floatFactorField.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
|
||
floatFactorField.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
|
||
await testUtils.nextTick();
|
||
assert.ok(floatFactorField.value.endsWith('🇧🇪🇧🇪'));
|
||
|
||
floatInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
|
||
floatInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
|
||
await testUtils.nextTick();
|
||
assert.ok(floatInput.value.endsWith('🇧🇪🇧🇪'));
|
||
|
||
integerInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
|
||
integerInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
|
||
await testUtils.nextTick();
|
||
assert.ok(integerInput.value.endsWith('🇧🇪🇧🇪'));
|
||
|
||
monetaryInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
|
||
monetaryInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
|
||
await testUtils.nextTick();
|
||
assert.ok(monetaryInput.querySelector('input.o_input') .value.endsWith('🇧🇪🇧🇪'));
|
||
|
||
percentageInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
|
||
percentageInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
|
||
await testUtils.nextTick();
|
||
assert.ok(percentageInput.querySelector('input.o_input').value.endsWith('🇧🇪🇧🇪'));
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('numeric field: field with type `number` and with keydown on numpad decimal key', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
patchWithCleanup(basicFields.NumericField.constructor.prototype, {
|
||
_onKeydown(ev) {
|
||
const res = this._super(...arguments);
|
||
|
||
// This _onKeydown handler must not prevent default
|
||
// a keydown event for NumericField with type=number
|
||
assert.ok(!ev.defaultPrevented);
|
||
return res;
|
||
},
|
||
});
|
||
|
||
this.data.partner.fields.float_field = { string: "Float", type: 'float' };
|
||
this.data.partner.records[0].float_field = 123;
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form string="Partners">
|
||
<field name="float_field" options="{'type': 'number'}"/>
|
||
</form>
|
||
`,
|
||
res_id: 1,
|
||
});
|
||
|
||
await testUtilsDom.click(form.el.querySelector('.o_form_button_edit'));
|
||
const floatField = form.el.querySelector('.o_input[name="float_field"]');
|
||
assert.strictEqual(floatField.type, 'number')
|
||
|
||
floatField.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldFloat');
|
||
|
||
QUnit.test('float field when unset', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await 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 list view no widget', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<field name="qux" 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.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('input[name=qux]').val(), '0.000',
|
||
'The value should be rendered with correct precision.');
|
||
|
||
await 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.');
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '18.8958938598598');
|
||
await 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 form view', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var form = await 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.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('input[name=qux]').val(), '0.000',
|
||
'The value should be rendered with correct precision.');
|
||
|
||
await 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.');
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '18.8958938598598');
|
||
await 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 using formula in form view', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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,
|
||
});
|
||
|
||
// Test computation with priority of operation
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '=20+3*2');
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '26.000',
|
||
'The new value should be calculated properly.');
|
||
|
||
// Test computation with ** operand
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '=2**3');
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '8.000',
|
||
'The new value should be calculated properly.');
|
||
|
||
// Test computation with ^ operant which should do the same as **
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '=2^3');
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '8.000',
|
||
'The new value should be calculated properly.');
|
||
|
||
// Test computation and rounding
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '=100/3');
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '33.333',
|
||
'The new value should be calculated properly.');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('float field using incorrect formula in form view', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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,
|
||
});
|
||
|
||
// Test that incorrect value is not computed
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '=abc');
|
||
await testUtils.form.clickSave(form);
|
||
assert.hasClass(form.$('.o_legacy_form_view'),'o_form_editable',
|
||
"form view should still be editable");
|
||
assert.hasClass(form.$('input[name=qux]'),'o_field_invalid',
|
||
"fload field should be displayed as invalid");
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '=3:2?+4');
|
||
await testUtils.form.clickSave(form);
|
||
assert.hasClass(form.$('.o_legacy_form_view'),'o_form_editable',
|
||
"form view should still be editable");
|
||
assert.hasClass(form.$('input[name=qux]'),'o_field_invalid',
|
||
"float field should be displayed as invalid");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('float field in editable list view', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var list = await 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();
|
||
await testUtils.dom.click($cell);
|
||
|
||
assert.containsOnce(list, 'input[name="qux"]',
|
||
'The view should have 1 input for editable float.');
|
||
|
||
await 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.');
|
||
|
||
await testUtils.fields.editInput(list.$('input[name="qux"]'), '18.8958938598598');
|
||
await 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', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.records[1].qux = false;
|
||
this.data.partner.records[1].int_field = false;
|
||
var form = await 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);
|
||
}
|
||
});
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.form.clickSave(form);
|
||
|
||
assert.verifySteps(['read']); // should not have save as nothing changed
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('float widget on monetary field', async 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 = await 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', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
this.data.partner.records = [{
|
||
id: 1,
|
||
qux: -8.89859,
|
||
currency_id: 1,
|
||
}];
|
||
var form = await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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],
|
||
},
|
||
});
|
||
|
||
await 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".');
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890');
|
||
await testUtils.form.clickSave(form);
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('.o_field_widget').val(), '123456.789',
|
||
'Float value must be not formatted if input type is number.');
|
||
await 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 with type number option and comma decimal separator', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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: ".",
|
||
decimal_point: ",",
|
||
grouping: [3, 0],
|
||
},
|
||
});
|
||
|
||
await 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".');
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '123456.789');
|
||
await testUtils.form.clickSave(form);
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('.o_field_widget').val(), '123456.789',
|
||
'Float value must be not formatted if input type is number.');
|
||
await 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', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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],
|
||
},
|
||
});
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'text',
|
||
'Float field with option type must have a text type (default type).');
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890');
|
||
await testUtils.form.clickSave(form);
|
||
await 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('Percentage');
|
||
|
||
QUnit.test('percentage widget in form view', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: ` <form string="Partners">
|
||
<field name="qux" widget="percentage"/>
|
||
</form>`,
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].qux, 0.24, 'the correct float value should be saved');
|
||
}
|
||
return this._super(...arguments);
|
||
},
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '44.4%',
|
||
'The value should be displayed properly.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '44.4',
|
||
'The input should be rendered without the percentage symbol.');
|
||
assert.strictEqual(form.$('.o_field_widget[name=qux] span').text(), '%',
|
||
'The input should be followed by a span containing the percentage symbol.');
|
||
|
||
await testUtils.fields.editInput(form.$('.o_field_float_percentage input'), '24');
|
||
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '24',
|
||
'The value should not be formated yet.');
|
||
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').text(), '24%',
|
||
'The new value should be formatted properly.');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldEmail');
|
||
|
||
QUnit.test('email field in form view', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
var form = await 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.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_email > a');
|
||
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
|
||
await 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
|
||
await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new');
|
||
|
||
// save
|
||
await testUtils.form.clickSave(form);
|
||
$mailtoLink = form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_email > a');
|
||
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', async function (assert) {
|
||
assert.expect(10);
|
||
|
||
var list = await 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.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_email > a');
|
||
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();
|
||
await 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');
|
||
await testUtils.fields.editInput($cell.find('input'), 'new');
|
||
|
||
// save
|
||
await 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.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_email > a');
|
||
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.test('email field with empty value', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="empty_string" widget="email"/>' +
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
var $mailtoLink = form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_email > a');
|
||
assert.strictEqual($mailtoLink.text(), '',
|
||
"the value should be displayed properly");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('email field trim user value', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="foo" widget="email"/></form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
await testUtils.fields.editInput(form.$('input[name="foo"]'), ' abc@abc.com ');
|
||
await testUtils.form.clickSave(form);
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('input[name="foo"]').val(), 'abc@abc.com',
|
||
"Foo value should have been trimmed");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('readonly email field field is properly rerendered after been changed by onchange', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.records[0].foo = 'dolores.abernathy@delos';
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="int_field" on_change="1"/>' + // onchange to update mobile in readonly mode directly
|
||
'<field name="foo" widget="email" readonly="1"/>' + // readonly only, we don't want to go through write mode
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {mode: 'edit'},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'onchange') {
|
||
return Promise.resolve({
|
||
value: {
|
||
foo: 'lara.espin@unknown', // onchange to update foo in readonly mode directly
|
||
},
|
||
});
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
});
|
||
// check initial rendering
|
||
assert.strictEqual(form.$('.o_field_email').text(), 'dolores.abernathy@delos',
|
||
'Initial email text should be set');
|
||
|
||
// trigger the onchange to update phone field, but still in readonly mode
|
||
await testUtils.fields.editInput($('input[name="int_field"]'), '3');
|
||
|
||
// check rendering after changes
|
||
assert.strictEqual(form.$('.o_field_email').text(), 'lara.espin@unknown',
|
||
'email text should be updated');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test("click on email field link don't switch to edit mode", async function (assert) {
|
||
testUtils.mock.patch(field_registry.map.email, {
|
||
_onClickLink: function (ev) {
|
||
assert.step(ev.target.nodeName + " clicked");
|
||
this._super.apply(this, arguments);
|
||
},
|
||
});
|
||
var form = await 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,
|
||
});
|
||
|
||
await testUtils.dom.click(form.el.querySelector('.o_field_email a'));
|
||
assert.containsOnce(form.el, ".o_form_readonly", "form view is not in edit mode");
|
||
|
||
await testUtils.dom.click(form.el.querySelector('.o_field_email'));
|
||
assert.verifySteps(["A clicked", "DIV clicked"]);
|
||
assert.containsOnce(form.el, ".o_form_editable", "form view is in edit mode");
|
||
|
||
form.destroy();
|
||
testUtils.mock.unpatch(field_registry.map.email);
|
||
});
|
||
|
||
QUnit.module('FieldChar');
|
||
|
||
QUnit.test('char widget isValid method works', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.fields.foo.required = true;
|
||
var form = await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'limbo');
|
||
|
||
// save
|
||
await 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', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await 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);
|
||
}
|
||
});
|
||
|
||
await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), '');
|
||
|
||
// save
|
||
await testUtils.form.clickSave(form);
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('char field with size attribute', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.fields.foo.size = 5; // max length
|
||
var form = await 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', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
var list = await 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();
|
||
await 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');
|
||
await testUtils.fields.editInput($cell.find('input'), 'brolo');
|
||
|
||
// save
|
||
await 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', async function (assert) {
|
||
assert.expect(12);
|
||
|
||
this.data.partner.fields.foo.translate = true;
|
||
|
||
var multiLang = _t.database.multi_lang;
|
||
_t.database.multi_lang = true;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="foo"/>' +
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
session: {
|
||
user_context: {lang: 'en_US'},
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (route === "/web/dataset/call_kw/partner/get_field_translations") {
|
||
assert.deepEqual(args.args, [[1],"foo"], "should translate the foo field of the record");
|
||
return Promise.resolve([
|
||
[{lang: "en_US", source: "yop", value: "yop"}, {lang: "fr_BE", source: "yop", value: "valeur français"}],
|
||
{translation_type: "char", translation_show_source: false},
|
||
]);
|
||
}
|
||
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
|
||
return Promise.resolve([["en_US", "English"], ["fr_BE", "French (Belgium)"]]);
|
||
}
|
||
if (route === "/web/dataset/call_kw/partner/update_field_translations") {
|
||
assert.deepEqual(args.args, [[1], "foo", {"en_US": "english value"}], "the new translation value should be written");
|
||
return Promise.resolve();
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
});
|
||
await 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");
|
||
assert.strictEqual($button.text(), 'EN', 'the button should have as test the current language');
|
||
await testUtils.dom.click($button);
|
||
await testUtils.nextTick();
|
||
|
||
assert.containsOnce($(document), '.modal', 'a translate modal should be visible');
|
||
assert.containsN($('.modal .o_translation_dialog'), '.translation', 2,
|
||
'two rows should be visible');
|
||
|
||
var $enField = $('.modal .o_translation_dialog .translation:first() input');
|
||
assert.strictEqual($enField.val(), 'yop',
|
||
'English translation should be filled');
|
||
assert.strictEqual($('.modal .o_translation_dialog .translation:last() input').val(), 'valeur français',
|
||
'French translation should be filled');
|
||
|
||
await testUtils.fields.editInput($enField, "english value");
|
||
await testUtils.dom.click($('.modal button.btn-primary')); // save
|
||
await testUtils.nextTick();
|
||
|
||
var $foo = form.$('input[type="text"].o_field_char');
|
||
assert.strictEqual($foo.val(), "english value",
|
||
"the new translation was not transfered to modified record");
|
||
|
||
await testUtils.fields.editInput($foo, "new english value");
|
||
|
||
await testUtils.dom.click($button);
|
||
await testUtils.nextTick();
|
||
|
||
assert.strictEqual($('.modal .o_translation_dialog .translation:first() input').val(), 'new english value',
|
||
'Modified value should be used instead of translation');
|
||
assert.strictEqual($('.modal .o_translation_dialog .translation:last() input').val(), 'valeur français',
|
||
'French translation should be filled');
|
||
|
||
form.destroy();
|
||
|
||
_t.database.multi_lang = multiLang;
|
||
});
|
||
|
||
QUnit.test('html field translatable', async function (assert) {
|
||
assert.expect(8);
|
||
|
||
this.data.partner.fields.foo.translate = true;
|
||
|
||
var multiLang = _t.database.multi_lang;
|
||
_t.database.multi_lang = true;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="foo"/>' +
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
session: {
|
||
user_context: {lang: 'en_US'},
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (route === "/web/dataset/call_kw/partner/get_field_translations") {
|
||
assert.deepEqual(args.args, [[1],"foo"], "should translate the foo field of the record");
|
||
return Promise.resolve([
|
||
[{lang: "en_US", source: "first paragraph", value: "first paragraph"},
|
||
{lang: "en_US", source: "second paragraph", value: "second paragraph"},
|
||
{lang: "fr_BE", source: "first paragraph", value: ""},
|
||
{lang: "fr_BE", source: "second paragraph", value: "deuxième paragraphe"}],
|
||
{translation_type: "char", translation_show_source: true},
|
||
]);
|
||
}
|
||
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
|
||
return Promise.resolve([["en_US", "English"], ["fr_BE", "French (Belgium)"]]);
|
||
}
|
||
if (route === "/web/dataset/call_kw/partner/update_field_translations") {
|
||
assert.deepEqual(args.args, [[1], "foo", {
|
||
"en_US": {"first paragraph": "first paragraph modified"},
|
||
"fr_BE": {
|
||
"first paragraph": "premier paragraphe modifié",
|
||
"deuxième paragraphe": "deuxième paragraphe modifié",
|
||
},
|
||
}], "the new translation value should be written");
|
||
return Promise.resolve();
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
});
|
||
await testUtils.form.clickEdit(form);
|
||
var $foo = form.$('input[type="text"].o_field_char');
|
||
|
||
// this will not affect the translate_fields effect until the record is
|
||
// saved but is set for consistency of the test
|
||
await testUtils.fields.editInput($foo, "<p>first paragraph</p><p>second paragraph</p>");
|
||
|
||
var $button = form.$('input[type="text"].o_field_char + .o_field_translate');
|
||
await testUtils.dom.click($button);
|
||
await testUtils.nextTick();
|
||
|
||
assert.containsOnce($(document), '.modal', 'a translate modal should be visible');
|
||
assert.containsN($('.modal .o_translation_dialog'), '.translation', 4,
|
||
'four rows should be visible');
|
||
|
||
const $translations = $('.modal .o_translation_dialog .translation input');
|
||
const enField1 = $translations[0];
|
||
assert.strictEqual(enField1.value, 'first paragraph',
|
||
'first part of english translation should be filled');
|
||
|
||
await testUtils.fields.editInput(enField1, "first paragraph modified");
|
||
|
||
const frField1 = $translations[2];
|
||
assert.strictEqual(frField1.value, '',
|
||
'first part of french translation should not be filled');
|
||
|
||
await testUtils.fields.editInput(frField1, "premier paragraphe modifié");
|
||
|
||
const frField2 = $translations[3];
|
||
assert.strictEqual(frField2.value, 'deuxième paragraphe',
|
||
'second part of french translation should be filled');
|
||
|
||
await testUtils.fields.editInput(frField2, "deuxième paragraphe modifié");
|
||
await testUtils.dom.click($('.modal button.btn-primary')); // save
|
||
await testUtils.nextTick();
|
||
|
||
assert.strictEqual($foo.val(), "<p>first paragraph</p><p>second paragraph</p>",
|
||
"the new partial translation should not be transfered");
|
||
|
||
form.destroy();
|
||
|
||
_t.database.multi_lang = multiLang;
|
||
});
|
||
|
||
QUnit.test('char field translatable in create mode', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.fields.foo.translate = true;
|
||
|
||
var multiLang = _t.database.multi_lang;
|
||
_t.database.multi_lang = true;
|
||
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="foo"/>' +
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
});
|
||
var $button = form.$('input[type="text"].o_field_char + .o_field_translate');
|
||
assert.strictEqual($button.length, 1, "should have a translate button in create mode");
|
||
form.destroy();
|
||
|
||
_t.database.multi_lang = multiLang;
|
||
});
|
||
|
||
QUnit.test('char field does not allow html injections', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await 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',
|
||
},
|
||
});
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=foo]'), '<script>throw Error();</script>');
|
||
await 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', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.fields.foo2 = {string: "Foo2", type: "char", trim: false};
|
||
|
||
var form = await 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',
|
||
},
|
||
});
|
||
|
||
await testUtils.fields.editInput(form.$('input[name="foo"]'), ' abc ');
|
||
await testUtils.fields.editInput(form.$('input[name="foo2"]'), ' def ');
|
||
|
||
await testUtils.form.clickSave(form);
|
||
|
||
// edit mode
|
||
await 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', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
this.data.partner.onchanges = {
|
||
product_id: function () {},
|
||
};
|
||
|
||
var def;
|
||
var form = await 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 Promise.resolve(def).then(function () {
|
||
return result;
|
||
});
|
||
} else {
|
||
return result;
|
||
}
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
await 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 = testUtils.makeTestPromise();
|
||
|
||
await testUtils.fields.many2one.clickOpenDropdown('product_id');
|
||
await testUtils.fields.many2one.clickHighlightedItem('product_id');
|
||
|
||
// set foo before onchange
|
||
await 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();
|
||
await testUtils.nextTick();
|
||
|
||
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 returns (with fieldDebounce)', async function (assert) {
|
||
// this test is exactly the same as the previous one, except that we set
|
||
// here a fieldDebounce to accurately reproduce what happens in practice:
|
||
// the field doesn't notify the changes on 'input', but on 'change' event.
|
||
assert.expect(5);
|
||
|
||
this.data.partner.onchanges = {
|
||
product_id: function (obj) {
|
||
obj.int_field = obj.product_id ? 7 : false;
|
||
},
|
||
};
|
||
|
||
let def;
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="p">
|
||
<tree editable="bottom">
|
||
<field name="product_id"/>
|
||
<field name="foo"/>
|
||
<field name="int_field"/>
|
||
</tree>
|
||
</field>
|
||
</form>`,
|
||
async mockRPC(route, args) {
|
||
const result = this._super(...arguments);
|
||
if (args.method === "onchange") {
|
||
await Promise.resolve(def);
|
||
}
|
||
return result;
|
||
},
|
||
fieldDebounce: 5000,
|
||
});
|
||
|
||
await 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 = testUtils.makeTestPromise();
|
||
|
||
await testUtils.fields.many2one.clickOpenDropdown('product_id');
|
||
await testUtils.fields.many2one.clickHighlightedItem('product_id');
|
||
|
||
// set foo before onchange
|
||
await testUtils.fields.editInput(form.$('input[name="foo"]'), "tralala");
|
||
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala');
|
||
assert.strictEqual(form.$('input[name="int_field"]').val(), '');
|
||
|
||
// complete the onchange
|
||
def.resolve();
|
||
await testUtils.nextTick();
|
||
|
||
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
|
||
'foo should contain the same value as before onchange');
|
||
assert.strictEqual(form.$('input[name="int_field"]').val(), '7',
|
||
'int_field should contain the value returned by the onchange');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('input field: change value before pending onchange renaming', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
this.data.partner.onchanges = {
|
||
product_id: function (obj) {
|
||
obj.foo = 'on change value';
|
||
},
|
||
};
|
||
|
||
var def = testUtils.makeTestPromise();
|
||
var form = await 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 def.then(function () {
|
||
return result;
|
||
});
|
||
} else {
|
||
return result;
|
||
}
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(form.$('input[name="foo"]').val(), 'yop',
|
||
'should contain the correct value');
|
||
|
||
await testUtils.fields.many2one.clickOpenDropdown('product_id');
|
||
await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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");
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
this.data.partner.records[0].foo = false;
|
||
|
||
var form = await 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");
|
||
|
||
await 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.test('input field: set and remove value, then wait for onchange', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.onchanges = {
|
||
product_id(obj) {
|
||
obj.foo = obj.product_id ? "onchange value" : false;
|
||
},
|
||
};
|
||
|
||
let def;
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="p">
|
||
<tree editable="bottom">
|
||
<field name="product_id"/>
|
||
<field name="foo"/>
|
||
</tree>
|
||
</field>
|
||
</form>`,
|
||
async mockRPC(route, args) {
|
||
const result = this._super(...arguments);
|
||
if (args.method === "onchange") {
|
||
await Promise.resolve(def);
|
||
}
|
||
return result;
|
||
},
|
||
fieldDebounce: 1000, // needed to accurately mock what really happens
|
||
});
|
||
|
||
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
|
||
assert.strictEqual(form.$('input[name="foo"]').val(), "");
|
||
|
||
await testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); // set value for foo
|
||
await testUtils.fields.editInput(form.$('input[name="foo"]'), ""); // remove value for foo
|
||
|
||
// trigger the onchange by setting a product
|
||
await testUtils.fields.many2one.clickOpenDropdown('product_id');
|
||
await testUtils.fields.many2one.clickHighlightedItem('product_id');
|
||
assert.strictEqual(form.$('input[name="foo"]').val(), 'onchange value',
|
||
'input should contain correct value after onchange');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('UrlWidget');
|
||
|
||
QUnit.test('url widget in form view', async function (assert) {
|
||
assert.expect(10);
|
||
|
||
var form = await 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, 'div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a',
|
||
"should have a anchor with correct classes");
|
||
assert.hasAttrValue(form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a'), 'href', 'http://yop',
|
||
"should have proper href link");
|
||
assert.hasAttrValue(form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a'), 'target', '_blank',
|
||
"should have target attribute set to _blank");
|
||
assert.strictEqual(form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a').text(), 'yop',
|
||
"the value should be displayed properly");
|
||
|
||
// switch to edit mode and check the result
|
||
await 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
|
||
await testUtils.form.clickSave(form);
|
||
assert.containsOnce(form, 'div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a',
|
||
"should still have a anchor with correct classes");
|
||
assert.hasAttrValue(form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a'), 'href', 'http://limbo',
|
||
"should have proper new href link");
|
||
assert.strictEqual(form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a').text(), 'limbo',
|
||
'the new value should be displayed');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), '/web/limbo');
|
||
|
||
await testUtils.form.clickSave(form);
|
||
assert.hasAttrValue(form.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a'), 'href', '/web/limbo',
|
||
"should'nt have change link");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('url widget takes text from proper attribute', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await 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.$('div[name="foo"].o_field_url > a').text(), 'kebeclibre',
|
||
"url text should come from the text attribute");
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('url widget: href attribute and website_path option', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
this.data.partner.fields.url1 = { string: "Url 1", type: "char", default: "www.url1.com" };
|
||
this.data.partner.fields.url2 = { string: "Url 2", type: "char", default: "www.url2.com" };
|
||
this.data.partner.fields.url3 = { string: "Url 3", type: "char", default: "http://www.url3.com" };
|
||
this.data.partner.fields.url4 = { string: "Url 4", type: "char", default: "https://url4.com" };
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="url1" widget="url"/>
|
||
<field name="url2" widget="url" options="{'website_path': True}"/>
|
||
<field name="url3" widget="url"/>
|
||
<field name="url4" widget="url"/>
|
||
</form>`,
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$('div[name="url1"].o_field_url > a').attr('href'), 'http://www.url1.com');
|
||
assert.strictEqual(form.$('div[name="url2"].o_field_url > a').attr('href'), 'www.url2.com');
|
||
assert.strictEqual(form.$('div[name="url3"].o_field_url > a').attr('href'), 'http://www.url3.com');
|
||
assert.strictEqual(form.$('div[name="url4"].o_field_url > a').attr('href'), 'https://url4.com');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('char field in editable list view', async function (assert) {
|
||
assert.expect(10);
|
||
|
||
var list = await 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, 'div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a', 5,
|
||
"should have 5 anchors with correct classes");
|
||
assert.hasAttrValue(list.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a').first(), 'href', 'http://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();
|
||
await 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');
|
||
await testUtils.fields.editInput($cell.find('input'), 'brolo');
|
||
|
||
// save
|
||
await 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, 'div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a', 5,
|
||
"should still have 5 anchors with correct classes");
|
||
assert.hasAttrValue(list.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a').first(), 'href', 'http://brolo',
|
||
"should have proper new href link");
|
||
assert.strictEqual(list.$('div.o_form_uri.o_field_widget.o_text_overflow.o_field_url > a').first().text(), 'brolo',
|
||
"value should be properly updated");
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('url widget with falsy value', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
this.data.partner.records[0].foo = false;
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="foo" widget="url"/></form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.containsOnce(form, 'div.o_field_widget[name=foo]');
|
||
assert.strictEqual(form.$('.o_field_widget[name=foo]').text(), "");
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
assert.containsOnce(form, 'input.o_field_widget[name=foo]');
|
||
assert.strictEqual(form.$('.o_field_widget[name=foo]').val(), "");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('url old content is cleaned on render edit', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
this.data.partner.fields.foo2 = { string: "Foo2", type: "char", default: "foo2" };
|
||
this.data.partner.onchanges.foo2 = function (record) {
|
||
record.foo = record.foo2;
|
||
}
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:`
|
||
<form string="Partners">
|
||
<sheet>
|
||
<group>
|
||
<field name="foo" widget="url" attrs="{'readonly': True}" />
|
||
<field name="foo2" />
|
||
</group>
|
||
</sheet>
|
||
</form>
|
||
`,
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$('.o_field_widget[name=foo]').text(), 'yop',
|
||
"the starting value should be displayed properly");
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
assert.strictEqual(form.$('.o_field_widget[name=foo2]').val(), 'foo2',
|
||
"input should contain field value in edit mode");
|
||
|
||
await testUtils.fields.editInput(form.$('.o_field_widget[name=foo2]'), 'bonjour');
|
||
assert.strictEqual(form.$('.o_field_widget[name=foo]').text(), 'bonjour',
|
||
"Url widget should show the new value and not " + form.$('.o_field_widget[name=foo]').text());
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('CopyClipboard');
|
||
|
||
QUnit.test('Char & Text Fields: Copy to clipboard button', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<div>' +
|
||
'<field name="txt" widget="CopyClipboardText"/>' +
|
||
'<field name="foo" widget="CopyClipboardChar"/>' +
|
||
'</div>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
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();
|
||
});
|
||
|
||
QUnit.test('CopyClipboard widget on unset field', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.records[0].foo = false;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="foo" widget="CopyClipboardChar" />' +
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.containsNone(form, '.o_field_copy[name="foo"] .o_clipboard_button',
|
||
"foo (unset) should not contain a button");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('CopyClipboard widget on readonly unset fields in create mode', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.fields.display_name.readonly = true;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="display_name" widget="CopyClipboardChar" />' +
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
});
|
||
|
||
assert.containsNone(form, '.o_field_copy[name="display_name"] .o_clipboard_button',
|
||
"the readonly unset field should not contain a button");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldText');
|
||
|
||
QUnit.test('text fields are correctly rendered', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
this.data.partner.fields.foo.type = 'text';
|
||
var form = await 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');
|
||
|
||
await 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');
|
||
|
||
await 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', async 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 = await 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[0].offsetHeight, $field[0].scrollHeight,
|
||
"text field should not have a scroll bar");
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
var $textarea = form.$('textarea:first');
|
||
|
||
// the difference is to take small calculation errors into account
|
||
assert.strictEqual($textarea[0].clientHeight, $textarea[0].scrollHeight,
|
||
"textarea should not have a scroll bar");
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('text fields in edit mode, no vertical resize', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="txt"/>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
await 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 should have correct height after onchange', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const damnLongText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||
Donec est massa, gravida eget dapibus ac, eleifend eget libero.
|
||
Suspendisse feugiat sed massa eleifend vestibulum. Sed tincidunt
|
||
velit sed lacinia lacinia. Nunc in fermentum nunc. Vestibulum ante
|
||
ipsum primis in faucibus orci luctus et ultrices posuere cubilia
|
||
Curae; Nullam ut nisi a est ornare molestie non vulputate orci.
|
||
Nunc pharetra porta semper. Mauris dictum eu nulla a pulvinar. Duis
|
||
eleifend odio id ligula congue sollicitudin. Curabitur quis aliquet
|
||
nunc, ut aliquet enim. Suspendisse malesuada felis non metus
|
||
efficitur aliquet.`;
|
||
|
||
this.data.partner.records[0].txt = damnLongText;
|
||
this.data.partner.records[0].bar = false;
|
||
this.data.partner.onchanges = {
|
||
bar: function (obj) {
|
||
obj.txt = damnLongText;
|
||
},
|
||
};
|
||
const form = await createView({
|
||
arch: `
|
||
<form string="Partners">
|
||
<field name="bar"/>
|
||
<field name="txt" attrs="{'invisible': [('bar', '=', True)]}"/>
|
||
</form>`,
|
||
data: this.data,
|
||
model: 'partner',
|
||
res_id: 1,
|
||
View: FormView,
|
||
viewOptions: { mode: 'edit' },
|
||
});
|
||
|
||
const textarea = form.el.querySelector('textarea[name="txt"]');
|
||
const initialHeight = textarea.offsetHeight;
|
||
|
||
await testUtils.fields.editInput($(textarea), 'Short value');
|
||
|
||
assert.ok(textarea.offsetHeight < initialHeight,
|
||
"Textarea height should have shrank");
|
||
|
||
await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input'));
|
||
await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input'));
|
||
|
||
assert.strictEqual(textarea.offsetHeight, initialHeight,
|
||
"Textarea height should be reset");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('text fields in editable list have correct height', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.records[0].txt = "a\nb\nc\nd\ne\nf";
|
||
|
||
var list = await 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.
|
||
await 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[0].clientHeight, $textarea[0].scrollHeight,
|
||
"textarea should not have a scroll bar");
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('text fields in edit mode should resize on reset', async 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 = await 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
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
await 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', async 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 = await 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_kw/partner/get_field_translations") {
|
||
assert.deepEqual(args.args, [[1],"txt"], "should translate the txt field of the record");
|
||
return Promise.resolve([
|
||
[{lang: "en_US", source: "yop", value: "yop"}, {lang: "fr_BE", source: "yop", value: "valeur français"}],
|
||
{translation_type: "text", translation_show_source: false},
|
||
]);
|
||
}
|
||
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
|
||
return Promise.resolve([["en_US", "English"], ["fr_BE", "French (Belgium)"]]);
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
});
|
||
await testUtils.form.clickEdit(form);
|
||
var $button = form.$('textarea + .o_field_translate');
|
||
assert.strictEqual($button.length, 1, "should have a translate button");
|
||
await testUtils.dom.click($button);
|
||
assert.containsOnce($(document), '.modal', 'there should be a translation modal');
|
||
form.destroy();
|
||
_t.database.multi_lang = multiLang;
|
||
});
|
||
|
||
QUnit.test('text field translatable in create mode', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.fields.txt.translate = true;
|
||
|
||
var multiLang = _t.database.multi_lang;
|
||
_t.database.multi_lang = true;
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<group>' +
|
||
'<field name="txt"/>' +
|
||
'</group>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
});
|
||
var $button = form.$('textarea + .o_field_translate');
|
||
assert.strictEqual($button.length, 1, "should 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
this.data.partner.fields.foo.type = 'text';
|
||
var list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<list editable="top">' +
|
||
'<field name="int_field"/>' +
|
||
'<field name="foo"/>' +
|
||
'<field name="qux"/>' +
|
||
'</list>',
|
||
});
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await 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', async 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 Promise.resolve();
|
||
};
|
||
|
||
this.data.partner.records[0].foo = 'coucou.txt';
|
||
var form = await 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");
|
||
|
||
await testUtils.dom.click(form.$('a.o_field_widget[name="document"]'));
|
||
|
||
await 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");
|
||
|
||
|
||
await 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");
|
||
|
||
await 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: option accepted_file_extensions', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<form string="Partners">
|
||
<field name="document" widget="binary" options="{'accepted_file_extensions': '.dat,.bin'}"/>
|
||
</form>`
|
||
});
|
||
assert.strictEqual(form.$('input.o_input_file').attr('accept'), '.dat,.bin',
|
||
"the input should have the correct ``accept`` attribute");
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('binary fields that are readonly in create mode do not download', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
// 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 = await 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,
|
||
});
|
||
|
||
await testUtils.form.clickCreate(form);
|
||
await testUtils.fields.many2one.clickOpenDropdown('product_id');
|
||
await testUtils.fields.many2one.clickHighlightedItem('product_id');
|
||
|
||
assert.containsOnce(form, 'a.o_field_widget[name="document"]',
|
||
'The link to download the binary should be present');
|
||
assert.containsNone(form, 'a.o_field_widget[name="document"] > .fa-download',
|
||
'The download icon should not be present');
|
||
|
||
var link = form.$('a.o_field_widget[name="document"]');
|
||
assert.ok(link.is(':hidden'), "the link element should not be visible");
|
||
|
||
// force visibility to test that the clicking has also been disabled
|
||
link.removeClass('o_hidden');
|
||
testUtils.dom.click(link);
|
||
|
||
assert.verifySteps([]); // We shouldn't have passed through steps
|
||
|
||
form.destroy();
|
||
session.get_file = oldGetFile;
|
||
});
|
||
|
||
QUnit.module('FieldPdfViewer');
|
||
|
||
QUnit.test("pdf_viewer without data", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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 Promise.resolve();
|
||
}
|
||
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%2Fcontent%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", async 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) {
|
||
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 = await 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(['open']);
|
||
|
||
testUtils.mock.unpatch(field_registry.map.pdf_viewer);
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('text field rendering in list view', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var data = {
|
||
foo: {
|
||
fields: {foo: {string: "F", type: "text"}},
|
||
records: [{id: 1, foo: "some text"}]
|
||
},
|
||
};
|
||
var list = await 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("binary fields input value is empty whean clearing after uploading", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="document" filename="foo"/>' +
|
||
'<field name="foo"/>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
// // We need to convert the input type since we can't programmatically set the value of a file input
|
||
form.$('.o_input_file').attr('type', 'text').val('coucou.txt');
|
||
|
||
assert.strictEqual(form.$('.o_input_file').val(), 'coucou.txt',
|
||
"input value should be changed to \"coucou.txt\"");
|
||
|
||
await testUtils.dom.click(form.$('.o_field_binary_file > .o_clear_file_button'));
|
||
|
||
assert.strictEqual(form.$('.o_input_file').val(), '',
|
||
"input value should be empty");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('field text in editable list view', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.fields.foo.type = 'text';
|
||
|
||
var list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree string="Phonecalls" editable="top">' +
|
||
'<field name="foo"/>' +
|
||
'</tree>',
|
||
});
|
||
|
||
await 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.module('FieldImage');
|
||
|
||
QUnit.test('image fields are correctly rendered', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
this.data.partner.records[0].__last_update = '2017-02-08 10:00:00';
|
||
this.data.partner.records[0].document = MY_IMAGE;
|
||
|
||
var form = await 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,
|
||
async mockRPC(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");
|
||
}
|
||
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.strictEqual(form.$('div[name="document"] > img')[0].dataset.src,
|
||
`data:image/png;base64,${MY_IMAGE}`, "the image should have the correct src");
|
||
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 are correctly rendered with one dimension set', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
this.data.partner.fields.picture = { string: 'Picture', type: 'binary' };
|
||
this.data.partner.records[0].__last_update = '2017-02-08 10:00:00';
|
||
this.data.partner.records[0].document = 'myimage1';
|
||
this.data.partner.records[0].picture = 'myimage2';
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="document" widget="image" options="{\'size\': [180, 0]}"/> ' +
|
||
'<field name="picture" widget="image" options="{\'size\': [0, 270]}"/> ' +
|
||
'</form>',
|
||
res_id: 1,
|
||
mockRPC: function (route, args) {
|
||
if (route.startsWith('data:image/png;base64,myimage')) {
|
||
return Promise.resolve('wow');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(form, 'div[name="document"] > img', "the widget should contain an image");
|
||
assert.hasAttrValue(form.$('div[name="document"] > img'), 'width', "180",
|
||
"the image should correctly set its attributes");
|
||
const image1Style = form.$('div[name="document"] > img').attr('style');
|
||
assert.ok(['max-width: 180px', 'height: auto', 'max-height: 100%'].every(e => image1Style.includes(e)),
|
||
"the image should correctly set its style");
|
||
|
||
assert.containsOnce(form, 'div[name="picture"] > img', "the widget should contain an image");
|
||
assert.hasAttrValue(form.$('div[name="picture"] > img'), 'height', "270",
|
||
"the image should correctly set its attributes");
|
||
const image2Style = form.$('div[name="picture"] > img').attr('style');
|
||
assert.ok(['max-height: 270px', 'width: auto', 'max-width: 100%'].every(e => image2Style.includes(e)),
|
||
"the image should correctly set its style");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('image fields are correctly replaced when given an incorrect value', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
this.data.partner.records[0].__last_update = '2017-02-08 10:00:00';
|
||
this.data.partner.records[0].document = 'incorrect_base64_value';
|
||
|
||
testUtils.mock.patch(basicFields.FieldBinaryImage, {
|
||
// Delay the _render function: this will ensure that the error triggered
|
||
// by the incorrect base64 value is dispatched before the src is replaced
|
||
// (see test_utils_mock.removeSrcAttribute), since that function is called
|
||
// when the element is inserted into the DOM.
|
||
async _render() {
|
||
const result = this._super.apply(this, arguments);
|
||
await concurrency.delay(100);
|
||
return result;
|
||
},
|
||
});
|
||
|
||
const form = await 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,
|
||
});
|
||
|
||
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.strictEqual(form.$('div[name="document"] > img')[0].dataset.src,
|
||
`/web/static/img/placeholder.png`, "the image should have the correct src");
|
||
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();
|
||
testUtils.mock.unpatch(basicFields.FieldBinaryImage);
|
||
});
|
||
|
||
QUnit.test('image: option accepted_file_extensions', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<form string="Partners">
|
||
<field name="document" widget="image" options="{'accepted_file_extensions': '.png,.jpeg'}"/>
|
||
</form>`
|
||
});
|
||
assert.strictEqual(form.$('input.o_input_file').attr('accept'), '.png,.jpeg',
|
||
"the input should have the correct ``accept`` attribute");
|
||
form.destroy();
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<form string="Partners">
|
||
<field name="document" widget="image"/>
|
||
</form>`
|
||
});
|
||
assert.strictEqual(form.$('input.o_input_file').attr('accept'), 'image/*',
|
||
'the default value for the attribute "accept" on the "image" widget must be "image/*"');
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('image fields in subviews are loaded correctly', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
this.data.partner.records[0].__last_update = '2017-02-08 10:00:00';
|
||
this.data.partner.records[0].document = MY_IMAGE;
|
||
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 = await 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" mode="kanban">' +
|
||
// use kanban view as the tree will trigger edit mode
|
||
// and thus won't display the field
|
||
'<kanban>' +
|
||
'<field name="display_name"/>' +
|
||
'<templates>' +
|
||
'<t t-name="kanban-box">' +
|
||
'<div class="oe_kanban_global_click">' +
|
||
'<span><t t-esc="record.display_name.value"/></span>' +
|
||
'</div>' +
|
||
'</t>' +
|
||
'</templates>' +
|
||
'</kanban>' +
|
||
'<form>' +
|
||
'<field name="image" widget="image"/>' +
|
||
'</form>' +
|
||
'</field>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
assert.ok(
|
||
document.querySelector(`img[data-src="data:image/png;base64,${MY_IMAGE}"]`),
|
||
"The view's image is in the DOM"
|
||
);
|
||
|
||
assert.containsOnce(form, '.o_kanban_record.oe_kanban_global_click',
|
||
'There should be one record in the many2many');
|
||
|
||
// Actual flow: click on an element of the m2m to get its form view
|
||
await testUtils.dom.click(form.$('.oe_kanban_global_click'));
|
||
assert.strictEqual($('.modal').length, 1,
|
||
'The modal should have opened');
|
||
assert.ok(
|
||
document.querySelector(`img[data-src="data:image/gif;base64,${PRODUCT_IMAGE}"]`),
|
||
"The dialog's image is in the DOM"
|
||
);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('image fields in x2many list are loaded correctly', async 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 = await 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,
|
||
});
|
||
|
||
assert.containsOnce(form, 'tr.o_data_row',
|
||
'There should be one record in the many2many');
|
||
assert.ok(
|
||
document.querySelector(`img[data-src="data:image/gif;base64,${PRODUCT_IMAGE}"]`),
|
||
"The list's image is in the DOM"
|
||
);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('image fields with required attribute', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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);
|
||
},
|
||
});
|
||
|
||
await testUtils.form.clickSave(form);
|
||
|
||
assert.hasClass(form.$('.o_legacy_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();
|
||
});
|
||
|
||
/**
|
||
* Same tests than for Image fields, but for Char fields with image_url widget.
|
||
*/
|
||
QUnit.module('FieldChar-ImageUrlWidget', {
|
||
beforeEach: function () {
|
||
// specific sixth partner data for image_url widget tests
|
||
this.data.partner.records.push({id: 6, bar: false, foo: FR_FLAG_URL, int_field: 5, qux: 0.0, timmy: []});
|
||
},
|
||
});
|
||
|
||
QUnit.test('image fields are correctly rendered', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="foo" widget="image_url" options="{\'size\': [90, 90]}"/> ' +
|
||
'</form>',
|
||
res_id: 6,
|
||
});
|
||
|
||
assert.hasClass(form.$('div[name="foo"]'), 'o_field_image',
|
||
"the widget should have the correct class");
|
||
assert.containsOnce(form, 'div[name="foo"] > img',
|
||
"the widget should contain an image");
|
||
assert.strictEqual(form.$('div[name="foo"] > img')[0].dataset.src,
|
||
FR_FLAG_URL, "the image should have the correct src");
|
||
assert.hasClass(form.$('div[name="foo"] > img'), 'img-fluid',
|
||
"the image should have the correct class");
|
||
assert.hasAttrValue(form.$('div[name="foo"] > img'), 'width', "90",
|
||
"the image should correctly set its attributes");
|
||
assert.strictEqual(form.$('div[name="foo"] > img').css('max-width'), "90px",
|
||
"the image should correctly set its attributes");
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('image fields are correctly rendered with one dimension set', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
this.data.partner.fields.tortue = {string: "Tortue", type: "char", default: "a"};
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="foo" widget="image_url" options="{\'size\': [180, 0]}"/> ' +
|
||
'<field name="tortue" widget="image_url" options="{\'size\': [0, 270]}"/> ' +
|
||
'</form>',
|
||
res_id: 6,
|
||
});
|
||
|
||
assert.containsOnce(form, 'div[name="foo"] > img', "the widget should contain an image");
|
||
assert.hasAttrValue(form.$('div[name="foo"] > img'), 'width', "180",
|
||
"the image should correctly set its attributes");
|
||
const image1Style = form.$('div[name="foo"] > img').attr('style');
|
||
assert.ok(['max-width: 180px', 'height: auto', 'max-height: 100%'].every(e => image1Style.includes(e)),
|
||
"the image should correctly set its style");
|
||
|
||
assert.containsOnce(form, 'div[name="tortue"] > img', "the widget should contain an image");
|
||
assert.hasAttrValue(form.$('div[name="tortue"] > img'), 'height', "270",
|
||
"the image should correctly set its attributes");
|
||
const image2Style = form.$('div[name="tortue"] > img').attr('style');
|
||
assert.ok(['max-height: 270px', 'width: auto', 'max-width: 100%'].every(e => image2Style.includes(e)),
|
||
"the image should correctly set its style");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('image_url widget in subviews are loaded correctly', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
this.data.partner_type.fields.image = {name: 'image', type: 'char'};
|
||
this.data.partner_type.records[0].image = EN_FLAG_URL;
|
||
this.data.partner.records[5].timmy = [12];
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="foo" widget="image_url" options="{\'size\': [90, 90]}"/>' +
|
||
'<field name="timmy" widget="many2many" mode="kanban">' +
|
||
// use kanban view as the tree will trigger edit mode
|
||
// and thus won't display the field
|
||
'<kanban>' +
|
||
'<field name="display_name"/>' +
|
||
'<templates>' +
|
||
'<t t-name="kanban-box">' +
|
||
'<div class="oe_kanban_global_click">' +
|
||
'<span><t t-esc="record.display_name.value"/></span>' +
|
||
'</div>' +
|
||
'</t>' +
|
||
'</templates>' +
|
||
'</kanban>' +
|
||
'<form>' +
|
||
'<field name="image" widget="image_url"/>' +
|
||
'</form>' +
|
||
'</field>' +
|
||
'</form>',
|
||
res_id: 6,
|
||
});
|
||
assert.ok(
|
||
document.querySelector(`img[data-src="${FR_FLAG_URL}"]`),
|
||
"The view's image is in the DOM"
|
||
);
|
||
|
||
assert.containsOnce(form, '.o_kanban_record.oe_kanban_global_click',
|
||
'There should be one record in the many2many');
|
||
|
||
// Actual flow: click on an element of the m2m to get its form view
|
||
await testUtils.dom.click(form.$('.oe_kanban_global_click'));
|
||
assert.strictEqual($('.modal').length, 1,
|
||
'The modal should have opened');
|
||
assert.ok(
|
||
document.querySelector(`img[data-src="${EN_FLAG_URL}"]`),
|
||
"The dialog's image is in the DOM"
|
||
);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('image fields in x2many list are loaded correctly', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner_type.fields.image = {name: 'image', type: 'char'};
|
||
this.data.partner_type.records[0].image = EN_FLAG_URL;
|
||
this.data.partner.records[5].timmy = [12];
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="timmy" widget="many2many">' +
|
||
'<tree>' +
|
||
'<field name="image" widget="image_url"/>' +
|
||
'</tree>' +
|
||
'</field>' +
|
||
'</form>',
|
||
res_id: 6,
|
||
});
|
||
|
||
assert.containsOnce(form, 'tr.o_data_row',
|
||
'There should be one record in the many2many');
|
||
assert.ok(
|
||
document.querySelector(`img[data-src="${EN_FLAG_URL}"]`),
|
||
"The list's image is in the DOM"
|
||
);
|
||
|
||
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', async function (assert) {
|
||
// This widget is rendered with Chart.js.
|
||
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');
|
||
},
|
||
});
|
||
|
||
createView({
|
||
View: KanbanView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<kanban class="o_kanban_test">' +
|
||
'<field name="graph_type"/>' +
|
||
'<templates><t t-name="kanban-box">' +
|
||
'<div>' +
|
||
'<field name="graph_data" t-att-graph_type="record.graph_type.raw_value" widget="dashboard_graph"/>' +
|
||
'</div>' +
|
||
'</t>' +
|
||
'</templates></kanban>',
|
||
domain: [['id', 'in', [1, 2]]],
|
||
}).then(function (kanban) {
|
||
assert.verifySteps([
|
||
'on_attach_callback',
|
||
'on_attach_callback'
|
||
]);
|
||
|
||
kanban.on_detach_callback();
|
||
|
||
assert.verifySteps([
|
||
'on_detach_callback',
|
||
'on_detach_callback'
|
||
]);
|
||
|
||
kanban.destroy();
|
||
testUtils.mock.unpatch(JournalDashboardGraph);
|
||
done();
|
||
});
|
||
});
|
||
|
||
QUnit.test('graph dashboard widget is rendered correctly', async function (assert) {
|
||
var done = assert.async();
|
||
assert.expect(3);
|
||
|
||
createView({
|
||
View: KanbanView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<kanban class="o_kanban_test">' +
|
||
'<field name="graph_type"/>' +
|
||
'<templates><t t-name="kanban-box">' +
|
||
'<div>' +
|
||
'<field name="graph_data" t-att-graph_type="record.graph_type.raw_value" widget="dashboard_graph"/>' +
|
||
'</div>' +
|
||
'</t>' +
|
||
'</templates></kanban>',
|
||
domain: [['id', 'in', [1, 2]]],
|
||
}).then(function (kanban) {
|
||
concurrency.delay(0).then(function () {
|
||
assert.strictEqual(kanban.$('.o_kanban_record:first() .o_graph_barchart').length, 1,
|
||
"graph of first record should be a barchart");
|
||
assert.strictEqual(kanban.$('.o_kanban_record:nth(1) .o_dashboard_graph').length, 1,
|
||
"graph of second record should be a linechart");
|
||
|
||
// 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);
|
||
}).then(function () {
|
||
assert.strictEqual(kanban.$('.o_kanban_record:first() canvas').length, 1,
|
||
"there should be only one rendered graph by record");
|
||
|
||
kanban.destroy();
|
||
done();
|
||
});
|
||
});
|
||
});
|
||
|
||
QUnit.test('rendering of a field with dashboard_graph widget in an updated kanban view (ungrouped)', async function (assert) {
|
||
|
||
var done = assert.async();
|
||
assert.expect(2);
|
||
|
||
createView({
|
||
View: KanbanView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<kanban class="o_kanban_test">' +
|
||
'<field name="graph_type"/>' +
|
||
'<templates><t t-name="kanban-box">' +
|
||
'<div>' +
|
||
'<field name="graph_data" t-att-graph_type="record.graph_type.raw_value" widget="dashboard_graph"/>' +
|
||
'</div>' +
|
||
'</t>' +
|
||
'</templates></kanban>',
|
||
domain: [['id', 'in', [1, 2]]],
|
||
}).then(function (kanban) {
|
||
concurrency.delay(0).then(function () {
|
||
assert.containsN(kanban, '.o_dashboard_graph canvas', 2, "there should be two graph rendered");
|
||
return kanban.update({});
|
||
}).then(function () {
|
||
return concurrency.delay(0); // one graph is re-rendered
|
||
}).then(function () {
|
||
assert.containsN(kanban, '.o_dashboard_graph canvas', 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)', async function (assert) {
|
||
|
||
var done = assert.async();
|
||
assert.expect(2);
|
||
|
||
createView({
|
||
View: KanbanView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<kanban class="o_kanban_test">' +
|
||
'<field name="graph_type"/>' +
|
||
'<templates><t t-name="kanban-box">' +
|
||
'<div>' +
|
||
'<field name="graph_data" t-att-graph_type="record.graph_type.raw_value" widget="dashboard_graph"/>' +
|
||
'</div>' +
|
||
'</t>' +
|
||
'</templates></kanban>',
|
||
domain: [['id', 'in', [1, 2]]],
|
||
}).then(function (kanban) {
|
||
concurrency.delay(0).then(function () {
|
||
assert.containsN(kanban, '.o_dashboard_graph canvas', 2, "there should be two graph rendered");
|
||
return kanban.update({groupBy: ['selection'], domain: [['int_field', '=', 10]]});
|
||
}).then(function () {
|
||
assert.containsOnce(kanban, '.o_dashboard_graph canvas', "there should be one graph rendered");
|
||
kanban.destroy();
|
||
done();
|
||
});
|
||
});
|
||
});
|
||
|
||
QUnit.module('AceEditor');
|
||
|
||
QUnit.test('ace widget on text fields works', async function (assert) {
|
||
assert.expect(2);
|
||
var done = assert.async();
|
||
|
||
this.data.partner.fields.foo.type = 'text';
|
||
testUtils.createView({
|
||
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', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
this.data.partner.records[0].p = [2, 4];
|
||
var form = await 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");
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var list = await 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('FieldDateRange');
|
||
|
||
QUnit.test('Datetime field without quickedit [REQUIRE FOCUS]', async function (assert) {
|
||
assert.expect(21);
|
||
|
||
this.data.partner.fields.datetime_end = {string: 'Datetime End', type: 'datetime'};
|
||
this.data.partner.records[0].datetime_end = '2017-03-13 00:00:00';
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="datetime" widget="daterange" options="{\'related_end_date\': \'datetime_end\'}"/>' +
|
||
'<field name="datetime_end" widget="daterange" options="{\'related_start_date\': \'datetime\'}"/>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
session: {
|
||
getTZOffset: function () {
|
||
return 330;
|
||
},
|
||
},
|
||
});
|
||
|
||
// Check date display correctly in readonly
|
||
assert.strictEqual(form.$('.o_field_date_range:first').text(), '02/08/2017 15:30:00',
|
||
"the start date should be correctly displayed in readonly");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').text(), '03/13/2017 05:30:00',
|
||
"the end date should be correctly displayed in readonly");
|
||
|
||
// Edit
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
// Check date range picker initialization
|
||
assert.containsN(document.body, '.daterangepicker', 2,
|
||
"should initialize 2 date range picker");
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'none',
|
||
"first date range picker should be closed initially");
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'none',
|
||
"second date range picker should be closed initially");
|
||
|
||
// open the first one
|
||
await testUtils.dom.click(form.$('.o_field_date_range:first'));
|
||
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'block',
|
||
"first date range picker should be opened");
|
||
assert.strictEqual($('.daterangepicker:first .drp-calendar.left .active.start-date').text(), '8',
|
||
"active start date should be '8' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:first .drp-calendar.left .hourselect').val(), '15',
|
||
"active start date hour should be '15' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:first .drp-calendar.left .minuteselect').val(), '30',
|
||
"active start date minute should be '30' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:first .drp-calendar.right .active.end-date').text(), '13',
|
||
"active end date should be '13' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:first .drp-calendar.right .hourselect').val(), '5',
|
||
"active end date hour should be '5' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:first .drp-calendar.right .minuteselect').val(), '30',
|
||
"active end date minute should be '30' in date range picker");
|
||
assert.containsN($('.daterangepicker:first .drp-calendar.left .minuteselect'), 'option', 12,
|
||
"minute selection should contain 12 options (1 for each 5 minutes)");
|
||
// Close picker
|
||
await testUtils.dom.click($('.daterangepicker:first .cancelBtn'));
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'none',
|
||
"date range picker should be closed");
|
||
|
||
// Try to check with end date
|
||
await testUtils.dom.click(form.$('.o_field_date_range:last'));
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'block',
|
||
"date range picker should be opened");
|
||
assert.strictEqual($('.daterangepicker:last .drp-calendar.left .active.start-date').text(), '8',
|
||
"active start date should be '8' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:last .drp-calendar.left .hourselect').val(), '15',
|
||
"active start date hour should be '15' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:last .drp-calendar.left .minuteselect').val(), '30',
|
||
"active start date minute should be '30' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:last .drp-calendar.right .active.end-date').text(), '13',
|
||
"active end date should be '13' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:last .drp-calendar.right .hourselect').val(), '5',
|
||
"active end date hour should be '5' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:last .drp-calendar.right .minuteselect').val(), '30',
|
||
"active end date minute should be '30' in date range picker");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test("Datetime field with option format type is 'date'", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.fields.datetime_end = {string: 'Datetime End', type: 'datetime'};
|
||
this.data.partner.records[0].datetime_end = '2017-03-13 00:00:00';
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<form>
|
||
<field name="datetime" widget="daterange" options="{'related_end_date': 'datetime_end', 'format_type': 'date'}"/>'
|
||
<field name="datetime_end" widget="daterange" options="{'related_start_date': 'datetime', 'format_type': 'date'}"/>'
|
||
</form>`,
|
||
res_id: 1,
|
||
session: {
|
||
getTZOffset() {
|
||
return 330;
|
||
},
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(form.el.querySelector('.o_field_date_range[name="datetime"]').innerText, '02/08/2017',
|
||
"the start date should only show date when option formatType is Date");
|
||
assert.strictEqual(form.el.querySelector('.o_field_date_range[name="datetime_end"]').innerText, '03/13/2017',
|
||
"the end date should only show date when option formatType is Date");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test("Date field with option format type is 'datetime'", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.fields.date_end = {string: 'Date End', type: 'date'};
|
||
this.data.partner.records[0].date_end = '2017-03-13';
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<form>
|
||
<field name="date" widget="daterange" options="{'related_end_date': 'date_end', 'format_type': 'datetime'}"/>
|
||
<field name="date_end" widget="daterange" options="{'related_start_date': 'date', 'format_type': 'datetime'}"/>
|
||
</form>`,
|
||
res_id: 1,
|
||
session: {
|
||
getTZOffset() {
|
||
return 330;
|
||
},
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(form.el.querySelector('.o_field_date_range[name="date"]').innerText, '02/03/2017 05:30:00',
|
||
"the start date should show date with time when option format_type is datatime");
|
||
assert.strictEqual(form.el.querySelector('.o_field_date_range[name="date_end"]').innerText, '03/13/2017 05:30:00',
|
||
"the end date should show date with time when option format_type is datatime");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Date field without quickedit [REQUIRE FOCUS]', async function (assert) {
|
||
assert.expect(19);
|
||
|
||
this.data.partner.fields.date_end = {string: 'Date End', type: 'date'};
|
||
this.data.partner.records[0].date_end = '2017-02-08';
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="date" widget="daterange" options="{\'related_end_date\': \'date_end\'}"/>' +
|
||
'<field name="date_end" widget="daterange" options="{\'related_start_date\': \'date\'}"/>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
session: {
|
||
getTZOffset: function () {
|
||
return 330;
|
||
},
|
||
},
|
||
});
|
||
|
||
// Check date display correctly in readonly
|
||
assert.strictEqual(form.$('.o_field_date_range:first').text(), '02/03/2017',
|
||
"the start date should be correctly displayed in readonly");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').text(), '02/08/2017',
|
||
"the end date should be correctly displayed in readonly");
|
||
|
||
// Edit
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
// Check date range picker initialization
|
||
assert.containsN(document.body, '.daterangepicker', 2,
|
||
"should initialize 2 date range picker");
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'none',
|
||
"first date range picker should be closed initially");
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'none',
|
||
"second date range picker should be closed initially");
|
||
|
||
// open the first one
|
||
await testUtils.dom.click(form.$('.o_field_date_range:first'));
|
||
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'block',
|
||
"first date range picker should be opened");
|
||
assert.strictEqual($('.daterangepicker:first .active.start-date').text(), '3',
|
||
"active start date should be '3' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:first .active.end-date').text(), '8',
|
||
"active end date should be '8' in date range picker");
|
||
|
||
// Change date
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:first .drp-calendar.left .available:contains("16")'), 'mousedown');
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:first .drp-calendar.right .available:contains("12")'), 'mousedown');
|
||
await testUtils.dom.click($('.daterangepicker:first .applyBtn'));
|
||
|
||
// Check date after change
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'none',
|
||
"date range picker should be closed");
|
||
assert.strictEqual(form.$('.o_field_date_range:first').val(), '02/16/2017',
|
||
"the date should be '02/16/2017'");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').val(), '03/12/2017',
|
||
"'the date should be '03/12/2017'");
|
||
|
||
// Try to change range with end date
|
||
await testUtils.dom.click(form.$('.o_field_date_range:last'));
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'block',
|
||
"date range picker should be opened");
|
||
assert.strictEqual($('.daterangepicker:last .active.start-date').text(), '16',
|
||
"start date should be a 16 in date range picker");
|
||
assert.strictEqual($('.daterangepicker:last .active.end-date').text(), '12',
|
||
"end date should be a 12 in date range picker");
|
||
|
||
// Change date
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:last .drp-calendar.left .available:contains("13")'), 'mousedown');
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:last .drp-calendar.right .available:contains("18")'), 'mousedown');
|
||
await testUtils.dom.click($('.daterangepicker:last .applyBtn'));
|
||
|
||
// Check date after change
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'none',
|
||
"date range picker should be closed");
|
||
assert.strictEqual(form.$('.o_field_date_range:first').val(), '02/13/2017',
|
||
"the start date should be '02/13/2017'");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').val(), '03/18/2017',
|
||
"the end date should be '03/18/2017'");
|
||
|
||
// Save
|
||
await testUtils.form.clickSave(form);
|
||
|
||
// Check date after save
|
||
assert.strictEqual(form.$('.o_field_date_range:first').text(), '02/13/2017',
|
||
"the start date should be '02/13/2017' after save");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').text(), '03/18/2017',
|
||
"the end date should be '03/18/2017' after save");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Date field with quickedit [REQUIRE FOCUS]', async function (assert) {
|
||
assert.expect(18);
|
||
|
||
this.data.partner.fields.date_end = {string: 'Date End', type: 'date'};
|
||
this.data.partner.records[0].date_end = '2017-02-08';
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="date" widget="daterange" options="{\'related_end_date\': \'date_end\'}"/>' +
|
||
'<field name="date_end" widget="daterange" options="{\'related_start_date\': \'date\'}"/>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
session: {
|
||
// #tzoffset_daterange
|
||
// Date field should not have an offset as they are ignored.
|
||
// However, in the test environement, a UTC timezone is set to run all tests. And if any code does not use the safe timezone method
|
||
// provided by the framework (which happens in this case inside the date range picker lib), unexpected behavior kicks in as the timezone
|
||
// of the dev machine collides with the timezone set by the test env.
|
||
// To avoid failing test on dev's local machines, a hack is to apply an timezone offset greater than the difference between UTC and the dev's
|
||
// machine timezone. For belgium, > 60 is enough. For India, > 5h30 is required, hence 330.
|
||
// Note that prod and runbot will never have a problem with this, it only happens as you mock the getTZOffset method (like in tests).
|
||
getTZOffset: function () {
|
||
return 330;
|
||
},
|
||
},
|
||
});
|
||
|
||
// Check date display correctly in readonly
|
||
assert.strictEqual(form.$('.o_field_date_range:first').text(), '02/03/2017',
|
||
"the start date should be correctly displayed in readonly");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').text(), '02/08/2017',
|
||
"the end date should be correctly displayed in readonly");
|
||
|
||
// open the first one with quick edit
|
||
await testUtils.dom.click(form.$('.o_field_date_range:first'));
|
||
|
||
// Check date range picker initialization
|
||
assert.containsN(document.body, '.daterangepicker', 2,
|
||
"should initialize 2 date range picker");
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'block',
|
||
"first date range picker should be opened initially");
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'none',
|
||
"second date range picker should be closed initially");
|
||
assert.strictEqual($('.daterangepicker:first .active.start-date').text(), '3',
|
||
"active start date should be '3' in date range picker");
|
||
assert.strictEqual($('.daterangepicker:first .active.end-date').text(), '8',
|
||
"active end date should be '8' in date range picker");
|
||
|
||
// Change date
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:first .drp-calendar.left .available:contains("16")'), 'mousedown');
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:first .drp-calendar.right .available:contains("12")'), 'mousedown');
|
||
await testUtils.dom.click($('.daterangepicker:first .applyBtn'));
|
||
|
||
// Check date after change
|
||
assert.strictEqual($('.daterangepicker:first').css('display'), 'none',
|
||
"date range picker should be closed");
|
||
assert.strictEqual(form.$('.o_field_date_range:first').val(), '02/16/2017',
|
||
"the date should be '02/16/2017'");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').val(), '03/12/2017',
|
||
"'the date should be '03/12/2017'");
|
||
|
||
// Try to change range with end date
|
||
await testUtils.dom.click(form.$('.o_field_date_range:last'));
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'block',
|
||
"date range picker should be opened");
|
||
assert.strictEqual($('.daterangepicker:last .active.start-date').text(), '16',
|
||
"start date should be a 16 in date range picker");
|
||
assert.strictEqual($('.daterangepicker:last .active.end-date').text(), '12',
|
||
"end date should be a 12 in date range picker");
|
||
|
||
// Change date
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:last .drp-calendar.left .available:contains("13")'), 'mousedown');
|
||
await testUtils.dom.triggerMouseEvent($('.daterangepicker:last .drp-calendar.right .available:contains("18")'), 'mousedown');
|
||
await testUtils.dom.click($('.daterangepicker:last .applyBtn'));
|
||
|
||
// Check date after change
|
||
assert.strictEqual($('.daterangepicker:last').css('display'), 'none',
|
||
"date range picker should be closed");
|
||
assert.strictEqual(form.$('.o_field_date_range:first').val(), '02/13/2017',
|
||
"the start date should be '02/13/2017'");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').val(), '03/18/2017',
|
||
"the end date should be '03/18/2017'");
|
||
|
||
// Save
|
||
await testUtils.form.clickSave(form);
|
||
|
||
// Check date after save
|
||
assert.strictEqual(form.$('.o_field_date_range:first').text(), '02/13/2017',
|
||
"the start date should be '02/13/2017' after save");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').text(), '03/18/2017',
|
||
"the end date should be '03/18/2017' after save");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('daterangepicker should disappear on scrolling outside of it', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.fields.datetime_end = {string: 'Datetime End', type: 'datetime'};
|
||
this.data.partner.records[0].datetime_end = '2017-03-13 00:00:00';
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="datetime" widget="daterange" options="{'related_end_date': 'datetime_end'}"/>
|
||
<field name="datetime_end" widget="daterange" options="{'related_start_date': 'datetime'}"/>
|
||
</form>`,
|
||
res_id: 1,
|
||
});
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.dom.click(form.$('.o_field_date_range:first'));
|
||
|
||
assert.isVisible($('.daterangepicker:first'), "date range picker should be opened");
|
||
|
||
form.el.dispatchEvent(new Event('scroll'));
|
||
assert.isNotVisible($('.daterangepicker:first'), "date range picker should be closed");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Datetime field manually input value should send utc value to server', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
this.data.partner.fields.datetime_end = { string: 'Datetime End', type: 'datetime' };
|
||
this.data.partner.records[0].datetime_end = '2017-03-13 00:00:00';
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="datetime" widget="daterange" options="{'related_end_date': 'datetime_end'}"/>
|
||
<field name="datetime_end" widget="daterange" options="{'related_start_date': 'datetime'}"/>
|
||
</form>`,
|
||
res_id: 1,
|
||
session: {
|
||
getTZOffset: function () {
|
||
return 330;
|
||
},
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.deepEqual(args.args[1], { datetime: '2017-02-08 06:00:00' });
|
||
}
|
||
return this._super(...arguments);
|
||
},
|
||
});
|
||
|
||
// check date display correctly in readonly
|
||
assert.strictEqual(form.$('.o_field_date_range:first').text(), '02/08/2017 15:30:00',
|
||
"the start date should be correctly displayed in readonly");
|
||
assert.strictEqual(form.$('.o_field_date_range:last').text(), '03/13/2017 05:30:00',
|
||
"the end date should be correctly displayed in readonly");
|
||
|
||
// edit form
|
||
await testUtils.form.clickEdit(form);
|
||
// update input for Datetime
|
||
await testUtils.fields.editInput(form.$('.o_field_date_range:first'), '02/08/2017 11:30:00');
|
||
// save form
|
||
await testUtils.form.clickSave(form);
|
||
|
||
assert.strictEqual(form.$('.o_field_date_range:first').text(), '02/08/2017 11:30:00',
|
||
"the start date should be correctly displayed in readonly after manual update");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Daterange field manually input wrong value should show toaster', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
this.data.partner.fields.date_end = { string: 'Date End', type: 'date' };
|
||
this.data.partner.records[0].date_end = '2017-02-08';
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="date" widget="daterange" options="{'related_end_date': 'date_end'}"/>
|
||
<field name="date_end" widget="daterange" options="{'related_start_date': 'date'}"/>
|
||
</form>`,
|
||
interceptsPropagate: {
|
||
call_service: function (ev) {
|
||
if (ev.data.service === 'notification') {
|
||
assert.strictEqual(ev.data.method, 'notify');
|
||
assert.strictEqual(ev.data.args[0].title, 'Invalid fields:');
|
||
assert.strictEqual(ev.data.args[0].message.toString(), '<ul><li>A date</li></ul>');
|
||
}
|
||
}
|
||
},
|
||
});
|
||
|
||
await testUtils.fields.editInput(form.$('.o_field_date_range:first'), 'blabla');
|
||
// click outside daterange field
|
||
await testUtils.dom.click(form.$el);
|
||
assert.hasClass(form.$('input[name=date]'), 'o_field_invalid',
|
||
"date field should be displayed as invalid");
|
||
// update input date with right value
|
||
await testUtils.fields.editInput(form.$('.o_field_date_range:first'), '02/08/2017');
|
||
assert.doesNotHaveClass(form.$('input[name=date]'), 'o_field_invalid',
|
||
"date field should not be displayed as invalid now");
|
||
|
||
// again enter wrong value and try to save should raise invalid fields value
|
||
await testUtils.fields.editInput(form.$('.o_field_date_range:first'), 'blabla');
|
||
await testUtils.form.clickSave(form);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldDate');
|
||
|
||
QUnit.test('date field: toggle datepicker [REQUIRE FOCUS]', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
var form = await 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");
|
||
|
||
await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
|
||
assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 1,
|
||
"datepicker should be opened");
|
||
|
||
// focus another field
|
||
await 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: toggle datepicker far in the future', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
this.data.partner.records = [{
|
||
id: 1,
|
||
date: "9999-12-30",
|
||
foo: "yop",
|
||
}]
|
||
|
||
var form = await 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',
|
||
},
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
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
|
||
form.$('.o_field_widget[name=foo]').click().focus();
|
||
|
||
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', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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: set an invalid date when the field is already set', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners"><field name="date"/></form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
var $input = form.$('.o_field_widget[name=date] input');
|
||
|
||
assert.strictEqual($input.val(), "02/03/2017");
|
||
|
||
$input.val('mmmh').trigger('change');
|
||
assert.strictEqual($input.val(), "02/03/2017", "should have reset the original value");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('date field: set an invalid date when the field is not set yet', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners"><field name="date"/></form>',
|
||
res_id: 4,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
var $input = form.$('.o_field_widget[name=date] input');
|
||
|
||
assert.strictEqual($input.text(), "");
|
||
|
||
$input.val('mmmh').trigger('change');
|
||
assert.strictEqual($input.text(), "", "The date field should be empty");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('date field value should not set on first click', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners"><field name="date"/></form>',
|
||
res_id: 4,
|
||
});
|
||
|
||
await 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)', async function (assert) {
|
||
assert.expect(8);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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)', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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
|
||
await 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 dropdown disappears on scroll', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:
|
||
'<form>' +
|
||
'<div class="scrollable" style="height: 2000px;">' +
|
||
'<field name="date"/>' +
|
||
'</div>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
|
||
assert.containsOnce($('body'), '.bootstrap-datetimepicker-widget', "datepicker should be opened");
|
||
|
||
form.el.dispatchEvent(new Event('wheel'));
|
||
assert.containsNone($('body'), '.bootstrap-datetimepicker-widget', "datepicker should be closed");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('date field with warn_future option', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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
|
||
await testUtils.form.clickEdit(form);
|
||
// open datepicker and select another value
|
||
await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year').eq(11));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(11));
|
||
await 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");
|
||
|
||
await 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', async 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 = await 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
|
||
await testUtils.form.clickEdit(form);
|
||
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
|
||
await testUtils.form.clickSave(form);
|
||
|
||
//Create a new record
|
||
await testUtils.form.clickCreate(form);
|
||
|
||
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', async function (assert) {
|
||
assert.expect(8);
|
||
|
||
var list = await 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');
|
||
await 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
|
||
await testUtils.dom.openDatepicker(list.$('.o_datepicker'));
|
||
assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open');
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(1));
|
||
await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017',
|
||
'the date should be correct in edit mode');
|
||
|
||
await testUtils.fields.editAndTrigger(form.$('.o_datepicker_input'), '', ['input', 'change', 'focusout']);
|
||
assert.strictEqual(form.$('.o_datepicker_input').val(), '',
|
||
'should have correctly removed the value');
|
||
|
||
// save
|
||
await 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', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
var form = await 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']);
|
||
await 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', async function (assert) {
|
||
assert.expect(3);
|
||
var done = assert.async();
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><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');
|
||
const active = document.activeElement;
|
||
assert.equal(active.tagName, 'INPUT', "The datepicker input should be focused");
|
||
const sel = active.value.slice(active.selectionStart, active.selectionEnd);
|
||
assert.strictEqual(sel, "02/03/2017",
|
||
'The whole input of the date field should have been selected');
|
||
done();
|
||
}
|
||
});
|
||
|
||
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('date field support internalization', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var originalLocale = moment.locale();
|
||
var originalParameters = _.clone(core._t.database.parameters);
|
||
|
||
_.extend(core._t.database.parameters, {date_format: '%d. %b %Y', time_format: '%H:%M:%S'});
|
||
moment.defineLocale('norvegianForTest', {
|
||
monthsShort: 'jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.'.split('_'),
|
||
monthsParseExact: true,
|
||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||
ordinal: '%d.',
|
||
});
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners"><field name="date"/></form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
var dateViewForm = form.$('.o_field_date').text();
|
||
await testUtils.dom.click(form.$buttons.find('.o_form_button_edit'));
|
||
await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
assert.strictEqual(form.$('.o_datepicker_input').val(), dateViewForm,
|
||
"input date field should be the same as it was in the view form");
|
||
|
||
await testUtils.dom.click($('.day:contains(30)'));
|
||
var dateEditForm = form.$('.o_datepicker_input').val();
|
||
await testUtils.dom.click(form.$buttons.find('.o_form_button_save'));
|
||
assert.strictEqual(form.$('.o_field_date').text(), dateEditForm,
|
||
"date field should be the same as the one selected in the view form");
|
||
|
||
moment.locale(originalLocale);
|
||
moment.updateLocale('norvegianForTest', null);
|
||
core._t.database.parameters = originalParameters;
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('date field: hit enter should update value', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const form = await 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',
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
session: {
|
||
getTZOffset: function () {
|
||
return 120;
|
||
},
|
||
},
|
||
});
|
||
|
||
const year = (new Date()).getFullYear();
|
||
|
||
await testUtils.fields.editInput(form.el.querySelector('input[name="date"]'), '01/08');
|
||
await testUtils.fields.triggerKeydown(form.el.querySelector('input[name="date"]'), 'enter');
|
||
assert.strictEqual(form.el.querySelector('input[name="date"]').value, '01/08/' + year);
|
||
|
||
await testUtils.fields.editInput(form.el.querySelector('input[name="date"]'), '08/01');
|
||
await testUtils.fields.triggerKeydown(form.el.querySelector('input[name="date"]'), 'enter');
|
||
assert.strictEqual(form.el.querySelector('input[name="date"]').value, '08/01/' + year);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldDatetime');
|
||
|
||
QUnit.test('datetime field in form view', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
var form = await 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
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('.o_datepicker_input').val(), expectedDateString,
|
||
'the datetime should be correct in edit mode');
|
||
|
||
// datepicker should not open on focus
|
||
assert.containsNone($('body'), '.bootstrap-datetimepicker-widget');
|
||
|
||
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
assert.containsOnce($('body'), '.bootstrap-datetimepicker-widget');
|
||
|
||
// select 22 February at 8:23:33
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second'));
|
||
await 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
|
||
await 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 fields do not trigger fieldChange before datetime completly picked', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
this.data.partner.onchanges = {
|
||
datetime: function () {},
|
||
};
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><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;
|
||
},
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'onchange') {
|
||
assert.step('onchange');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
|
||
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
assert.containsOnce($('body'), '.bootstrap-datetimepicker-widget');
|
||
|
||
// select a date and time
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second'));
|
||
assert.verifySteps([], "should not have done any onchange yet");
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)'));
|
||
|
||
assert.containsNone($('body'), '.bootstrap-datetimepicker-widget');
|
||
assert.strictEqual(form.$('.o_datepicker_input').val(), "04/22/2017 08:25:35");
|
||
assert.verifySteps(['onchange'], "should have done only one onchange");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('datetime field not visible in form view should not capture the focus on keyboard navigation', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await 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', async 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 = await 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');
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(9);
|
||
|
||
var list = await 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
|
||
await 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');
|
||
|
||
assert.containsNone($('body'), '.bootstrap-datetimepicker-widget');
|
||
testUtils.dom.openDatepicker(list.$('.o_datepicker'));
|
||
assert.containsOnce($('body'), '.bootstrap-datetimepicker-widget');
|
||
|
||
// select 22 February at 8:23:33
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second'));
|
||
await 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
|
||
await 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('multi edition of datetime field in list view: edit date in input', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree multi_edit="1">' +
|
||
'<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;
|
||
},
|
||
},
|
||
});
|
||
|
||
// select two records and edit them
|
||
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
|
||
await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
|
||
|
||
await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell'));
|
||
assert.containsOnce(list, 'input.o_datepicker_input');
|
||
list.$('.o_datepicker_input').val("10/02/2019 09:00:00");
|
||
await testUtils.dom.triggerEvents(list.$('.o_datepicker_input'), ['change']);
|
||
|
||
assert.containsOnce(document.body, '.modal');
|
||
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
|
||
|
||
assert.strictEqual(list.$('.o_data_row:first .o_data_cell').text(), "10/02/2019 09:00:00");
|
||
assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), "10/02/2019 09:00:00");
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('datetime field remove value', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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
|
||
await 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');
|
||
|
||
await testUtils.fields.editAndTrigger($('.o_datepicker_input'), '', ['input', 'change', 'focusout']);
|
||
assert.strictEqual(form.$('.o_datepicker_input').val(), '',
|
||
"should have an empty input");
|
||
|
||
// save
|
||
await 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)', async 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 = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners">' +
|
||
'<field name="p">' +
|
||
'<tree>' +
|
||
'<field name="datetime"/>' +
|
||
'</tree>' +
|
||
'<form>' +
|
||
// display datetime in readonly as modal will open in edit
|
||
'<field name="datetime" widget="date" attrs="{\'readonly\': 1}"/>' +
|
||
'</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
|
||
await 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)', async 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 = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners">' +
|
||
'<field name="p">' +
|
||
'<tree>' +
|
||
'<field name="datetime"/>' +
|
||
'</tree>' +
|
||
'<form>' +
|
||
// display datetime in readonly as modal will open in edit
|
||
'<field name="datetime" widget="date" attrs="{\'readonly\': 1}"/>' +
|
||
'</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
|
||
await 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', async 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 = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners">' +
|
||
'<field name="datetime" ' +
|
||
'options=\'{"datepicker": {"daysOfWeekDisabled": [0, 6]}}\'/>' +
|
||
'</form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
await testUtils.form.clickCreate(form);
|
||
testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
$.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.test('datetime field: hit enter should update value', async function (assert) {
|
||
/*
|
||
This test verifies that the field datetime is correctly computed when:
|
||
- we press enter to validate our entry
|
||
- we click outside the field to validate our entry
|
||
- we save
|
||
*/
|
||
assert.expect(3);
|
||
|
||
const form = await 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',
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
session: {
|
||
getTZOffset: function () {
|
||
return 120;
|
||
},
|
||
},
|
||
});
|
||
|
||
const datetime = form.el.querySelector('input[name="datetime"]');
|
||
|
||
// Enter a beginning of date and press enter to validate
|
||
await testUtils.fields.editInput(datetime, '01/08/22 14:30:40');
|
||
await testUtils.fields.triggerKeydown(datetime, 'enter');
|
||
|
||
const datetimeValue = `01/08/2022 14:30:40`;
|
||
|
||
assert.strictEqual(datetime.value, datetimeValue);
|
||
|
||
// Click outside the field to check that the field is not changed
|
||
await testUtils.dom.click(form.$el);
|
||
assert.strictEqual(datetime.value, datetimeValue);
|
||
|
||
// Save and check that it's still ok
|
||
await testUtils.form.clickSave(form);
|
||
|
||
const { textContent } = form.el.querySelector('span[name="datetime"]')
|
||
assert.strictEqual(textContent, datetimeValue);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('datetime field with date widget: hit enter should update value', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners"><field name="datetime" widget="date"/></form>',
|
||
res_id: 1,
|
||
translateParameters: { // Avoid issues due to localization formats
|
||
date_format: '%m/%d/%Y',
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
session: {
|
||
getTZOffset: function () {
|
||
return 120;
|
||
},
|
||
},
|
||
});
|
||
|
||
const datetime = form.el.querySelector('input[name="datetime"]');
|
||
|
||
await testUtils.fields.editInput(datetime, '01/08/22');
|
||
await testUtils.fields.triggerKeydown(datetime, 'enter');
|
||
assert.strictEqual(datetime.value, '01/08/2022');
|
||
|
||
// Click outside the field to check that the field is not changed
|
||
await testUtils.dom.click(form.$el);
|
||
assert.strictEqual(datetime.value, '01/08/2022');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test("datetime field: use picker with arabic numbering system", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const symbols = [
|
||
["1", "١"],
|
||
["2", "٢"],
|
||
["3", "٣"],
|
||
["4", "٤"],
|
||
["5", "٥"],
|
||
["6", "٦"],
|
||
["7", "٧"],
|
||
["8", "٨"],
|
||
["9", "٩"],
|
||
["0", "٠"],
|
||
];
|
||
const symbolMap = Object.fromEntries(symbols);
|
||
const numberMap = Object.fromEntries(symbols.map(([latn, arab]) => [arab, latn]));
|
||
|
||
const originalLocale = moment.locale();
|
||
moment.defineLocale("TEST_ar", {
|
||
preparse:
|
||
(string) => string
|
||
.replace(/\u200f/g, "")
|
||
.replace(/[١٢٣٤٥٦٧٨٩٠]/g, (match) => numberMap[match])
|
||
.replace(/،/g, ","),
|
||
postformat:
|
||
(string) => string
|
||
.replace(/\d/g, (match) => symbolMap[match])
|
||
.replace(/,/g, "،"),
|
||
});
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: "partner",
|
||
data: this.data,
|
||
arch: /* xml */ `
|
||
<form string="Partners">
|
||
<field name="datetime" />
|
||
</form>
|
||
`,
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: "edit",
|
||
},
|
||
});
|
||
|
||
const getInput = () => form.el.querySelector("[name=datetime] input")
|
||
const click = (el) => testUtils.dom.click($(el));
|
||
|
||
assert.strictEqual(getInput().value, "٠٢/٠٨/٢٠١٧ ١٠:٠٠:٠٠");
|
||
|
||
await click(getInput());
|
||
|
||
await click(document.querySelector("[data-action=togglePicker]"));
|
||
|
||
await click(document.querySelector("[data-action=showMinutes]"));
|
||
await click(document.querySelectorAll("[data-action=selectMinute]")[9]);
|
||
|
||
await click(document.querySelector("[data-action=showSeconds]"));
|
||
await click(document.querySelectorAll("[data-action=selectSecond]")[3]);
|
||
|
||
assert.strictEqual(getInput().value, "٠٢/٠٨/٢٠١٧ ١٠:٤٥:١٥");
|
||
|
||
moment.locale(originalLocale);
|
||
moment.updateLocale("TEST_ar", null);
|
||
|
||
form.destroy();
|
||
})
|
||
|
||
QUnit.module('RemainingDays');
|
||
|
||
QUnit.test('remaining_days widget on a date field in list view', async function (assert) {
|
||
assert.expect(16);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, date: '2017-10-08' }, // today
|
||
{ id: 2, date: '2017-10-09' }, // tomorrow
|
||
{ id: 3, date: '2017-10-07' }, // yesterday
|
||
{ id: 4, date: '2017-10-10' }, // + 2 days
|
||
{ id: 5, date: '2017-10-05' }, // - 3 days
|
||
{ id: 6, date: '2018-02-08' }, // + 4 months (diff >= 100 days)
|
||
{ id: 7, date: '2017-06-08' }, // - 4 months (diff >= 100 days)
|
||
{ id: 8, date: false },
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree><field name="date" widget="remaining_days"/></tree>',
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(2)').text(), 'Yesterday');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(3)').text(), 'In 2 days');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(4)').text(), '3 days ago');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(5)').text(), '02/08/2018');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(6)').text(), '06/08/2017');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(7)').text(), '');
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0) .o_field_widget').attr('title'), '10/08/2017');
|
||
|
||
assert.hasClass(list.$('.o_data_cell:nth(0) div'), 'fw-bold text-warning');
|
||
assert.doesNotHaveClass(list.$('.o_data_cell:nth(1) div'), 'fw-bold text-warning text-danger');
|
||
assert.hasClass(list.$('.o_data_cell:nth(2) div'), 'fw-bold text-danger');
|
||
assert.doesNotHaveClass(list.$('.o_data_cell:nth(3) div'), 'fw-bold text-warning text-danger');
|
||
assert.hasClass(list.$('.o_data_cell:nth(4) div'), 'fw-bold text-danger');
|
||
assert.doesNotHaveClass(list.$('.o_data_cell:nth(5) div'), 'fw-bold text-warning text-danger');
|
||
assert.hasClass(list.$('.o_data_cell:nth(6) div'), 'fw-bold text-danger');
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget on a date field in multi edit list view', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, date: '2017-10-08' }, // today
|
||
{ id: 2, date: '2017-10-09' }, // tomorrow
|
||
{ id: 8, date: false },
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree multi_edit="1"><field name="date" widget="remaining_days"/></tree>',
|
||
translateParameters: { // Avoid issues due to localization formats
|
||
date_format: '%m/%d/%Y',
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow');
|
||
|
||
// select two records and edit them
|
||
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
|
||
await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
|
||
|
||
await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell'));
|
||
assert.containsOnce(list, 'input.o_datepicker_input', 'should have date picker input');
|
||
await testUtils.fields.editAndTrigger(list.$('.o_datepicker_input'), '10/10/2017', ['input', 'change', 'focusout']);
|
||
|
||
assert.containsOnce(document.body, '.modal');
|
||
assert.strictEqual($('.modal .o_field_widget').text(), "In 2 days",
|
||
"should have 'In 2 days' value to change");
|
||
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
|
||
|
||
assert.strictEqual(list.$('.o_data_row:first .o_data_cell').text(), "In 2 days",
|
||
"should have 'In 2 days' as date field value");
|
||
assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), "In 2 days",
|
||
"should have 'In 2 days' as date field value");
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget, enter empty value manually in edit list view', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, date: '2017-10-08' }, // today
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree multi_edit="1"><field name="date" widget="remaining_days"/></tree>',
|
||
translateParameters: { // Avoid issues due to localization formats
|
||
date_format: '%m/%d/%Y',
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
|
||
// select two records and edit them
|
||
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
|
||
|
||
await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell'));
|
||
assert.containsOnce(list, 'input.o_datepicker_input', 'should have date picker input');
|
||
await testUtils.fields.editAndTrigger(list.$('.o_datepicker_input'), '', ['input', 'change']);
|
||
await testUtils.dom.click(list.$el);
|
||
|
||
assert.containsNone(document.body, '.modal');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), '');
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget, enter wrong value manually in multi edit list view', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, date: '2017-10-08' }, // today
|
||
{ id: 2, date: '2017-10-09' }, // tomorrow
|
||
{ id: 8, date: false },
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree multi_edit="1"><field name="date" widget="remaining_days"/></tree>',
|
||
translateParameters: { // Avoid issues due to localization formats
|
||
date_format: '%m/%d/%Y',
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow');
|
||
|
||
// select two records and edit them
|
||
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
|
||
await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
|
||
|
||
await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell'));
|
||
assert.containsOnce(list, 'input.o_datepicker_input', 'should have date picker input');
|
||
await testUtils.fields.editAndTrigger(list.$('.o_datepicker_input'), 'blabla', ['input', 'change']);
|
||
await testUtils.dom.click(list.$el);
|
||
|
||
assert.containsNone(document.body, '.modal');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow');
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget on a date field in form view', async function (assert) {
|
||
assert.expect(8);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, date: '2017-10-08' }, // today
|
||
];
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="date" widget="remaining_days"/></form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$('.o_field_widget').text(), 'Today');
|
||
assert.hasClass(form.$('.o_field_widget'), 'fw-bold text-warning');
|
||
|
||
// in edit mode, this widget should be editable.
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
assert.hasClass(form.$('.o_legacy_form_view'), 'o_form_editable');
|
||
assert.containsOnce(form, 'div.o_field_widget[name=date] .o_datepicker');
|
||
|
||
await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 1,
|
||
"datepicker should be opened");
|
||
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day[data-day="10/09/2017"]'));
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').text(), 'Tomorrow');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day[data-day="10/07/2017"]'));
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').text(), 'Yesterday');
|
||
assert.hasClass(form.$('.o_field_widget'), 'text-danger');
|
||
|
||
form.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget on a datetime field in form view', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, datetime: '2017-10-08 10:00:00' }, // today
|
||
];
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="datetime" widget="remaining_days"/></form>',
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$('.o_field_widget').text(), 'Today');
|
||
assert.hasClass(form.$('.o_field_widget'), 'text-warning');
|
||
|
||
// in edit mode, this widget should be editable.
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
assert.hasClass(form.$('.o_legacy_form_view'), 'o_form_editable');
|
||
assert.containsOnce(form, 'div.o_field_widget[name=datetime] .o_datepicker');
|
||
|
||
await testUtils.dom.openDatepicker(form.$('.o_datepicker'));
|
||
assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 1,
|
||
"datepicker should be opened");
|
||
|
||
await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day[data-day="10/09/2017"]'));
|
||
await testUtils.dom.click($('a[data-action="close"]'));
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').text(), 'Tomorrow');
|
||
|
||
form.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget on a datetime field in list view in UTC', async function (assert) {
|
||
assert.expect(16);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, datetime: '2017-10-08 20:00:00' }, // today
|
||
{ id: 2, datetime: '2017-10-09 08:00:00' }, // tomorrow
|
||
{ id: 3, datetime: '2017-10-07 18:00:00' }, // yesterday
|
||
{ id: 4, datetime: '2017-10-10 22:00:00' }, // + 2 days
|
||
{ id: 5, datetime: '2017-10-05 04:00:00' }, // - 3 days
|
||
{ id: 6, datetime: '2018-02-08 04:00:00' }, // + 4 months (diff >= 100 days)
|
||
{ id: 7, datetime: '2017-06-08 04:00:00' }, // - 4 months (diff >= 100 days)
|
||
{ id: 8, datetime: false },
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree><field name="datetime" widget="remaining_days"/></tree>',
|
||
session: {
|
||
getTZOffset: () => 0,
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(2)').text(), 'Yesterday');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(3)').text(), 'In 2 days');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(4)').text(), '3 days ago');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(5)').text(), '02/08/2018');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(6)').text(), '06/08/2017');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(7)').text(), '');
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0) .o_field_widget').attr('title'), '10/08/2017');
|
||
|
||
assert.hasClass(list.$('.o_data_cell:nth(0) div'), 'fw-bold text-warning');
|
||
assert.doesNotHaveClass(list.$('.o_data_cell:nth(1) div'), 'fw-bold text-warning text-danger');
|
||
assert.hasClass(list.$('.o_data_cell:nth(2) div'), 'fw-bold text-danger');
|
||
assert.doesNotHaveClass(list.$('.o_data_cell:nth(3) div'), 'fw-bold text-warning text-danger');
|
||
assert.hasClass(list.$('.o_data_cell:nth(4) div'), 'fw-bold text-danger');
|
||
assert.doesNotHaveClass(list.$('.o_data_cell:nth(5) div'), 'fw-bold text-warning text-danger');
|
||
assert.hasClass(list.$('.o_data_cell:nth(6) div'), 'fw-bold text-danger');
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget on a datetime field in list view in UTC+6', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11, UTC+6
|
||
this.data.partner.records = [
|
||
{ id: 1, datetime: '2017-10-08 20:00:00' }, // tomorrow
|
||
{ id: 2, datetime: '2017-10-09 08:00:00' }, // tomorrow
|
||
{ id: 3, datetime: '2017-10-07 18:30:00' }, // today
|
||
{ id: 4, datetime: '2017-10-07 12:00:00' }, // yesterday
|
||
{ id: 5, datetime: '2017-10-09 20:00:00' }, // + 2 days
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree><field name="datetime" widget="remaining_days"/></tree>',
|
||
session: {
|
||
getTZOffset: () => 360,
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Tomorrow');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(2)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(3)').text(), 'Yesterday');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(4)').text(), 'In 2 days');
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0) .o_field_widget').attr('title'), '10/09/2017');
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget on a date field in list view in UTC-6', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11
|
||
this.data.partner.records = [
|
||
{ id: 1, date: '2017-10-08' }, // today
|
||
{ id: 2, date: '2017-10-09' }, // tomorrow
|
||
{ id: 3, date: '2017-10-07' }, // yesterday
|
||
{ id: 4, date: '2017-10-10' }, // + 2 days
|
||
{ id: 5, date: '2017-10-05' }, // - 3 days
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree><field name="date" widget="remaining_days"/></tree>',
|
||
session: {
|
||
getTZOffset: () => -360,
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Tomorrow');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(2)').text(), 'Yesterday');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(3)').text(), 'In 2 days');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(4)').text(), '3 days ago');
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0) .o_field_widget').attr('title'), '10/08/2017');
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.test('remaining_days widget on a datetime field in list view in UTC-8', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
const unpatchDate = patchDate(2017, 9, 8, 15, 35, 11); // October 8 2017, 15:35:11, UTC-8
|
||
this.data.partner.records = [
|
||
{ id: 1, datetime: '2017-10-08 20:00:00' }, // today
|
||
{ id: 2, datetime: '2017-10-09 07:00:00' }, // today
|
||
{ id: 3, datetime: '2017-10-09 10:00:00' }, // tomorrow
|
||
{ id: 4, datetime: '2017-10-08 06:00:00' }, // yesterday
|
||
{ id: 5, datetime: '2017-10-07 02:00:00' }, // - 2 days
|
||
];
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<tree><field name="datetime" widget="remaining_days"/></tree>',
|
||
session: {
|
||
getTZOffset: () => -560,
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(list.$('.o_data_cell:nth(0)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(1)').text(), 'Today');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(2)').text(), 'Tomorrow');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(3)').text(), 'Yesterday');
|
||
assert.strictEqual(list.$('.o_data_cell:nth(4)').text(), '2 days ago');
|
||
|
||
list.destroy();
|
||
unpatchDate();
|
||
});
|
||
|
||
QUnit.module('FieldMonetary');
|
||
|
||
QUnit.test('monetary field in form view', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var form = await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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 in the lines of a form view', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'team',
|
||
data: this.data,
|
||
arch:'<form>' +
|
||
'<sheet>' +
|
||
'<field name="partner_ids">' +
|
||
'<tree editable="bottom">' +
|
||
'<field name="monetary"/>' +
|
||
'<field name="currency_id" invisible="1"/>' +
|
||
'</tree>' +
|
||
'</field>' +
|
||
'</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_list_table .o_list_number').first().text(), '$\u00a099.9',
|
||
'The value should be displayed properly.');
|
||
assert.hasAttrValue(form.$('.o_list_table .o_list_number').first(), 'title', '$\u00a099.9',
|
||
'The title value should be displayed properly.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
// Non-breaking space between the currency and the amount
|
||
assert.strictEqual(form.$('.o_list_table .o_list_number').first().text(), '$\u00a099.9',
|
||
'The value should be displayed properly.');
|
||
assert.hasAttrValue(form.$('.o_list_table .o_list_number').first(), 'title', '$\u00a099.9',
|
||
'The title value should be displayed properly.');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('monetary field rounding using formula in form view', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await 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'),
|
||
},
|
||
});
|
||
|
||
// Test computation and rounding
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editInput(form.$('.o_field_monetary input'), '=100/3');
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '$\u00a033.33',
|
||
'The new value should be calculated and rounded properly.');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('monetary field with currency symbol after', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var form = await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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', async 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 = await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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 without currency symbol', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners">' +
|
||
'<sheet>' +
|
||
'<field name="qux" widget="monetary" options="{\'no_symbol\': True}"/>' +
|
||
'<field name="currency_id" invisible="1"/>' +
|
||
'</sheet>' +
|
||
'</form>',
|
||
res_id: 5,
|
||
session: {
|
||
currencies: _.indexBy(this.data.currency.records, 'id'),
|
||
},
|
||
});
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
// Non-breaking space between the currency and the amount
|
||
assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), "9.10", "The currency symbol is not displayed");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('monetary field in editable list view', async function (assert) {
|
||
assert.expect(9);
|
||
|
||
var list = await 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($)');
|
||
await 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.');
|
||
|
||
await 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.');
|
||
|
||
await 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', async 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 = await 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(), "$ 9.10",
|
||
"readonly value should contain the currency");
|
||
assert.strictEqual(form.$('.o_field_monetary').first().next().html(), "$ 4.20",
|
||
"readonly value should contain the currency");
|
||
|
||
await 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");
|
||
|
||
await 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");
|
||
|
||
await testUtils.fields.many2one.clickOpenDropdown('currency_id');
|
||
await 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 €",
|
||
"readonly value should contain the updated currency");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('monetary field with monetary field given in options', async 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 = await 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 €",
|
||
"field monetary should be formatted with correct currency");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('should keep the focus when being edited in x2many lists', async 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 = await 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]');
|
||
await testUtils.dom.click($o2m.find('.o_field_x2many_list_row_add a'));
|
||
await 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");
|
||
|
||
await testUtils.dom.click(form.$el);
|
||
|
||
assert.strictEqual($o2m.find('.o_field_widget[name=qux]').html(), "$ 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]');
|
||
await testUtils.dom.click($m2m.find('.o_data_row td:first'));
|
||
await 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");
|
||
|
||
await testUtils.dom.click(form.$el);
|
||
|
||
assert.strictEqual($m2m.find('.o_field_widget[name=qux]').html(), "22.00 €",
|
||
"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 = await 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'),
|
||
},
|
||
});
|
||
|
||
await 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
|
||
await 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');
|
||
await 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
|
||
await testUtils.dom.click(list.$('.o_field_widget[name=int_field]'));
|
||
await 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 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', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('input[name=int_field]').val(), '0',
|
||
'The value should be rendered correctly in edit mode.');
|
||
|
||
await 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.');
|
||
|
||
await 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 rounding using formula in form view', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:'<form string="Partners"><field name="int_field"/></form>',
|
||
res_id: 2,
|
||
});
|
||
|
||
// Test computation and rounding
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editInput(form.$('input[name=int_field]'), '=100/3');
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '33',
|
||
'The new value should be calculated properly.');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('integer field in form view with virtual id', async 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 = await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var list = await 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();
|
||
await testUtils.dom.click($cell);
|
||
|
||
assert.containsOnce(list, 'input[name="int_field"]',
|
||
'The view should have 1 input for editable integer.');
|
||
|
||
await 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.');
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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],
|
||
},
|
||
});
|
||
|
||
await 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".');
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890');
|
||
await testUtils.form.clickSave(form);
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('.o_field_widget').val(), '1234567890',
|
||
'Integer value must be not formatted if input type is number.');
|
||
await 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', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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],
|
||
},
|
||
});
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'text',
|
||
'Integer field without option type must have a text type (default type).');
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890');
|
||
await testUtils.form.clickSave(form);
|
||
await 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.test('integer field without formatting', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
this.data.partner.records = [{
|
||
'id': 999,
|
||
'int_field': 8069,
|
||
}];
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="int_field" options="{\'format\': \'false\'}"/>' +
|
||
'</form>',
|
||
res_id: 999,
|
||
translateParameters: {
|
||
thousands_sep: ",",
|
||
grouping: [3, 0],
|
||
},
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
|
||
assert.strictEqual(form.$('.o_field_widget[name=int_field]').text(), '8069',
|
||
'Integer value must not be formatted');
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
assert.strictEqual(form.$('.o_field_widget').val(), '8069',
|
||
'Integer value must not be formatted');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('integer field is formatted by default', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
this.data.partner.records = [{
|
||
'id': 999,
|
||
'int_field': 8069,
|
||
}];
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form string="Partners">' +
|
||
'<field name="int_field" />' +
|
||
'</form>',
|
||
res_id: 999,
|
||
translateParameters: {
|
||
thousands_sep: ",",
|
||
grouping: [3, 0],
|
||
},
|
||
});
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
|
||
assert.strictEqual(form.$('.o_field_widget[name=int_field]').text(), '8,069',
|
||
'Integer value must be formatted by default');
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
assert.strictEqual(form.$('.o_field_widget').val(), '8,069',
|
||
'Integer value must be formatted by default');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldFloatTime');
|
||
|
||
QUnit.test('float_time field in form view', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var form = await 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.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('input[name=qux]').val(), '09:06',
|
||
'The value should be rendered correctly in the input.');
|
||
|
||
await 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.');
|
||
|
||
await 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.test('float_time field value formatted on blur', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:
|
||
`<form string="Partners">
|
||
<field name="qux" widget="float_time"/>
|
||
</form>`,
|
||
mockRPC: function (route, args) {
|
||
if (route === '/web/dataset/call_kw/partner/write') {
|
||
assert.strictEqual(args.args[1].qux, 9.5, 'the correct float value should be saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
res_id: 5,
|
||
});
|
||
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '09:06',
|
||
'The formatted time value should be displayed properly.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.fields.editAndTrigger(form.$('input[name=qux]'), '9.5', ['change']);
|
||
assert.strictEqual(form.$('input[name=qux]').val(), '09:30',
|
||
'The new value should be displayed properly in the input.');
|
||
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('.o_field_widget').first().text(), '09:30',
|
||
'The new value should be saved and displayed properly.');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('float_time field with invalid value', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:
|
||
`<form>
|
||
<field name="qux" widget="float_time"/>
|
||
</form>`,
|
||
interceptsPropagate: {
|
||
call_service: function (ev) {
|
||
if (ev.data.service === 'notification') {
|
||
assert.strictEqual(ev.data.method, 'notify');
|
||
assert.strictEqual(ev.data.args[0].title, 'Invalid fields:');
|
||
assert.strictEqual(ev.data.args[0].message.toString(), '<ul><li>Qux</li></ul>');
|
||
}
|
||
}
|
||
},
|
||
});
|
||
|
||
await testUtils.fields.editAndTrigger(form.$('input[name=qux]'), 'blabla', ['change']);
|
||
await testUtils.form.clickSave(form);
|
||
assert.hasClass(form.$('input[name=qux]'), 'o_field_invalid');
|
||
|
||
await testUtils.fields.editAndTrigger(form.$('input[name=qux]'), '6.5', ['change']);
|
||
assert.doesNotHaveClass(form.$('input[name=qux]'), 'o_field_invalid',
|
||
"date field should not be displayed as invalid now");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
|
||
QUnit.module('FieldFloatFactor');
|
||
|
||
QUnit.test('float_factor field in form view', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
assert.strictEqual(form.$('input[name=qux]').val(), '4.55',
|
||
'The value should be rendered correctly in the input.');
|
||
|
||
await testUtils.fields.editInput(form.$('input[name=qux]'), '2.3');
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var form = await 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.');
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
|
||
assert.strictEqual(form.$('button.o_field_float_toggle').text(), '0.056',
|
||
'The value should be rendered correctly on the button.');
|
||
|
||
await 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.');
|
||
|
||
await 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.test('float_toggle widget in kanban view(readonly) with option force_button', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var kanban = await createView({
|
||
View: KanbanView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<kanban>' +
|
||
'<templates><t t-name="kanban-box">' +
|
||
'<div>' +
|
||
'<field name="qux" widget="float_toggle" options="{\'force_button\': true}"/>' +
|
||
'</div>' +
|
||
'</t>' +
|
||
'</templates></kanban>',
|
||
domain: [['id', 'in', [1]]],
|
||
});
|
||
assert.containsOnce(kanban, 'button.o_field_float_toggle', "should have rendered toggle button");
|
||
const value = kanban.$('button.o_field_float_toggle').text();
|
||
await testUtils.dom.click(kanban.$('button.o_field_float_toggle'));
|
||
assert.notEqual(kanban.$('button.o_field_float_toggle').text(), value, "qux field value should be changed");
|
||
kanban.destroy();
|
||
});
|
||
|
||
|
||
QUnit.module('PhoneWidget');
|
||
|
||
QUnit.test('phone field in form view on normal screens', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var form = await 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.$('div.o_field_widget.o_form_uri.o_field_phone > a');
|
||
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
|
||
await 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
|
||
await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new');
|
||
|
||
// save
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$('div.o_field_widget.o_form_uri.o_field_phone > a').text(), 'new',
|
||
"new value should be displayed properly");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('phone field in editable list view on normal screens', async function (assert) {
|
||
assert.expect(8);
|
||
|
||
var list = await 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.containsN(list, 'tbody td:not(.o_list_record_selector)', 5);
|
||
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) a').first().text(), 'yop',
|
||
"value should be displayed properly with a link to send SMS");
|
||
|
||
assert.containsN(list, 'div.o_field_widget.o_form_uri.o_field_phone > a', 5,
|
||
"should have the correct classnames");
|
||
|
||
// Edit a line and check the result
|
||
var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
|
||
await 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');
|
||
await testUtils.fields.editInput($cell.find('input'), 'new');
|
||
|
||
// save
|
||
await 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) a').first().text(), 'new',
|
||
"value should be properly updated");
|
||
assert.containsN(list, 'div.o_field_widget.o_form_uri.o_field_phone > a', 5,
|
||
"should still have links with correct classes");
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('use TAB to navigate to a phone field', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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 tooltip', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const form = await 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,
|
||
});
|
||
|
||
// check title attribute (for basic html tooltip on all the stars)
|
||
const $stars = form.$('.o_field_widget.o_priority').find('a.o_priority_star');
|
||
assert.strictEqual($stars[0].title, 'Selection: Blocked',
|
||
"Should set field label and correct selection label as title attribute (tooltip)");
|
||
assert.strictEqual($stars[1].title, 'Selection: Done',
|
||
"Should set field label and correct selection label as title attribute (tooltip)");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('priority widget in form view', async function (assert) {
|
||
assert.expect(22);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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
|
||
await 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
|
||
await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(25);
|
||
|
||
var list = await 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();
|
||
await 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
|
||
await 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
|
||
await 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();
|
||
await 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
|
||
await 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
|
||
await 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.test('priority widget with readonly attribute', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form><field name="selection" widget="priority" readonly="1"/></form>',
|
||
res_id: 2,
|
||
mockRPC(route, args) {
|
||
if (args.method === "write") {
|
||
throw new Error("should not save");
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(form.$('span.o_priority_star.fa.fa-star-o').length, 2,
|
||
"stars of priority widget should rendered with span tag if readonly");
|
||
|
||
await testUtils.dom.click(form.$('.o_priority_star.fa-star-o').last());
|
||
|
||
assert.strictEqual(form.$('.o_priority_star.fa.fa-star-o').length, 2,
|
||
"should still have two stars");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('priority widget edited by the smart action "Set priority..."', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||
const serviceRegistry = registry.category("services");
|
||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||
|
||
const views = {
|
||
'partner,false,form': '<form>' +
|
||
'<field name="selection" widget="priority"/>' +
|
||
'</form>',
|
||
'partner,false,search': '<search></search>',
|
||
};
|
||
const serverData = { models: this.data, views}
|
||
const webClient = await createWebClient({ serverData });
|
||
await doAction(webClient, {
|
||
res_id: 1,
|
||
type: 'ir.actions.act_window',
|
||
target: 'current',
|
||
res_model: 'partner',
|
||
'view_mode': 'form',
|
||
'views': [[false, 'form']],
|
||
});
|
||
assert.containsOnce(target, ".fa-star")
|
||
|
||
triggerHotkey("control+k")
|
||
await nextTick();
|
||
const idx = [...target.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Set priority...ALT + R")
|
||
assert.ok(idx >= 0);
|
||
|
||
await click([...target.querySelectorAll(".o_command")][idx])
|
||
await nextTick();
|
||
assert.deepEqual([...target.querySelectorAll(".o_command")].map(el => el.textContent), [
|
||
"Normal",
|
||
"Blocked",
|
||
"Done"
|
||
])
|
||
await click(target, "#o_command_2")
|
||
await legacyExtraNextTick();
|
||
assert.containsN(target, ".fa-star", 2)
|
||
});
|
||
|
||
QUnit.module('StateSelection Widget');
|
||
|
||
QUnit.test('state_selection widget in form view', async function (assert) {
|
||
assert.expect(21);
|
||
|
||
var form = await 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
|
||
await 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"
|
||
await 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
|
||
await 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
|
||
await 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"
|
||
await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await 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'));
|
||
|
||
await testUtils.dom.click(form.$('.o_selection > a'));
|
||
assert.isNotVisible(form.$('.dropdown-menu.state'));
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('state_selection widget for list view with hide_label option', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
Object.assign(this.data.partner.fields, {
|
||
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";
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:`
|
||
<tree>
|
||
<field name="graph_type" widget="state_selection" options="{'hide_label': True}"/>
|
||
<field name="selection" widget="state_selection"/>
|
||
</tree>`,
|
||
});
|
||
|
||
assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 10,
|
||
"should have ten status selection widgets");
|
||
assert.containsN(list, '.o_state_selection_cell .o_selection[name=selection] > span.align-middle', 5,
|
||
"should have five label on selection widgets");
|
||
assert.containsOnce(list, '.o_state_selection_cell .o_selection[name=selection] > span.align-middle:contains(Done)',
|
||
"should have one Done status label");
|
||
assert.containsN(list, '.o_state_selection_cell .o_selection[name=selection] > span.align-middle:contains(Normal)', 3,
|
||
"should have three Normal status label");
|
||
assert.containsN(list, '.o_state_selection_cell .o_selection[name=graph_type] > a span.o_status', 5,
|
||
"should have five status selection widgets");
|
||
assert.containsNone(list, '.o_state_selection_cell .o_selection[name=graph_type] > span.align-middle',
|
||
"should not have status label in selection widgets");
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('state_selection widget in editable list view', async function (assert) {
|
||
assert.expect(33);
|
||
|
||
var list = await 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();
|
||
await 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"
|
||
await 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");
|
||
assert.containsNone(list, 'tr.o_selected_row', 'should not be in edit mode');
|
||
|
||
// switch to edit mode and check the result
|
||
$cell = list.$('tbody td.o_state_selection_cell').first();
|
||
await 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
|
||
await 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();
|
||
await 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
|
||
await 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"
|
||
await 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
|
||
await 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.test('state_selection edited by the smart action "Set kanban state..."', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||
const serviceRegistry = registry.category("services");
|
||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||
|
||
const views = {
|
||
'partner,false,form': '<form>' +
|
||
'<field name="selection" widget="state_selection"/>' +
|
||
'</form>',
|
||
'partner,false,search': '<search></search>',
|
||
};
|
||
const serverData = { models: this.data, views}
|
||
const webClient = await createWebClient({ serverData });
|
||
await doAction(webClient, {
|
||
res_id: 1,
|
||
type: 'ir.actions.act_window',
|
||
target: 'current',
|
||
res_model: 'partner',
|
||
'view_mode': 'form',
|
||
'views': [[false, 'form']],
|
||
});
|
||
assert.containsOnce(target, ".o_status_red")
|
||
|
||
triggerHotkey("control+k")
|
||
await nextTick();
|
||
const idx = [...target.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Set kanban state...ALT + SHIFT + R")
|
||
assert.ok(idx >= 0);
|
||
|
||
await click([...target.querySelectorAll(".o_command")][idx])
|
||
await nextTick();
|
||
assert.deepEqual([...target.querySelectorAll(".o_command")].map(el => el.textContent), [
|
||
"Normal",
|
||
"Blocked",
|
||
"Done"
|
||
])
|
||
await click(target, "#o_command_2")
|
||
await legacyExtraNextTick();
|
||
assert.containsOnce(target, ".o_status_green")
|
||
});
|
||
|
||
|
||
QUnit.module('FavoriteWidget');
|
||
|
||
QUnit.test('favorite widget in kanban view', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var kanban = await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(10);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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
|
||
await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var list = await 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
|
||
await 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
|
||
await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(12);
|
||
|
||
var form = await 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.text-bg-warning',
|
||
"should have a warning status label since selection is the second, blocked state");
|
||
assert.containsNone(form, '.o_field_widget.badge.text-bg-secondary',
|
||
"should not have a default status since selection is the second, blocked state");
|
||
assert.containsNone(form, '.o_field_widget.badge.text-bg-success',
|
||
"should not have a success status since selection is the second, blocked state");
|
||
assert.strictEqual(form.$('.o_field_widget.badge.text-bg-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
|
||
await testUtils.form.clickEdit(form);
|
||
assert.containsOnce(form, '.o_field_widget.badge.text-bg-warning',
|
||
"should have a warning status label since selection is the second, blocked state");
|
||
assert.containsNone(form, '.o_field_widget.badge.text-bg-secondary',
|
||
"should not have a default status since selection is the second, blocked state");
|
||
assert.containsNone(form, '.o_field_widget.badge.text-bg-success',
|
||
"should not have a success status since selection is the second, blocked state");
|
||
assert.strictEqual(form.$('.o_field_widget.badge.text-bg-warning').text(), 'Blocked',
|
||
"the label should say 'Blocked' since this is the label value for that state");
|
||
|
||
// save
|
||
await testUtils.form.clickSave(form);
|
||
assert.containsOnce(form, '.o_field_widget.badge.text-bg-warning',
|
||
"should have a warning status label since selection is the second, blocked state");
|
||
assert.containsNone(form, '.o_field_widget.badge.text-bg-secondary',
|
||
"should not have a default status since selection is the second, blocked state");
|
||
assert.containsNone(form, '.o_field_widget.badge.text-bg-success',
|
||
"should not have a success status since selection is the second, blocked state");
|
||
assert.strictEqual(form.$('.o_field_widget.badge.text-bg-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', async function (assert) {
|
||
assert.expect(21);
|
||
|
||
var list = await 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.text-bg-warning',
|
||
"should have one warning status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-warning').text(), 'Blocked',
|
||
"the warning label should read 'Blocked'");
|
||
assert.containsOnce(list, '.o_field_widget.badge.text-bg-secondary',
|
||
"should have one default status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-secondary').text(), 'Normal',
|
||
"the default label should read 'Normal'");
|
||
assert.containsOnce(list, '.o_field_widget.badge.text-bg-success',
|
||
"should have one success status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-success').text(), 'Done',
|
||
"the success label should read 'Done'");
|
||
|
||
// switch to edit mode and check the result
|
||
await testUtils.dom.clickFirst(list.$('tbody td:not(.o_list_record_selector)'));
|
||
assert.strictEqual(list.$('.o_field_widget.badge:not(:empty)').length, 3,
|
||
"should have three visible status labels");
|
||
assert.containsOnce(list, '.o_field_widget.badge.text-bg-warning',
|
||
"should have one warning status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-warning').text(), 'Blocked',
|
||
"the warning label should read 'Blocked'");
|
||
assert.containsOnce(list, '.o_field_widget.badge.text-bg-secondary',
|
||
"should have one default status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-secondary').text(), 'Normal',
|
||
"the default label should read 'Normal'");
|
||
assert.containsOnce(list, '.o_field_widget.badge.text-bg-success',
|
||
"should have one success status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-success').text(), 'Done',
|
||
"the success label should read 'Done'");
|
||
|
||
// save and check the result
|
||
await 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.text-bg-warning',
|
||
"should have one warning status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-warning').text(), 'Blocked',
|
||
"the warning label should read 'Blocked'");
|
||
assert.containsOnce(list, '.o_field_widget.badge.text-bg-secondary',
|
||
"should have one default status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-secondary').text(), 'Normal',
|
||
"the default label should read 'Normal'");
|
||
assert.containsOnce(list, '.o_field_widget.badge.text-bg-success',
|
||
"should have one success status label");
|
||
assert.strictEqual(list.$('.o_field_widget.badge.text-bg-success').text(), 'Done',
|
||
"the success label should read 'Done'");
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
|
||
QUnit.module('StatInfo');
|
||
|
||
QUnit.test('statinfo widget formats decimal precision', async 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 = await 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', async function (assert) {
|
||
assert.expect(9);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(9);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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', async function (assert) {
|
||
assert.expect(9);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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%', async function (assert) {
|
||
assert.expect(12);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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%', async function (assert) {
|
||
assert.expect(12);
|
||
|
||
var form = await 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
|
||
await 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
|
||
await 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', async function(assert) {
|
||
// assert.expect(10);
|
||
//
|
||
// var list = await 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', async 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 = await 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', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
this.data.partner.records[0].foo = "[]";
|
||
|
||
var form = await 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,
|
||
});
|
||
await 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
|
||
await 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
|
||
await testUtils.dom.triggerEvents($fieldSelector, 'focus');
|
||
var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
|
||
assert.ok($fieldSelectorPopover.is(":visible"),
|
||
"field selector popover should be visible");
|
||
|
||
assert.containsOnce($fieldSelectorPopover, '.o_field_selector_search input',
|
||
"field selector popover should contain a search input");
|
||
|
||
// 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
|
||
await testUtils.dom.click($colorIndex);
|
||
await testUtils.fields.editAndTrigger($('.o_domain_leaf_value_input'), 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
|
||
await 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', async 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 = await 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,
|
||
});
|
||
await 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
|
||
await testUtils.dom.triggerEvents($fieldSelector, 'focus');
|
||
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"
|
||
await testUtils.dom.click(form.$("input.o_field_widget"));
|
||
await 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)', async 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 = await 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
|
||
await 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 []', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
this.data.partner.records[0].foo = false;
|
||
this.data.partner.fields.bar.type = "char";
|
||
this.data.partner.records[0].bar = "product";
|
||
|
||
var form = await 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");
|
||
|
||
await 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', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.records[0].foo = "[]";
|
||
|
||
var form = await 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
|
||
await testUtils.dom.click(form.$(".o_domain_show_selection_button"));
|
||
assert.strictEqual($('.modal .o_legacy_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
|
||
await testUtils.dom.click($('.modal .o_legacy_list_view .o_data_row:first .o_data_cell'));
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('field context is propagated when opening selection', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
this.data.partner.records[0].foo = "[]";
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="foo" widget="domain" options="{'model': 'partner_type'}" context="{'tree_view_ref': 3}"/>
|
||
</form>
|
||
`,
|
||
archs: {
|
||
'partner_type,false,list': '<tree><field name="display_name"/></tree>',
|
||
'partner_type,3,list': '<tree><field name="id"/></tree>',
|
||
'partner_type,false,search': '<search><field name="name" string="Name"/></search>',
|
||
},
|
||
res_id: 1,
|
||
});
|
||
|
||
await testUtils.dom.click(form.$(".o_domain_show_selection_button"));
|
||
|
||
assert.strictEqual($('.modal .o_data_row').text(), '1214',
|
||
"should have picked the correct list view");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('domain field: manually edit domain with textarea', async function (assert) {
|
||
assert.expect(9);
|
||
|
||
const originalDebug = odoo.debug;
|
||
odoo.debug = true;
|
||
|
||
this.data.partner.records[0].foo = false;
|
||
this.data.partner.fields.bar.type = "char";
|
||
this.data.partner.records[0].bar = "product";
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="bar"/>
|
||
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
|
||
</form>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === 'search_count') {
|
||
assert.step(JSON.stringify(args.args[0]));
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "2 record(s)");
|
||
assert.verifySteps(["[]"]);
|
||
|
||
await testUtils.fields.editAndTrigger(form.$('.o_domain_debug_input'), "[['id', '<', 40]]", ["change"]);
|
||
// the count should not be re-computed when editing with the textarea
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "2 record(s)");
|
||
assert.verifySteps([]);
|
||
|
||
await testUtils.form.clickSave(form);
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "1 record(s)");
|
||
assert.verifySteps([
|
||
"[[\"id\",\"<\",40]]", // to validate the domain, before saving
|
||
"[[\"id\",\"<\",40]]", // to render in readonly once it has been saved
|
||
]);
|
||
|
||
form.destroy();
|
||
odoo.debug = originalDebug;
|
||
});
|
||
|
||
QUnit.test('domain field: manually set an invalid domain with textarea', async function (assert) {
|
||
assert.expect(9);
|
||
|
||
const originalDebug = odoo.debug;
|
||
odoo.debug = true;
|
||
|
||
this.data.partner.records[0].foo = false;
|
||
this.data.partner.fields.bar.type = "char";
|
||
this.data.partner.records[0].bar = "product";
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="bar"/>
|
||
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
|
||
</form>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === 'search_count') {
|
||
assert.step(JSON.stringify(args.args[0]));
|
||
}
|
||
if (args.method === "write") {
|
||
throw new Error("should not save");
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "2 record(s)");
|
||
assert.verifySteps(["[]"]);
|
||
|
||
await testUtils.fields.editAndTrigger(form.$('.o_domain_debug_input'), "[['abc']]", ["change"]);
|
||
// the count should not be re-computed when editing with the textarea
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "2 record(s)");
|
||
assert.verifySteps([]);
|
||
|
||
await testUtils.form.clickSave(form);
|
||
assert.hasClass(form.$(".o_field_domain"), "o_field_invalid", "the field is marked as invalid");
|
||
assert.hasClass(form.$(".o_legacy_form_view"), "o_form_editable", "the view is still in edit mode");
|
||
assert.verifySteps(["[[\"abc\"]]"]);
|
||
|
||
form.destroy();
|
||
odoo.debug = originalDebug;
|
||
});
|
||
|
||
QUnit.test('domain field: reload count by clicking on the refresh button', async function (assert) {
|
||
assert.expect(7);
|
||
|
||
const originalDebug = odoo.debug;
|
||
odoo.debug = true;
|
||
|
||
this.data.partner.records[0].foo = "[]";
|
||
this.data.partner.fields.bar.type = "char";
|
||
this.data.partner.records[0].bar = "product";
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="bar"/>
|
||
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
|
||
</form>`,
|
||
async mockRPC(route, args) {
|
||
if (args.method === 'search_count') {
|
||
assert.step(JSON.stringify(args.args[0]));
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "2 record(s)");
|
||
|
||
await testUtils.fields.editAndTrigger(form.$('.o_domain_debug_input'), "[['id', '<', 40]]", ["change"]);
|
||
// the count should not be re-computed when editing with the textarea
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "2 record(s)");
|
||
assert.verifySteps(["[]"]);
|
||
|
||
// click on the refresh button
|
||
await testUtils.dom.click(form.$(".o_refresh_count"));
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "1 record(s)");
|
||
assert.verifySteps(["[[\"id\",\"<\",40]]"]);
|
||
|
||
form.destroy();
|
||
odoo.debug = originalDebug;
|
||
});
|
||
|
||
QUnit.test('domain field: does not wait for the count to render', async function (assert) {
|
||
assert.expect(5);
|
||
|
||
this.data.partner.records[0].foo = "[]";
|
||
this.data.partner.fields.bar.type = "char";
|
||
this.data.partner.records[0].bar = "product";
|
||
|
||
const def = testUtils.makeTestPromise();
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="bar"/>
|
||
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
|
||
</form>`,
|
||
async mockRPC(route, args) {
|
||
const result = this._super.apply(this, arguments);
|
||
if (args.method === 'search_count') {
|
||
await def;
|
||
}
|
||
return result;
|
||
},
|
||
res_id: 1,
|
||
});
|
||
|
||
assert.containsOnce(form, ".o_field_domain_panel .fa-circle-o-notch.fa-spin");
|
||
assert.containsNone(form, ".o_field_domain_panel .o_domain_show_selection_button");
|
||
|
||
def.resolve();
|
||
await testUtils.nextTick();
|
||
|
||
assert.containsNone(form, ".o_field_domain_panel .fa-circle-o-notch .fa-spin");
|
||
assert.containsOnce(form, ".o_field_domain_panel .o_domain_show_selection_button");
|
||
assert.strictEqual(form.$(".o_domain_show_selection_button").text().trim(), "2 record(s)");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('domain field: edit domain with dynamic content', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const originalDebug = odoo.debug;
|
||
odoo.debug = true;
|
||
let rawDomain = `
|
||
[
|
||
["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -365), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")]
|
||
]
|
||
`;
|
||
this.data.partner.records[0].foo = rawDomain;
|
||
this.data.partner.fields.bar.type = "char";
|
||
this.data.partner.records[0].bar = "partner";
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form>
|
||
<field name="bar"/>
|
||
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
|
||
</form>`,
|
||
async mockRPC(route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].foo, rawDomain);
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
},
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(form.$(".o_domain_debug_input").val(), rawDomain);
|
||
|
||
rawDomain = `
|
||
[
|
||
["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -1), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")]
|
||
]
|
||
`;
|
||
await testUtils.fields.editAndTrigger(form.$('.o_domain_debug_input'), rawDomain, ["change"]);
|
||
await testUtils.form.clickSave(form);
|
||
|
||
form.destroy();
|
||
odoo.debug = originalDebug;
|
||
});
|
||
|
||
QUnit.module('FieldProgressBar');
|
||
|
||
QUnit.test('Field ProgressBar: max_value should update', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
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 = await 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',
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.deepEqual(
|
||
args.args[1],
|
||
{int_field: 999, qux: 5, display_name: 'new name'},
|
||
'New value of progress bar saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '10 / 2',
|
||
'The initial value of the progress bar should be correct');
|
||
|
||
// trigger the onchange
|
||
await testUtils.fields.editInput(form.$('.o_input[name=display_name]'), 'new name');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '999 / 5',
|
||
'The value of the progress bar should be correct after the update');
|
||
|
||
await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: value should not update in readonly mode when sliding the bar', async function (assert) {
|
||
assert.expect(4);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
mockRPC: function (route, args) {
|
||
assert.step(route);
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
var $view = $('#qunit-fixture').contents();
|
||
$view.prependTo('body'); // => select with click position
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'Initial value should be correct')
|
||
|
||
var $progressBarEl = form.$('.o_progress');
|
||
var top = $progressBarEl.offset().top + 5;
|
||
var left = $progressBarEl.offset().left + 5;
|
||
try {
|
||
testUtils.dom.triggerPositionalMouseEvent(left, top, "click");
|
||
} catch (_e) {
|
||
form.destroy();
|
||
$view.remove();
|
||
throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.');
|
||
}
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'New value should be different than initial after click');
|
||
|
||
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
|
||
|
||
form.destroy();
|
||
$view.remove();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: value should not update in edit mode when sliding the bar', async function (assert) {
|
||
assert.expect(6);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
mockRPC: function (route, args) {
|
||
assert.step(route);
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
var $view = $('#qunit-fixture').contents();
|
||
$view.prependTo('body'); // => select with click position
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_editable'), 'Form in edit mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'Initial value should be correct')
|
||
|
||
var $progressBarEl = form.$('.o_progress');
|
||
var top = $progressBarEl.offset().top + 5;
|
||
var left = $progressBarEl.offset().left + 5;
|
||
try {
|
||
testUtils.dom.triggerPositionalMouseEvent(left, top, "click");
|
||
} catch (_e) {
|
||
form.destroy();
|
||
$view.remove();
|
||
throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.');
|
||
}
|
||
assert.strictEqual(form.$('.o_progressbar_value.o_input').val(), "99",
|
||
'Value of input is not changed');
|
||
await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'New value should be different than initial after click');
|
||
|
||
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
|
||
|
||
form.destroy();
|
||
$view.remove();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: value should update in edit mode when typing in input', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].int_field, 69,
|
||
'New value of progress bar saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_editable'), 'Form in edit mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
var $valInput = form.$('.o_progressbar_value.o_input');
|
||
assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
|
||
|
||
await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
|
||
|
||
await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '69%',
|
||
'New value should be different than initial after click');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: value should update in edit mode when typing in input with field max value', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="qux" invisible="1" />' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\'}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].int_field, 69,
|
||
'New value of progress bar saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_editable'), 'Form in edit mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
var $valInput = form.$('.o_progressbar_value.o_input');
|
||
assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
|
||
|
||
await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
|
||
|
||
await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '69 / 0',
|
||
'New value should be different than initial after click');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: max value should update in edit mode when typing in input with field max value', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="qux" invisible="1" />' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\', \'edit_max_value\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].qux, 69,
|
||
'New value of progress bar saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_editable'), 'Form in edit mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
var $valInput = form.$('.o_progressbar_value.o_input');
|
||
assert.strictEqual($valInput.val(), "0.44444", 'Initial value in input is correct');
|
||
|
||
await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
|
||
|
||
await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 69',
|
||
'New value should be different than initial after click');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: Standard readonly mode is readonly', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="qux" invisible="1" />' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\', \'edit_max_value\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
mockRPC: function (route, args) {
|
||
assert.step(route);
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
assert.containsNone(form, '.o_progressbar_value.o_input', 'no input in readonly mode');
|
||
|
||
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: max value should update in readonly mode with right parameter when typing in input with field max value', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="qux" invisible="1" />' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\', \'edit_max_value\': true, \'editable_readonly\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].qux, 69,
|
||
'New value of progress bar saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
var $valInput = form.$('.o_progressbar_value.o_input');
|
||
assert.strictEqual($valInput.val(), "0.44444", 'Initial value in input is correct');
|
||
|
||
await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 69',
|
||
'New value should be different than initial after changing it');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: value should update in readonly mode with right parameter when typing in input with field value', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true, \'editable_readonly\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].int_field, 69,
|
||
'New value of progress bar saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
var $valInput = form.$('.o_progressbar_value.o_input');
|
||
assert.strictEqual($valInput.val(), "99", 'Initial value in input is correct');
|
||
|
||
await testUtils.fields.editAndTrigger($valInput, '69.6', ['input', 'blur']);
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '69%',
|
||
'New value should be different than initial after changing it');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: write float instead of int works, in locale', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
translateParameters: {
|
||
thousands_sep: "#",
|
||
decimal_point: ":",
|
||
},
|
||
mockRPC: function (route, args) {
|
||
if (args.method === 'write') {
|
||
assert.strictEqual(args.args[1].int_field, 1037,
|
||
'New value of progress bar saved');
|
||
}
|
||
return this._super.apply(this, arguments);
|
||
}
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_editable'), 'Form in edit mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
var $valInput = form.$('.o_progressbar_value.o_input');
|
||
assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
|
||
|
||
await testUtils.fields.editAndTrigger($valInput, '1#037:9', ['input', 'blur']);
|
||
|
||
await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '1k%',
|
||
'New value should be different than initial after click');
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field ProgressBar: write gibbrish instead of int throws warning', async function (assert) {
|
||
assert.expect(5);
|
||
this.data.partner.records[0].int_field = 99;
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: '<form>' +
|
||
'<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
interceptsPropagate: {
|
||
call_service: function (ev) {
|
||
if (ev.data.service === 'notification') {
|
||
assert.strictEqual(ev.data.method, 'notify');
|
||
assert.strictEqual(
|
||
ev.data.args[0].message,
|
||
"Please enter a numerical value"
|
||
);
|
||
}
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.ok(form.$('.o_legacy_form_view').hasClass('o_form_editable'), 'Form in edit mode');
|
||
|
||
assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
|
||
'Initial value should be correct');
|
||
|
||
await testUtilsDom.click(form.$('.o_progress'));
|
||
|
||
var $valInput = form.$('.o_progressbar_value.o_input');
|
||
assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
|
||
|
||
await testUtils.fields.editAndTrigger($valInput, 'trente sept virgule neuf', ['input']);
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field Color: default widget state', async function (assert) {
|
||
assert.expect(4);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:
|
||
'<form>' +
|
||
'<field name="hex_color" widget="color" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
await testUtils.dom.click(form.$('.o_field_color'));
|
||
assert.containsOnce($, '.modal');
|
||
assert.containsNone($('.modal'), '.o_opacity_slider',
|
||
"Opacity slider should not be present");
|
||
assert.containsNone($('.modal'), '.o_opacity_input',
|
||
"Opacity input should not be present");
|
||
|
||
await testUtils.dom.click($('.modal .btn:contains("Discard")'));
|
||
|
||
assert.strictEqual(document.activeElement, form.$('.o_field_color')[0],
|
||
"Focus should go back to the color field");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field Color: behaviour in different views', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
this.data.partner.records[0].p = [4, 2];
|
||
this.data.partner.records[1].hex_color = '#ff0080';
|
||
|
||
const form = await createView({
|
||
arch: '<form>' +
|
||
'<field name="hex_color" widget="color"/>' +
|
||
'<field name="p">' +
|
||
'<tree editable="top">' +
|
||
'<field name="display_name"/>' +
|
||
'<field name="hex_color" widget="color"/>' +
|
||
'</tree>' +
|
||
'</field>' +
|
||
'</form>',
|
||
data: this.data,
|
||
model: 'partner',
|
||
res_id: 1,
|
||
View: FormView,
|
||
});
|
||
|
||
await testUtils.dom.click(form.$('.o_field_color:first()'));
|
||
assert.containsNone($(document.body), '.modal',
|
||
"Color field in readonly shouldn't be editable");
|
||
|
||
const rowInitialHeight = form.$('.o_data_row:first()').height();
|
||
|
||
await testUtils.form.clickEdit(form);
|
||
await testUtils.dom.click(form.$('.o_data_row:first() .o_data_cell:first()'));
|
||
|
||
assert.strictEqual(rowInitialHeight, form.$('.o_data_row:first()').height(),
|
||
"Color field shouldn't change the color height when edited");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.test('Field Color: pick and reset colors', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
var form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch:
|
||
'<form>' +
|
||
'<field name="hex_color" widget="color" />' +
|
||
'</form>',
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
assert.strictEqual($('.o_field_color').css('backgroundColor'), 'rgb(255, 0, 0)',
|
||
"Background of the color field should be initially red");
|
||
|
||
await testUtils.dom.click(form.$('.o_field_color'));
|
||
await testUtils.fields.editAndTrigger($('.modal .o_hex_input'), '#00ff00', ['change']);
|
||
await testUtils.dom.click($('.modal .btn:contains("Choose")'));
|
||
|
||
assert.strictEqual($('.o_field_color').css('backgroundColor'), 'rgb(0, 255, 0)',
|
||
"Background of the color field should be updated to green");
|
||
|
||
form.destroy();
|
||
});
|
||
|
||
QUnit.module('FieldColorPicker');
|
||
|
||
QUnit.test('FieldColorPicker: can navigate away with TAB', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
const form = await createView({
|
||
View: FormView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<form string="Partners">
|
||
<field name="int_field" widget="color_picker"/>
|
||
<field name="foo" />
|
||
</form>`,
|
||
res_id: 1,
|
||
viewOptions: {
|
||
mode: 'edit',
|
||
},
|
||
});
|
||
|
||
form.$el.find('a.oe_kanban_color_1')[0].focus();
|
||
|
||
form.$el.find('a.oe_kanban_color_1').trigger($.Event('keydown', {
|
||
which: $.ui.keyCode.TAB,
|
||
keyCode: $.ui.keyCode.TAB,
|
||
}));
|
||
assert.strictEqual(document.activeElement, form.$el.find('input[name="foo"]')[0],
|
||
"foo field should be focused");
|
||
form.destroy();
|
||
});
|
||
|
||
|
||
QUnit.module('FieldBadge');
|
||
|
||
QUnit.test('FieldBadge component on a char field in list view', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<list><field name="display_name" widget="badge"/></list>`,
|
||
});
|
||
|
||
assert.containsOnce(list, '.o_field_badge[name="display_name"]:contains(first record)');
|
||
assert.containsOnce(list, '.o_field_badge[name="display_name"]:contains(second record)');
|
||
assert.containsOnce(list, '.o_field_badge[name="display_name"]:contains(aaa)');
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('FieldBadge component on a selection field in list view', async function (assert) {
|
||
assert.expect(3);
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<list><field name="selection" widget="badge"/></list>`,
|
||
});
|
||
|
||
assert.containsOnce(list, '.o_field_badge[name="selection"]:contains(Blocked)');
|
||
assert.containsOnce(list, '.o_field_badge[name="selection"]:contains(Normal)');
|
||
assert.containsOnce(list, '.o_field_badge[name="selection"]:contains(Done)');
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('FieldBadge component on a many2one field in list view', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `<list><field name="trululu" widget="badge"/></list>`,
|
||
});
|
||
|
||
assert.containsOnce(list, '.o_field_badge[name="trululu"]:contains(first record)');
|
||
assert.containsOnce(list, '.o_field_badge[name="trululu"]:contains(aaa)');
|
||
|
||
list.destroy();
|
||
});
|
||
|
||
QUnit.test('FieldBadge component with decoration-xxx attributes', async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const list = await createView({
|
||
View: ListView,
|
||
model: 'partner',
|
||
data: this.data,
|
||
arch: `
|
||
<list>
|
||
<field name="selection"/>
|
||
<field name="foo" widget="badge" decoration-danger="selection == 'done'" decoration-warning="selection == 'blocked'"/>
|
||
</list>`,
|
||
});
|
||
|
||
assert.containsN(list, '.o_field_badge[name="foo"]', 5);
|
||
assert.containsOnce(list, '.o_field_badge[name="foo"].text-bg-danger.bg-opacity-50');
|
||
assert.containsOnce(list, '.o_field_badge[name="foo"].text-bg-warning.bg-opacity-50');
|
||
|
||
await list.reload();
|
||
|
||
assert.containsN(list, '.o_field_badge[name="foo"]', 5);
|
||
assert.containsOnce(list, '.o_field_badge[name="foo"].text-bg-danger.bg-opacity-50');
|
||
assert.containsOnce(list, '.o_field_badge[name="foo"].text-bg-warning.bg-opacity-50');
|
||
|
||
list.destroy();
|
||
});
|
||
});
|
||
});
|
||
});
|