Initial commit: Core packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:45 +02:00
commit 12c29a983b
9512 changed files with 8379910 additions and 0 deletions

View file

@ -0,0 +1,227 @@
odoo.define('web.basic_fields_mobile_tests', function (require) {
"use strict";
var FormView = require('web.FormView');
var ListView = require('web.ListView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
QUnit.module('fields', {}, function () {
QUnit.module('basic_fields', {
beforeEach: function () {
this.data = {
partner: {
fields: {
date: {string: "A date", type: "date", searchable: true},
datetime: {string: "A datetime", type: "datetime", searchable: true},
display_name: {string: "Displayed name", type: "char", searchable: true},
foo: {string: "Foo", type: "char", default: "My little Foo Value", searchable: true, trim: true},
bar: {string: "Bar", type: "boolean", default: true, searchable: true},
int_field: {string: "int_field", type: "integer", sortable: true, searchable: true},
qux: {string: "Qux", type: "float", digits: [16,1], searchable: true},
},
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,
}, {
id: 2,
display_name: "second record",
bar: true,
foo: "blip",
int_field: 0,
qux: 0,
}, {
id: 4,
display_name: "aaa",
foo: "abc",
int_field: false,
qux: false,
}],
onchanges: {},
},
};
}
}, function () {
QUnit.module('PhoneWidget');
QUnit.test('phone field in form view on extra small screens', 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="phone"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
var $phoneLink = form.$('div.o_form_uri.o_field_widget.o_field_phone > a');
assert.strictEqual($phoneLink.length, 1,
"should have a anchor with correct classes");
assert.strictEqual($phoneLink.text(), 'yop',
"value should be displayed properly");
assert.hasAttrValue($phoneLink, 'href', 'tel:yop',
"should have proper tel 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 int 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);
$phoneLink = form.$('div.o_form_uri.o_field_widget.o_field_phone > a');
assert.strictEqual($phoneLink.text(), 'new',
"new value should be displayed properly");
assert.hasAttrValue($phoneLink, 'href', 'tel:new',
"should still have proper tel prefix");
form.destroy();
});
QUnit.test('phone field in editable list view on extra small screens', 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="phone"/></tree>',
});
assert.containsN(list, '.o_data_row', 3,
"should have 3 record");
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) a').first().text(), 'yop',
"value should be displayed properly");
var $phoneLink = list.$('div.o_form_uri.o_field_widget.o_field_phone > a');
assert.strictEqual($phoneLink.length, 3,
"should have anchors with correct classes");
assert.hasAttrValue($phoneLink.first(), 'href', 'tel:yop',
"should have proper tel prefix");
// Edit a line and check the result
var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
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");
$phoneLink = list.$('div.o_form_uri.o_field_widget.o_field_phone > a');
assert.strictEqual($phoneLink.length, 3,
"should still have anchors with correct classes");
assert.hasAttrValue($phoneLink.first(), 'href', 'tel:new',
"should still have proper tel prefix");
list.destroy();
});
QUnit.test('phone 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" widget="phone"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
var val = '<script>throw Error();</script><script>throw Error();</script>';
await testUtils.fields.editInput(form.$('input.o_field_widget[name="foo"]'), val);
// save
await testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), val,
"value should have been correctly escaped");
form.destroy();
});
QUnit.module('FieldDateRange');
QUnit.test('date field: toggle daterangepicker then scroll', async function (assert) {
assert.expect(4);
const scrollEvent = new UIEvent('scroll');
function scrollAtHeight(height) {
window.scrollTo(0, height);
document.dispatchEvent(scrollEvent);
}
this.data.partner.fields.date_end = {string: 'Date End', type: 'date'};
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>',
session: {
getTZOffset: function () {
return 330;
},
},
});
// Check date range picker initialization
assert.containsN(document.body, '.daterangepicker', 2,
"should initialize 2 date range picker");
// Open date range picker
await testUtils.dom.click("input[name=date]");
assert.isVisible($('.daterangepicker:first'),
"date range picker should be opened");
// Scroll
scrollAtHeight(50);
assert.isVisible($('.daterangepicker:first'),
"date range picker should be opened");
// Close picker
await testUtils.dom.click($('.daterangepicker:first .cancelBtn'));
assert.isNotVisible($('.daterangepicker:first'),
"date range picker should be closed");
form.destroy();
});
});
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,451 @@
odoo.define('web.field_utils_tests', function (require) {
"use strict";
var core = require('web.core');
var session = require('web.session');
var fieldUtils = require('web.field_utils');
QUnit.module('fields', {}, function () {
QUnit.module('field_utils');
QUnit.test('format integer', function(assert) {
assert.expect(5);
var originalGrouping = core._t.database.parameters.grouping;
core._t.database.parameters.grouping = [3, 3, 3, 3];
assert.strictEqual(fieldUtils.format.integer(1000000), '1,000,000');
core._t.database.parameters.grouping = [3, 2, -1];
assert.strictEqual(fieldUtils.format.integer(106500), '1,06,500');
core._t.database.parameters.grouping = [1, 2, -1];
assert.strictEqual(fieldUtils.format.integer(106500), '106,50,0');
assert.strictEqual(fieldUtils.format.integer(0), "0");
assert.strictEqual(fieldUtils.format.integer(false), "");
core._t.database.parameters.grouping = originalGrouping;
});
QUnit.test('format float', function(assert) {
assert.expect(5);
var originalParameters = $.extend(true, {}, core._t.database.parameters);
core._t.database.parameters.grouping = [3, 3, 3, 3];
assert.strictEqual(fieldUtils.format.float(1000000), '1,000,000.00');
core._t.database.parameters.grouping = [3, 2, -1];
assert.strictEqual(fieldUtils.format.float(106500), '1,06,500.00');
core._t.database.parameters.grouping = [1, 2, -1];
assert.strictEqual(fieldUtils.format.float(106500), '106,50,0.00');
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
thousands_sep: '.'
});
assert.strictEqual(fieldUtils.format.float(6000), '6.000,00');
assert.strictEqual(fieldUtils.format.float(false), '');
core._t.database.parameters = originalParameters;
});
QUnit.test("format_datetime", function (assert) {
assert.expect(1);
var date_string = "2009-05-04 12:34:23";
var date = fieldUtils.parse.datetime(date_string, {}, {timezone: false});
var str = fieldUtils.format.datetime(date, {}, {timezone: false});
assert.strictEqual(str, moment(date).format("MM/DD/YYYY HH:mm:ss"));
});
QUnit.test("format_datetime (with different timezone offset)", function (assert) {
assert.expect(2);
// mock the date format to avoid issues due to localisation
var dateFormat = core._t.database.parameters.date_format;
core._t.database.parameters.date_format = '%m/%d/%Y';
session.getTZOffset = function (date) {
// simulate daylight saving time
var startDate = new Date(2017, 2, 26);
var endDate = new Date(2017, 9, 29);
if (startDate < date && date < endDate) {
return 120; // UTC+2
} else {
return 60; // UTC+1
}
};
var str = fieldUtils.format.datetime(moment.utc('2017-01-01T10:00:00Z'));
assert.strictEqual(str, '01/01/2017 11:00:00');
str = fieldUtils.format.datetime(moment.utc('2017-06-01T10:00:00Z'));
assert.strictEqual(str, '06/01/2017 12:00:00');
core._t.database.parameters.date_format = dateFormat;
});
QUnit.test("format_many2one", function (assert) {
assert.expect(2);
assert.strictEqual('', fieldUtils.format.many2one(null));
assert.strictEqual('A M2O value', fieldUtils.format.many2one({
data: { display_name: 'A M2O value' },
}));
});
QUnit.test('format monetary', function(assert) {
assert.expect(1);
assert.strictEqual(fieldUtils.format.monetary(false), '');
});
QUnit.test('format char', function(assert) {
assert.expect(1);
assert.strictEqual(fieldUtils.format.char(), '',
"undefined char should be formatted as an empty string");
});
QUnit.test('format many2many', function(assert) {
assert.expect(3);
assert.strictEqual(fieldUtils.format.many2many({data: []}), 'No records');
assert.strictEqual(fieldUtils.format.many2many({data: [1]}), '1 record');
assert.strictEqual(fieldUtils.format.many2many({data: [1, 2]}), '2 records');
});
QUnit.test('format one2many', function(assert) {
assert.expect(3);
assert.strictEqual(fieldUtils.format.one2many({data: []}), 'No records');
assert.strictEqual(fieldUtils.format.one2many({data: [1]}), '1 record');
assert.strictEqual(fieldUtils.format.one2many({data: [1, 2]}), '2 records');
});
QUnit.test('format binary', function (assert) {
assert.expect(1);
// base64 estimated size (bytes) = value.length / 1.37 (http://en.wikipedia.org/wiki/Base64#MIME)
// Here: 4 / 1.37 = 2.91970800 => 2.92 (rounded 2 decimals by utils.human_size)
assert.strictEqual(fieldUtils.format.binary('Cg=='), '2.92 Bytes');
});
QUnit.test('format percentage', function (assert) {
assert.expect(12);
var originalParameters = _.clone(core._t.database.parameters);
assert.strictEqual(fieldUtils.format.percentage(0), '0%');
assert.strictEqual(fieldUtils.format.percentage(0.5), '50%');
assert.strictEqual(fieldUtils.format.percentage(1), '100%');
assert.strictEqual(fieldUtils.format.percentage(-0.2), '-20%');
assert.strictEqual(fieldUtils.format.percentage(2.5), '250%');
assert.strictEqual(fieldUtils.format.percentage(0.125), '12.5%');
assert.strictEqual(fieldUtils.format.percentage(0.666666), '66.67%');
assert.strictEqual(fieldUtils.format.percentage(false), '0%');
assert.strictEqual(fieldUtils.format.percentage(50, null,
{humanReadable: function (val) {return true;}}), '5k%'
);
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
thousands_sep: '.'
});
assert.strictEqual(fieldUtils.format.percentage(0.125), '12,5%');
assert.strictEqual(fieldUtils.format.percentage(0.666666), '66,67%');
assert.strictEqual(fieldUtils.format.percentage(0.5, null, { noSymbol: true }), '50');
core._t.database.parameters = originalParameters;
});
QUnit.test('format float time', function (assert) {
assert.expect(7);
assert.strictEqual(fieldUtils.format.float_time(2), '02:00');
assert.strictEqual(fieldUtils.format.float_time(3.5), '03:30');
assert.strictEqual(fieldUtils.format.float_time(0.25), '00:15');
assert.strictEqual(fieldUtils.format.float_time(-0.5), '-00:30');
const options = {
noLeadingZeroHour: true,
};
assert.strictEqual(fieldUtils.format.float_time(2, null, options), '2:00');
assert.strictEqual(fieldUtils.format.float_time(3.5, null, options), '3:30');
assert.strictEqual(fieldUtils.format.float_time(-0.5, null, options), '-0:30');
});
QUnit.test('parse float', function(assert) {
assert.expect(10);
var originalParameters = _.clone(core._t.database.parameters);
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: '.',
thousands_sep: ','
});
assert.strictEqual(fieldUtils.parse.float(""), 0);
assert.strictEqual(fieldUtils.parse.float("0"), 0);
assert.strictEqual(fieldUtils.parse.float("100.00"), 100);
assert.strictEqual(fieldUtils.parse.float("-100.00"), -100);
assert.strictEqual(fieldUtils.parse.float("1,000.00"), 1000);
assert.strictEqual(fieldUtils.parse.float("1,000,000.00"), 1000000);
assert.strictEqual(fieldUtils.parse.float('1,234.567'), 1234.567);
assert.throws(function () {
fieldUtils.parse.float("1.000.000");
}, "Throw an exception if it's not a valid number");
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
thousands_sep: '.'
});
assert.strictEqual(fieldUtils.parse.float('1.234,567'), 1234.567);
assert.throws(function () {
fieldUtils.parse.float("1,000,000");
}, "Throw an exception if it's not a valid number");
_.extend(core._t.database.parameters, originalParameters);
});
QUnit.test('parse integer', function(assert) {
assert.expect(11);
var originalParameters = _.clone(core._t.database.parameters);
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: '.',
thousands_sep: ','
});
assert.strictEqual(fieldUtils.parse.integer(""), 0);
assert.strictEqual(fieldUtils.parse.integer("0"), 0);
assert.strictEqual(fieldUtils.parse.integer("100"), 100);
assert.strictEqual(fieldUtils.parse.integer("-100"), -100);
assert.strictEqual(fieldUtils.parse.integer("1,000"), 1000);
assert.strictEqual(fieldUtils.parse.integer("1,000,000"), 1000000);
assert.throws(function () {
fieldUtils.parse.integer("1.000.000");
}, "Throw an exception if it's not a valid number");
assert.throws(function () {
fieldUtils.parse.integer("1,234.567");
}, "Throw an exception if the number is a float");
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
thousands_sep: '.'
});
assert.strictEqual(fieldUtils.parse.integer("1.000.000"), 1000000);
assert.throws(function () {
fieldUtils.parse.integer("1,000,000");
}, "Throw an exception if it's not a valid number");
assert.throws(function () {
fieldUtils.parse.integer("1.234,567");
}, "Throw an exception if the number is a float");
_.extend(core._t.database.parameters, originalParameters);
});
QUnit.test('parse monetary', function(assert) {
assert.expect(15);
var originalCurrencies = session.currencies;
const originalParameters = _.clone(core._t.database.parameters);
session.currencies = {
1: {
digits: [69, 2],
position: "after",
symbol: "€"
},
3: {
digits: [69, 2],
position: "before",
symbol: "$"
}
};
assert.strictEqual(fieldUtils.parse.monetary(""), 0);
assert.strictEqual(fieldUtils.parse.monetary("0"), 0);
assert.strictEqual(fieldUtils.parse.monetary("100.00"), 100);
assert.strictEqual(fieldUtils.parse.monetary("-100.00"), -100);
assert.strictEqual(fieldUtils.parse.monetary("1,000.00"), 1000);
assert.strictEqual(fieldUtils.parse.monetary("1,000,000.00"), 1000000);
assert.strictEqual(fieldUtils.parse.monetary("$&nbsp;125.00", {}, {currency_id: 3}), 125);
assert.strictEqual(fieldUtils.parse.monetary("1,000.00&nbsp;€", {}, {currency_id: 1}), 1000);
assert.throws(function() {fieldUtils.parse.monetary("$ 12.00", {}, {currency_id: 3})}, /is not a correct/);
assert.throws(function() {fieldUtils.parse.monetary("$&nbsp;12.00", {}, {currency_id: 1})}, /is not a correct/);
assert.throws(function() {fieldUtils.parse.monetary("$&nbsp;12.00&nbsp;34", {}, {currency_id: 3})}, /is not a correct/);
// In some languages, the non-breaking space character is used as thousands separator.
const nbsp = '\u00a0';
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: '.',
thousands_sep: nbsp,
});
assert.strictEqual(fieldUtils.parse.monetary(`1${nbsp}000.00${nbsp}`, {}, {currency_id: 1}), 1000);
assert.strictEqual(fieldUtils.parse.monetary(`$${nbsp}1${nbsp}000.00`, {}, {currency_id: 3}), 1000);
assert.strictEqual(fieldUtils.parse.monetary(`1${nbsp}000.00`), 1000);
assert.strictEqual(fieldUtils.parse.monetary(`1${nbsp}000${nbsp}000.00`), 1000000);
session.currencies = originalCurrencies;
core._t.database.parameters = originalParameters;
});
QUnit.test('parse percentage', function(assert) {
assert.expect(7);
var originalParameters = _.clone(core._t.database.parameters);
assert.strictEqual(fieldUtils.parse.percentage(""), 0);
assert.strictEqual(fieldUtils.parse.percentage("0"), 0);
assert.strictEqual(fieldUtils.parse.percentage("0.5"), 0.005);
assert.strictEqual(fieldUtils.parse.percentage("1"), 0.01);
assert.strictEqual(fieldUtils.parse.percentage("100"), 1);
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
thousands_sep: '.'
});
assert.strictEqual(fieldUtils.parse.percentage("1.234,56"), 12.3456);
assert.strictEqual(fieldUtils.parse.percentage("6,02"), 0.0602);
core._t.database.parameters = originalParameters;
});
QUnit.test('parse datetime', function (assert) {
assert.expect(7);
var originalParameters = _.clone(core._t.database.parameters);
var originalLocale = moment.locale();
var dateStr, date1, date2;
moment.defineLocale('englishForTest', {
dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/,
ordinal: function (number) {
var b = number % 10,
output = (~~(number % 100 / 10) === 1) ? 'th' :
(b === 1) ? 'st' :
(b === 2) ? 'nd' :
(b === 3) ? 'rd' : 'th';
return number + output;
},
});
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.',
});
moment.locale('englishForTest');
_.extend(core._t.database.parameters, {date_format: '%m/%d/%Y', time_format: '%H:%M:%S'});
assert.throws(function () {
fieldUtils.parse.datetime("13/01/2019 12:00:00", {}, {});
}, /is not a correct/, "Wrongly formated dates should be invalid");
assert.throws(function () {
fieldUtils.parse.datetime("10000-01-01 12:00:00", {}, {});
}, /is not a correct/, "Dates after 9999 should be invalid");
assert.throws(function () {
fieldUtils.parse.datetime("999-01-01 12:00:00", {}, {});
}, /is not a correct/, "Dates before 1000 should be invalid");
dateStr = '01/13/2019 10:05:45';
date1 = fieldUtils.parse.datetime(dateStr);
date2 = moment.utc(dateStr, ['MM/DD/YYYY HH:mm:ss'], true);
assert.equal(date1.format(), date2.format(), "Date with leading 0");
dateStr = '1/14/2019 10:5:45';
date1 = fieldUtils.parse.datetime(dateStr);
date2 = moment.utc(dateStr, ['M/D/YYYY H:m:s'], true);
assert.equal(date1.format(), date2.format(), "Date without leading 0");
dateStr = '01/01/1000 10:15:45';
date1 = fieldUtils.parse.datetime(dateStr);
date2 = moment.utc(dateStr, ['MM/DD/YYYY HH:mm:ss'], true);
assert.equal(date1.format(), date2.format(), "can parse dates of year 1");
moment.locale('norvegianForTest');
_.extend(core._t.database.parameters, {date_format: '%d. %b %Y', time_format: '%H:%M:%S'});
dateStr = '16. jan. 2019 10:05:45';
date1 = fieldUtils.parse.datetime(dateStr);
date2 = moment.utc(dateStr, ['DD. MMM YYYY HH:mm:ss'], true);
assert.equal(date1.format(), date2.format(), "Day/month inverted + month i18n");
moment.locale(originalLocale);
moment.updateLocale("englishForTest", null);
moment.updateLocale("norvegianForTest", null);
core._t.database.parameters = originalParameters;
});
QUnit.test('parse date without separator', function (assert) {
assert.expect(8);
var originalParameters = _.clone(core._t.database.parameters);
_.extend(core._t.database.parameters, {date_format: '%d.%m/%Y'});
var dateFormat = "DD.MM/YYYY";
assert.throws(function () {fieldUtils.parse.date("1197")}, /is not a correct/, "Wrongly formated dates should be invalid");
assert.throws(function () {fieldUtils.parse.date("0131")}, /is not a correct/, "Wrongly formated dates should be invalid");
assert.throws(function () {fieldUtils.parse.date("970131")}, /is not a correct/, "Wrongly formated dates should be invalid");
assert.equal(fieldUtils.parse.date("3101").format(dateFormat), "31.01/" + moment.utc().year());
assert.equal(fieldUtils.parse.date("31.01").format(dateFormat), "31.01/" + moment.utc().year());
assert.equal(fieldUtils.parse.date("310197").format(dateFormat), "31.01/1997");
assert.equal(fieldUtils.parse.date("310117").format(dateFormat), "31.01/2017");
assert.equal(fieldUtils.parse.date("31011985").format(dateFormat), "31.01/1985");
core._t.database.parameters = originalParameters;
});
QUnit.test('parse datetime without separator', function (assert) {
assert.expect(3);
var originalParameters = _.clone(core._t.database.parameters);
_.extend(core._t.database.parameters, {date_format: '%d.%m/%Y', time_format: '%H:%M/%S'});
var dateTimeFormat = "DD.MM/YYYY HH:mm/ss";
assert.equal(fieldUtils.parse.datetime("3101198508").format(dateTimeFormat), "31.01/1985 08:00/00");
assert.equal(fieldUtils.parse.datetime("310119850833").format(dateTimeFormat), "31.01/1985 08:33/00");
assert.equal(fieldUtils.parse.datetime("31/01/1985 08").format(dateTimeFormat), "31.01/1985 08:00/00");
core._t.database.parameters = originalParameters;
});
});
QUnit.test('parse smart date input', function (assert) {
assert.expect(10);
const format = "DD MM YYYY";
assert.strictEqual(fieldUtils.parse.date("+1d").format(format), moment().add(1, 'days').format(format));
assert.strictEqual(fieldUtils.parse.datetime("+2w").format(format), moment().add(2, 'weeks').format(format));
assert.strictEqual(fieldUtils.parse.date("+3m").format(format), moment().add(3, 'months').format(format));
assert.strictEqual(fieldUtils.parse.datetime("+4y").format(format), moment().add(4, 'years').format(format));
assert.strictEqual(fieldUtils.parse.date("+5").format(format), moment().add(5, 'days').format(format));
assert.strictEqual(fieldUtils.parse.datetime("-5").format(format), moment().subtract(5, 'days').format(format));
assert.strictEqual(fieldUtils.parse.date("-4y").format(format), moment().subtract(4, 'years').format(format));
assert.strictEqual(fieldUtils.parse.datetime("-3m").format(format), moment().subtract(3, 'months').format(format));
assert.strictEqual(fieldUtils.parse.date("-2w").format(format), moment().subtract(2, 'weeks').format(format));
assert.strictEqual(fieldUtils.parse.datetime("-1d").format(format), moment().subtract(1, 'days').format(format));
});
});

View file

@ -0,0 +1,66 @@
odoo.define("web.relational_fields_mobile_tests", function (require) {
"use strict";
const FormView = require("web.FormView");
const testUtils = require("web.test_utils");
QUnit.module("fields", {}, function () {
QUnit.module("relational_fields", {
beforeEach() {
this.data = {
partner: {
fields: {
display_name: { string: "Displayed name", type: "char" },
p: {string: "one2many field", type: "one2many", relation: "partner", relation_field: "trululu"},
trululu: {string: "Trululu", type: "many2one", relation: "partner"},
},
records: [{
id: 1,
display_name: "first record",
p: [2, 4],
trululu: 4,
}, {
id: 2,
display_name: "second record",
p: [],
trululu: 1,
}, {
id: 4,
display_name: "aaa",
}],
},
};
},
}, function () {
QUnit.module("FieldOne2Many");
QUnit.test("one2many on mobile: display list if present without kanban view", async function (assert) {
assert.expect(2);
const form = await testUtils.createView({
View: FormView,
model: "partner",
data: this.data,
arch: `
<form>
<field name="p">
<tree>
<field name="display_name"/>
</tree>
</field>
</form>
`,
res_id: 1,
});
await testUtils.form.clickEdit(form);
assert.containsOnce(form, ".o_field_x2many_list",
"should display one2many's list");
assert.containsN(form, ".o_field_x2many_list .o_data_row", 2,
"should display 2 records in one2many's list");
form.destroy();
});
});
});
});

View file

@ -0,0 +1,208 @@
odoo.define('web.signature_field_tests', function (require) {
"use strict";
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
QUnit.module('fields', {}, function () {
QUnit.module('signature legacy', {
beforeEach: function () {
this.data = {
partner: {
fields: {
display_name: {string: "Name", type: "char" },
product_id: {string: "Product Name", type: "many2one", relation: 'product'},
sign: {string: "Signature", type: "binary"},
},
records: [{
id: 1,
display_name: "Pop's Chock'lit",
product_id: 7,
}],
onchanges: {},
},
product: {
fields: {
name: {string: "Product Name", type: "char"}
},
records: [{
id: 7,
display_name: "Veggie Burger",
}]
},
};
}
}, function () {
QUnit.test('Set simple field in "full_name" node option', async function (assert) {
assert.expect(3);
var form = await createView({
View: FormView,
model: 'partner',
res_id: 1,
data: this.data,
arch: '<form>' +
'<field name="display_name"/>' +
'<field name="sign" widget="signature" options="{\'full_name\': \'display_name\'}" />' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/sign/get_fonts/') {
return Promise.resolve();
}
return this._super(route, args);
},
});
await testUtils.form.clickEdit(form);
assert.containsOnce(form, 'div[name=sign] div.o_signature svg',
"should have a valid signature widget");
// Click on the widget to open signature modal
await testUtils.dom.click(form.$('div[name=sign] div.o_signature'));
assert.strictEqual($('.modal .modal-body a.o_web_sign_auto_button').length, 1,
'should open a modal with "Auto" button');
assert.strictEqual($('.modal .modal-body .o_web_sign_name_input').val(), "Pop's Chock'lit",
'Correct Value should be set in the input for auto drawing the signature');
form.destroy();
});
QUnit.test('Set m2o field in "full_name" node option', async function (assert) {
assert.expect(3);
var form = await createView({
View: FormView,
model: 'partner',
res_id: 1,
data: this.data,
arch: '<form>' +
'<field name="product_id"/>' +
'<field name="sign" widget="signature" options="{\'full_name\': \'product_id\'}" />' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/sign/get_fonts/') {
return Promise.resolve();
}
return this._super(route, args);
},
});
await testUtils.form.clickEdit(form);
assert.containsOnce(form, 'div[name=sign] div.o_signature svg',
"should have a valid signature widget");
// Click on the widget to open signature modal
await testUtils.dom.click(form.$('div[name=sign] div.o_signature'));
assert.strictEqual($('.modal .modal-body a.o_web_sign_auto_button').length, 1,
'should open a modal with "Auto" button');
assert.strictEqual($('.modal .modal-body .o_web_sign_name_input').val(), "Veggie Burger",
'Correct Value should be set in the input for auto drawing the signature');
form.destroy();
});
QUnit.module('Signature Widget');
QUnit.test('Signature widget renders a Sign button', async function (assert) {
assert.expect(3);
const form = await createView({
View: FormView,
model: 'partner',
res_id: 1,
data: this.data,
arch: '<form>' +
'<header>' +
'<widget name="signature" string="Sign"/>' +
'</header>' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/sign/get_fonts/') {
return Promise.resolve();
}
return this._super(route, args);
},
});
assert.containsOnce(form, 'button.o_sign_button.o_widget',
"Should have a signature widget button");
assert.strictEqual($('.modal-dialog').length, 0,
"Should not have any modal");
// Clicks on the sign button to open the sign modal.
await testUtils.dom.click(form.$('span.o_sign_label'));
assert.strictEqual($('.modal-dialog').length, 1,
"Should have one modal opened");
form.destroy();
});
QUnit.test('Signature widget: full_name option', async function (assert) {
assert.expect(2);
const form = await createView({
View: FormView,
model: 'partner',
res_id: 1,
data: this.data,
arch: '<form>' +
'<header>' +
'<widget name="signature" string="Sign" full_name="display_name"/>' +
'</header>' +
'<field name="display_name"/>' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/sign/get_fonts/') {
return Promise.resolve();
}
return this._super(route, args);
},
});
// Clicks on the sign button to open the sign modal.
await testUtils.dom.click(form.$('span.o_sign_label'));
assert.strictEqual($('.modal .modal-body a.o_web_sign_auto_button').length, 1,
"Should open a modal with \"Auto\" button");
assert.strictEqual($('.modal .modal-body .o_web_sign_name_input').val(), "Pop's Chock'lit",
"Correct Value should be set in the input for auto drawing the signature");
form.destroy();
});
QUnit.test('Signature widget: highlight option', async function (assert) {
assert.expect(3);
const form = await createView({
View: FormView,
model: 'partner',
res_id: 1,
data: this.data,
arch: '<form>' +
'<header>' +
'<widget name="signature" string="Sign" highlight="1"/>' +
'</header>' +
'</form>',
mockRPC: function (route, args) {
if (route === '/web/sign/get_fonts/') {
return Promise.resolve();
}
return this._super(route, args);
},
});
assert.hasClass(form.$('button.o_sign_button.o_widget'), 'btn-primary',
"The button must have the 'btn-primary' class as \"highlight=1\"");
// Clicks on the sign button to open the sign modal.
await testUtils.dom.click(form.$('span.o_sign_label'));
assert.isNotVisible($('.modal .modal-body a.o_web_sign_auto_button'),
"\"Auto\" button must be invisible");
assert.strictEqual($('.modal .modal-body .o_web_sign_name_input').val(), '',
"No value should be set in the input for auto drawing the signature");
form.destroy();
});
});
});
});

View file

@ -0,0 +1,314 @@
odoo.define('web.special_fields_tests', function (require) {
"use strict";
var FormView = require('web.FormView');
var ListView = require('web.ListView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
QUnit.module('fields', {}, function () {
QUnit.module('special_fields', {
beforeEach: function () {
this.data = {
partner: {
fields: {
display_name: { string: "Displayed name", type: "char" },
foo: {string: "Foo", type: "char", default: "My little Foo Value"},
bar: {string: "Bar", type: "boolean", default: true},
int_field: {string: "int_field", type: "integer", sortable: true},
qux: {string: "Qux", type: "float", digits: [16,1] },
p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'},
turtles: {string: "one2many turtle field", type: "one2many", relation: 'turtle'},
trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'},
product_id: {string: "Product", type: "many2one", relation: 'product'},
color: {
type: "selection",
selection: [['red', "Red"], ['black', "Black"]],
default: 'red',
},
date: {string: "Some Date", type: "date"},
datetime: {string: "Datetime Field", type: 'datetime'},
user_id: {string: "User", type: 'many2one', relation: 'user'},
},
records: [{
id: 1,
display_name: "first record",
bar: true,
foo: "yop",
int_field: 10,
qux: 0.44,
p: [],
turtles: [2],
timmy: [],
trululu: 4,
user_id: 17,
}, {
id: 2,
display_name: "second record",
bar: true,
foo: "blip",
int_field: 9,
qux: 13,
p: [],
timmy: [],
trululu: 1,
product_id: 37,
date: "2017-01-25",
datetime: "2016-12-12 10:55:05",
user_id: 17,
}, {
id: 4,
display_name: "aaa",
bar: false,
}],
onchanges: {},
},
product: {
fields: {
name: {string: "Product Name", type: "char"}
},
records: [{
id: 37,
display_name: "xphone",
}, {
id: 41,
display_name: "xpad",
}]
},
partner_type: {
fields: {
name: {string: "Partner Type", type: "char"},
color: {string: "Color index", type: "integer"},
},
records: [
{id: 12, display_name: "gold", color: 2},
{id: 14, display_name: "silver", color: 5},
]
},
turtle: {
fields: {
display_name: { string: "Displayed name", type: "char" },
turtle_foo: {string: "Foo", type: "char", default: "My little Foo Value"},
turtle_bar: {string: "Bar", type: "boolean", default: true},
turtle_int: {string: "int", type: "integer", sortable: true},
turtle_qux: {string: "Qux", type: "float", digits: [16,1], required: true, default: 1.5},
turtle_description: {string: "Description", type: "text"},
turtle_trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
product_id: {string: "Product", type: "many2one", relation: 'product', required: true},
partner_ids: {string: "Partner", type: "many2many", relation: 'partner'},
},
records: [{
id: 1,
display_name: "leonardo",
turtle_bar: true,
turtle_foo: "yop",
partner_ids: [],
}, {
id: 2,
display_name: "donatello",
turtle_bar: true,
turtle_foo: "blip",
turtle_int: 9,
partner_ids: [2,4],
}, {
id: 3,
display_name: "raphael",
turtle_bar: false,
turtle_foo: "kawa",
turtle_int: 21,
turtle_qux: 9.8,
partner_ids: [],
}],
},
user: {
fields: {
name: {string: "Name", type: "char"}
},
records: [{
id: 17,
name: "Aline",
}, {
id: 19,
name: "Christine",
}]
},
};
}
}, function () {
QUnit.module('FieldTimezoneMismatch');
QUnit.test('widget timezone_mismatch in a list view', async function (assert) {
assert.expect(5);
this.data.partner.fields.tz_offset = {
string: "tz_offset",
type: "char"
};
this.data.partner.records.forEach(function (r) {
r.color = 'red';
r.tz_offset = 0;
});
this.data.partner.onchanges = {
color: function (r) {
r.tz_offset = '+4800'; // make sur we have a mismatch
}
};
var list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree string="Colors" editable="top">' +
'<field name="tz_offset" invisible="True"/>' +
'<field name="color" widget="timezone_mismatch"/>' +
'</tree>',
});
assert.strictEqual(list.$('td:contains(Red)').length, 3,
"should have 3 rows with correct value");
await testUtils.dom.click(list.$('td:contains(Red):first'));
var $td = list.$('tbody tr.o_selected_row td:not(.o_list_record_selector)');
assert.strictEqual($td.find('select').length, 1, "td should have a child 'select'");
assert.strictEqual($td.contents().length, 1, "select tag should be only child of td");
await testUtils.fields.editSelect($td.find('select'), '"black"');
assert.strictEqual($td.find('.o_tz_warning').length, 1, "Should display icon alert");
assert.ok($td.find('select option:selected').text().match(/Black\s+\([0-9]+\/[0-9]+\/[0-9]+ [0-9]+:[0-9]+:[0-9]+\)/), "Should display the datetime in the selected timezone");
list.destroy();
});
QUnit.test('widget timezone_mismatch in a form view', async function (assert) {
assert.expect(2);
this.data.partner.fields.tz_offset = {
string: "tz_offset",
type: "char"
};
this.data.partner.fields.tz = {
type: "selection",
selection: [['Europe/Brussels', "Europe/Brussels"], ['America/Los_Angeles', "America/Los_Angeles"]],
};
this.data.partner.records[0].tz = false;
this.data.partner.records[0].tz_offset = '+4800';
var form = await createView({
View: FormView,
model: 'partner',
res_id: 1,
data: this.data,
arch: '<form>' +
'<field name="tz_offset" invisible="True"/>' +
'<field name="tz" widget="timezone_mismatch"/>' +
'</form>',
});
await testUtils.form.clickEdit(form);
assert.containsOnce(form, 'select[name=tz]');
var $timezoneMismatch = form.$('.o_tz_warning');
assert.strictEqual($timezoneMismatch.length, 1, "warning class should be there.");
form.destroy();
});
QUnit.test('widget timezone_mismatch in a form view edit mode with mismatch', async function (assert) {
assert.expect(3);
this.data.partner.fields.tz_offset = {
string: "tz_offset",
type: "char"
};
this.data.partner.fields.tz = {
type: "selection",
selection: [['Europe/Brussels', "Europe/Brussels"], ['America/Los_Angeles', "America/Los_Angeles"]],
};
this.data.partner.records[0].tz = 'America/Los_Angeles';
this.data.partner.records[0].tz_offset = '+4800';
var form = await createView({
View: FormView,
model: 'partner',
res_id: 1,
data: this.data,
arch: '<form>' +
'<field name="tz_offset" invisible="True"/>' +
'<field name="tz" widget="timezone_mismatch" options="{\'tz_offset_field\': \'tz_offset\'}"/>' +
'</form>',
viewOptions: {
mode: 'edit',
},
});
var $timezoneEl = form.$('select[name="tz"]');
assert.strictEqual($timezoneEl.children().length, 3,
'The select element should have 3 children');
var $timezoneMismatch = form.$('.o_tz_warning');
assert.strictEqual($timezoneMismatch.length, 1,
'timezone mismatch is present');
assert.notOk($timezoneMismatch.children().length,
'The mismatch element should not have children');
form.destroy();
});
QUnit.module('IframeWrapper');
QUnit.test('iframe_wrapper widget in form view', async function (assert) {
assert.expect(2);
this.data = {
report: {
fields: {
report_content: {string: "Content of report", type: "html"}
},
records: [{
id: 1,
report_content:
`<html>
<head>
<style>
body { color : rgb(255, 0, 0); }
</style>
<head>
<body>
<div class="nice_div"><p>Some content</p></div>
</body>
</html>`
}]
}
};
const form = await createView({
View: FormView,
model: 'report',
data: this.data,
arch: `<form><field name="report_content" widget="iframe_wrapper"/></form>`,
res_id: 1,
});
const $iframe = form.$('iframe');
await $iframe.data('ready');
const doc = $iframe.contents()[0];
assert.strictEqual($(doc).find('.nice_div').html(), '<p>Some content</p>',
"should have rendered a div with correct content");
assert.strictEqual($(doc).find('.nice_div p').css('color'), 'rgb(255, 0, 0)',
"head tag style should have been applied");
form.destroy();
});
});
});
});