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,420 @@
odoo.define('web.data_export_tests', function (require) {
"use strict";
const data = require('web.data');
const framework = require('web.framework');
const ListView = require('web.ListView');
const testUtils = require('web.test_utils');
const createView = testUtils.createView;
QUnit.module('widgets', {
beforeEach: function () {
this.data = {
'partner': {
fields: {
foo: {string: "Foo", type: "char"},
bar: {string: "Bar", type: "char"},
unexportable: {string: "Unexportable", type: "boolean", exportable: false},
},
records: [
{
id: 1,
foo: "yop",
bar: "bar-blup",
}, {
id: 2,
foo: "yop",
bar: "bar-yop",
}, {
id: 3,
foo: "blup",
bar: "bar-blup",
}
]
},
'ir.exports': {
fields: {
name: {string: "Name", type: "char"},
},
records: [],
},
};
this.mockSession = {
async user_has_group(g) { return g === 'base.group_allow_export'; }
}
this.mockDataExportRPCs = function (route) {
if (route === '/web/export/formats') {
return Promise.resolve([
{tag: 'csv', label: 'CSV'},
{tag: 'xls', label: 'Excel'},
]);
}
if (route === '/web/export/get_fields') {
return Promise.resolve([
{
field_type: "one2many",
string: "Activities",
required: false,
value: "activity_ids/id",
id: "activity_ids",
params: {"model": "mail.activity", "prefix": "activity_ids", "name": "Activities"},
relation_field: "res_id",
children: true,
}, {
children: false,
field_type: 'char',
id: "foo",
relation_field: null,
required: false,
string: 'Foo',
value: "foo",
}
]);
}
return this._super.apply(this, arguments);
};
}
}, function () {
QUnit.module('Data Export');
QUnit.test('exporting all data in list view', async function (assert) {
assert.expect(8);
var blockUI = framework.blockUI;
var unblockUI = framework.unblockUI;
framework.blockUI = function () {
assert.step('block UI');
};
framework.unblockUI = function () {
assert.step('unblock UI');
};
var list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree><field name="foo"/></tree>',
viewOptions: {
hasActionMenus: true,
},
mockRPC: this.mockDataExportRPCs,
session: {
...this.mockSession,
get_file: function (params) {
assert.step(params.url);
params.complete();
},
},
});
await testUtils.dom.click(list.$('thead th.o_list_record_selector input'));
await testUtils.controlPanel.toggleActionMenu(list);
await testUtils.controlPanel.toggleMenuItem(list, 'Export');
assert.strictEqual($('.modal').length, 1, "a modal dialog should be open");
assert.strictEqual($('div.o_tree_column:contains(Activities)').length, 1,
"the Activities field should be in the list of exportable fields");
assert.strictEqual($('.modal .o_export_field').length, 1, "There should be only one export field");
assert.strictEqual($('.modal .o_export_field').data('field_id'), 'foo', "There should be only one export field");
// select the field Description, click on add, then export and close
await testUtils.dom.click($('.modal .o_tree_column:contains(Foo) .o_add_field'));
await testUtils.dom.click($('.modal span:contains(Export)'));
await testUtils.dom.click($('.modal span:contains(Close)'));
list.destroy();
framework.blockUI = blockUI;
framework.unblockUI = unblockUI;
assert.verifySteps([
'block UI',
'/web/export/csv',
'unblock UI',
]);
});
QUnit.test('exporting data in list view (multi pages)', async function (assert) {
assert.expect(4);
let expectedData;
const list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree limit="2"><field name="foo"/></tree>',
domain: [['id', '<', 1000]],
viewOptions: {
hasActionMenus: true,
},
mockRPC: this.mockDataExportRPCs,
session: {
...this.mockSession,
get_file: function (params) {
const data = JSON.parse(params.data.data);
assert.deepEqual({ids: data.ids, domain: data.domain}, expectedData);
params.complete();
},
},
});
// select all records (first page) and export
expectedData = {
ids: [1, 2],
domain: [['id', '<', 1000]],
};
await testUtils.dom.click(list.$('thead th.o_list_record_selector input'));
await testUtils.controlPanel.toggleActionMenu(list);
await testUtils.controlPanel.toggleMenuItem(list, 'Export');
assert.containsOnce(document.body, '.modal');
await testUtils.dom.click($('.modal span:contains(Export)'));
await testUtils.dom.click($('.modal span:contains(Close)'));
// select all domain and export
expectedData = {
ids: false,
domain: [['id', '<', 1000]],
};
await testUtils.dom.click(list.$('.o_list_selection_box .o_list_select_domain'));
await testUtils.controlPanel.toggleActionMenu(list);
await testUtils.controlPanel.toggleMenuItem(list, 'Export');
assert.containsOnce(document.body, '.modal');
await testUtils.dom.click($('.modal span:contains(Export)'));
await testUtils.dom.click($('.modal span:contains(Close)'));
list.destroy();
});
QUnit.test('exporting view with non-exportable field', async function (assert) {
assert.expect(0);
var list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree><field name="unexportable"/></tree>',
viewOptions: {
hasActionMenus: true,
},
mockRPC: this.mockDataExportRPCs,
session: {
...this.mockSession,
get_file: function (params) {
assert.step(params.url);
params.complete();
},
},
});
await testUtils.dom.click(list.$('thead th.o_list_record_selector input'));
await testUtils.controlPanel.toggleActionMenu(list);
await testUtils.controlPanel.toggleMenuItem(list, 'Export');
list.destroy();
});
QUnit.test('saving fields list when exporting data', async function (assert) {
assert.expect(4);
var create = data.DataSet.prototype.create;
data.DataSet.prototype.create = function () {
assert.step('create');
return Promise.resolve([]);
};
var list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree><field name="foo"/></tree>',
viewOptions: {
hasActionMenus: true,
},
session: this.mockSession,
mockRPC: this.mockDataExportRPCs,
});
// Open the export modal
await testUtils.dom.click(list.$('thead th.o_list_record_selector input'));
await testUtils.controlPanel.toggleActionMenu(list);
await testUtils.controlPanel.toggleMenuItem(list, 'Export');
assert.strictEqual($('.modal').length, 1,
"a modal dialog should be open");
// Select 'Activities' in fields to export
await testUtils.dom.click($('.modal .o_export_tree_item:contains(Activities) .o_add_field'));
assert.strictEqual($('.modal .o_fields_list .o_export_field').length, 2,
"there should be two items in the fields list");
// Save as template
await testUtils.fields.editAndTrigger($('.modal .o_exported_lists_select'), 'new_template', ['change']);
await testUtils.fields.editInput($('.modal .o_save_list .o_save_list_name'), 'fields list');
await testUtils.dom.click($('.modal .o_save_list .o_save_list_btn'));
assert.verifySteps(['create'],
"create should have been called");
// Close the modal and destroy list
await testUtils.dom.click($('.modal button span:contains(Close)'));
list.destroy();
// restore create function
data.DataSet.prototype.create = create;
});
QUnit.test('Export dialog UI test', async function (assert) {
assert.expect(5);
var list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: '<tree><field name="foo"/></tree>',
viewOptions: {
hasActionMenus: true,
},
session: this.mockSession,
mockRPC: this.mockDataExportRPCs,
});
// Open the export modal
await testUtils.dom.click(list.$('thead th.o_list_record_selector input'));
await testUtils.controlPanel.toggleActionMenu(list);
await testUtils.controlPanel.toggleMenuItem(list, 'Export');
assert.strictEqual($('.modal .o_export_tree_item:visible').length, 2, "There should be only two items visible");
await testUtils.dom.click($('.modal .o_export_search_input'));
$('.modal .o_export_search_input').val('Activities').trigger($.Event('input', {
keyCode: 65,
}));
assert.strictEqual($('.modal .o_export_tree_item:visible').length, 1, "Only match item visible");
// Add field
await testUtils.dom.click($('.modal div:contains(Activities) .o_add_field'));
assert.strictEqual($('.modal .o_fields_list li').length, 2, "There should be two fields in export field list.");
assert.strictEqual($('.modal .o_fields_list li:eq(1)').text(), "Activities",
"string of second field in export list should be 'Activities'");
// Remove field
await testUtils.dom.click($('.modal .o_fields_list li:first .o_remove_field'));
assert.strictEqual($('.modal .o_fields_list li').length, 1, "There should be only one field in list");
list.destroy();
});
QUnit.test('Direct export button invisible', async function (assert) {
assert.expect(1)
let list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: `<tree export_xlsx="0"><field name="foo"/></tree>`,
session: this.mockSession,
});
assert.containsNone(list, '.o_list_export_xlsx')
list.destroy();
});
QUnit.test('Direct export list ', async function (assert) {
assert.expect(2);
let list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: `
<tree export_xlsx="1">
<field name="foo"/>
<field name="bar"/>
</tree>`,
domain: [['bar', '!=', 'glou']],
session: {
...this.mockSession,
get_file(args) {
let data = JSON.parse(args.data.data);
assert.strictEqual(args.url, '/web/export/xlsx', "should call get_file with the correct url");
assert.deepEqual(data, {
context: {},
model: 'partner',
domain: [['bar', '!=', 'glou']],
groupby: [],
ids: false,
import_compat: false,
fields: [{
name: 'foo',
label: 'Foo',
type: 'char',
}, {
name: 'bar',
label: 'Bar',
type: 'char',
}]
}, "should be called with correct params");
args.complete();
},
},
});
// Download
await testUtils.dom.click(list.$buttons.find('.o_list_export_xlsx'));
list.destroy();
});
QUnit.test('Direct export grouped list ', async function (assert) {
assert.expect(2);
let list = await createView({
View: ListView,
model: 'partner',
data: this.data,
arch: `
<tree>
<field name="foo"/>
<field name="bar"/>
</tree>`,
groupBy: ['foo', 'bar'],
domain: [['bar', '!=', 'glou']],
session: {
...this.mockSession,
get_file(args) {
let data = JSON.parse(args.data.data);
assert.strictEqual(args.url, '/web/export/xlsx', "should call get_file with the correct url");
assert.deepEqual(data, {
context: {},
model: 'partner',
domain: [['bar', '!=', 'glou']],
groupby: ['foo', 'bar'],
ids: false,
import_compat: false,
fields: [{
name: 'foo',
label: 'Foo',
type: 'char',
}, {
name: 'bar',
label: 'Bar',
type: 'char',
}]
}, "should be called with correct params");
args.complete();
},
},
});
await testUtils.dom.click(list.$buttons.find('.o_list_export_xlsx'));
list.destroy();
});
});
});

View file

@ -0,0 +1,315 @@
odoo.define('web.domain_selector_tests', function (require) {
"use strict";
const FormView = require("web.FormView");
var DomainSelector = require("web.DomainSelector");
var Widget = require("web.Widget");
var testUtils = require("web.test_utils");
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
const { registry } = require('@web/core/registry');
const legacyViewRegistry = require('web.view_registry');
QUnit.module('widgets', {}, function () {
QUnit.module('DomainSelector', {
beforeEach: function () {
this.data = {
partner: {
fields: {
foo: {string: "Foo", type: "char", searchable: true},
bar: {string: "Bar", type: "boolean", searchable: true},
nice_datetime: {string: "Datetime", type: "datetime", searchable: true},
product_id: {string: "Product", type: "many2one", relation: "product", searchable: true},
},
records: [{
id: 1,
foo: "yop",
bar: true,
product_id: 37,
}, {
id: 2,
foo: "blip",
bar: true,
product_id: false,
}, {
id: 4,
foo: "abc",
bar: false,
product_id: 41,
}],
onchanges: {},
},
product: {
fields: {
name: {string: "Product Name", type: "char", searchable: true}
},
records: [{
id: 37,
display_name: "xphone",
}, {
id: 41,
display_name: "xpad",
}]
},
};
},
}, function () {
QUnit.test("creating a domain from scratch", async function (assert) {
assert.expect(12);
var $target = $("#qunit-fixture");
// Create the domain selector and its mock environment
var domainSelector = new DomainSelector(null, "partner", [], {
readonly: false,
debugMode: true,
});
await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data});
await domainSelector.appendTo($target);
// As we gave an empty domain, there should be a visible button to add
// the first domain part
var $domainAddFirstNodeButton = domainSelector.$(".o_domain_add_first_node_button:visible");
assert.strictEqual($domainAddFirstNodeButton.length, 1,
"there should be a button to create first domain element");
// Clicking on the button should add a visible field selector in the
// widget so that the user can change the field chain
await testUtils.dom.click($domainAddFirstNodeButton);
var $fieldSelector = domainSelector.$(".o_field_selector:visible");
assert.strictEqual($fieldSelector.length, 1,
"there should be a field selector");
// Focusing the field selector input should open a field selector popover
$fieldSelector.trigger('focusin');
await testUtils.nextTick();
var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover:visible");
assert.strictEqual($fieldSelectorPopover.length, 1,
"field selector popover should be visible");
// The field selector popover should contain the list of "partner"
// fields. "Bar" should be among them.
var $lis = $fieldSelectorPopover.find("li");
var $barLi = $();
$lis.each(function () {
var $li = $(this);
if ($li.html().indexOf("Bar") >= 0) {
$barLi = $li;
}
});
assert.strictEqual($barLi.length, 1,
"field selector popover should contain the 'Bar' field");
// Clicking the "Bar" field should change the internal domain and this
// should be displayed in the debug textarea
await testUtils.dom.click($barLi);
assert.containsOnce(domainSelector, "textarea.o_domain_debug_input");
assert.strictEqual(
domainSelector.$(".o_domain_debug_input").val(),
'[["bar","=",True]]',
"the domain input should contain a domain with 'bar'"
);
// There should be a "+" button to add a domain part; clicking on it
// should add the default "['id', '=', 1]" domain
var $plus = domainSelector.$(".fa-plus-circle");
assert.strictEqual($plus.length, 1, "there should be a '+' button");
await testUtils.dom.click($plus);
assert.strictEqual(
domainSelector.$(".o_domain_debug_input").val(),
'["&",["bar","=",True],["id","=",1]]',
"the domain input should contain a domain with 'bar' and 'id'");
// There should be two "..." buttons to add a domain group; clicking on
// the first one, should add this group with defaults "['id', '=', 1]"
// domains and the "|" operator
var $dots = domainSelector.$(".fa-ellipsis-h");
assert.strictEqual($dots.length, 2, "there should be two '...' buttons");
await testUtils.dom.click($dots.first());
assert.strictEqual(
domainSelector.$(".o_domain_debug_input").val(),
'["&","&",["bar","=",True],"|",["id","=",1],["id","=",1],["id","=",1]]',
"the domain input should contain a domain with 'bar', 'id' and a subgroup"
);
// There should be five "-" buttons to remove domain part; clicking on
// the two last ones, should leave a domain with only the "bar" and
// "foo" fields, with the initial "&" operator
var $minus = domainSelector.$(".o_domain_delete_node_button");
assert.strictEqual($minus.length, 5, "there should be five 'x' buttons");
await testUtils.dom.click($minus.last());
await testUtils.dom.click(domainSelector.$(".o_domain_delete_node_button").last());
assert.strictEqual(
domainSelector.$(".o_domain_debug_input").val(),
'["&",["bar","=",True],["id","=",1]]',
"the domain input should contain a domain with 'bar' and 'id'"
);
domainSelector.destroy();
});
QUnit.test("building a domain with a datetime", async function (assert) {
assert.expect(2);
var $target = $("#qunit-fixture");
// Create the domain selector and its mock environment
var domainSelector = new DomainSelector(null, "partner", [["nice_datetime", "=", "2017-03-27 15:42:00"]], {
readonly: false,
});
await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data});
await domainSelector.appendTo($target);
// Check that there is a datepicker to choose the date
var $datepicker = domainSelector.$(".o_datepicker:visible");
assert.strictEqual($datepicker.length, 1,
"there should be a datepicker");
var val = $datepicker.find('input').val();
await testUtils.dom.openDatepicker($datepicker);
await testUtils.dom.clickFirst($('.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]'));
assert.notEqual(domainSelector.$(".o_datepicker:visible input").val(), val,
"datepicker value should have changed");
await testUtils.dom.click($('.bootstrap-datetimepicker-widget a[data-action=close]'));
domainSelector.destroy();
});
QUnit.test("building a domain with a m2o without following the relation", async function (assert) {
assert.expect(1);
var $target = $("#qunit-fixture");
// Create the domain selector and its mock environment
var domainSelector = new DomainSelector(null, "partner", [["product_id", "ilike", 1]], {
debugMode: true,
readonly: false,
});
await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data});
await domainSelector.appendTo($target);
await testUtils.fields.editAndTrigger(domainSelector.$('.o_domain_leaf_value_input'),
'pad', ['input', 'change']);
assert.strictEqual(domainSelector.$('.o_domain_debug_input').val(), '[["product_id","ilike","pad"]]',
"string should have been allowed as m2o value");
domainSelector.destroy();
});
QUnit.test("editing a domain with `parent` key", async function (assert) {
assert.expect(1);
var $target = $("#qunit-fixture");
// Create the domain selector and its mock environment
var domainSelector = new DomainSelector(null, "product", "[['name','=',parent.foo]]", {
debugMode: true,
readonly: false,
});
await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data});
await domainSelector.appendTo($target);
assert.strictEqual(domainSelector.$el.text(), "This domain is not supported.",
"an error message should be displayed because of the `parent` key");
domainSelector.destroy();
});
QUnit.test("creating a domain with a default option", async function (assert) {
assert.expect(1);
var $target = $("#qunit-fixture");
// Create the domain selector and its mock environment
var domainSelector = new DomainSelector(null, "partner", [], {
readonly: false,
debugMode: true,
default: [["foo","=","kikou"]],
});
await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data});
await domainSelector.appendTo($target);
// Clicking on the button should add a visible field selector in the
// widget so that the user can change the field chain
await testUtils.dom.click(domainSelector.$(".o_domain_add_first_node_button:visible"));
assert.strictEqual(
domainSelector.$(".o_domain_debug_input").val(),
'[["foo","=","kikou"]]',
"the domain input should contain the default domain");
domainSelector.destroy();
});
QUnit.test("inline domain editor in modal", async function (assert) {
registry.category("views").remove("form"); // remove new form from registry
legacyViewRegistry.add("form", FormView); // add legacy form -> will be wrapped and added to new registry
assert.expect(1);
const serverData = {
actions: {
5: {
id: 5,
name: "Partner Form",
res_model: "partner",
target: "new",
type: "ir.actions.act_window",
views: [["view_ref", "form"]],
},
},
models: this.data,
views: {
"partner,view_ref,form": `
<form>
<field name="foo" string="Domain" widget="domain" options="{'model': 'partner'}"/>
</form>
`,
},
};
const webClient = await createWebClient({ serverData });
await doAction(webClient, 5);
assert.strictEqual(document.querySelector('div[name="foo"]').closest('.modal-body').style.overflow,
'visible', "modal should have visible overflow if there is inline domain field widget");
});
QUnit.test("edit a domain with the debug textarea", async function (assert) {
assert.expect(5);
const $target = $("#qunit-fixture");
let newValue;
// Create the domain selector and its mock environment
const Parent = Widget.extend({
custom_events: {
domain_changed: (e) => {
assert.deepEqual(e.data.domain, newValue);
assert.ok(e.data.debug);
},
},
});
const parent = new Parent(null);
const domainSelector = new DomainSelector(parent, "partner", [["product_id", "ilike", 1]], {
debugMode: true,
readonly: false,
});
await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data});
await domainSelector.appendTo($target);
assert.containsOnce(domainSelector, ".o_domain_node", "should have a single domain node");
newValue = `
[
['product_id', 'ilike', 1],
['id', '=', 0]
]`;
await testUtils.fields.editAndTrigger(domainSelector.$('.o_domain_debug_input'), newValue, ["change"]);
assert.strictEqual(domainSelector.$('.o_domain_debug_input').val(), newValue,
"the domain should not have been formatted");
assert.containsOnce(domainSelector, ".o_domain_node", "should still have a single domain node");
domainSelector.destroy();
});
});
});
});

View file

@ -0,0 +1,325 @@
odoo.define('web.model_field_selector_tests', function (require) {
"use strict";
var ModelFieldSelector = require("web.ModelFieldSelector");
var testUtils = require("web.test_utils");
QUnit.module('widgets', {}, function () {
QUnit.module('ModelFieldSelector', {
beforeEach: function () {
this.data = {
partner: {
fields: {
foo: {string: "Foo", type: "char", searchable: true},
bar: {string: "Bar", type: "boolean", searchable: true},
product_id: {string: "Product", type: "many2one", relation: "product", searchable: true},
},
records: [{
id: 1,
foo: "yop",
bar: true,
product_id: 37,
}, {
id: 2,
foo: "blip",
bar: true,
product_id: false,
}, {
id: 4,
foo: "abc",
bar: false,
product_id: 41,
}],
onchanges: {},
},
product: {
fields: {
name: {string: "Product Name", type: "char", searchable: true}
},
records: [{
id: 37,
display_name: "xphone",
}, {
id: 41,
display_name: "xpad",
}]
},
};
},
}, function () {
QUnit.test("creating a field chain from scratch", async function (assert) {
assert.expect(14);
var $target = $("#qunit-fixture");
// Create the field selector and its mock environment
var fieldSelector = new ModelFieldSelector(null, "partner", [], {
readonly: false,
debugMode: true,
});
await testUtils.mock.addMockEnvironment(fieldSelector.popover, {data: this.data});
await fieldSelector.appendTo($target);
var $value = fieldSelector.$("> .o_field_selector_value");
// Focusing the field selector input should open a field selector popover
fieldSelector.$el.trigger('focusin');
var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible");
assert.strictEqual($fieldSelectorPopover.length, 1,
"field selector popover should be visible");
// The field selector popover should contain the list of "partner"
// fields. "Bar" should be among them.
var $lis = $fieldSelectorPopover.find("li");
var $barLi = $();
$lis.each(function () {
var $li = $(this);
if ($li.html().indexOf("Bar") >= 0) {
$barLi = $li;
}
});
assert.strictEqual($barLi.length, 1,
"field selector popover should contain the 'Bar' field");
// Clicking the "Bar" field should close the popover and set the field
// chain to "bar" as it is a basic field
await testUtils.dom.click($barLi);
assert.notOk($fieldSelectorPopover.is("visible"),
"field selector popover should be closed now");
assert.strictEqual(getValueFromDOM($value), "Bar",
"field selector value should be displayed with a 'Bar' tag");
assert.deepEqual(fieldSelector.getSelectedField(), {
model: "partner",
name: "bar",
searchable: true,
string: "Bar",
type: "boolean",
}, "the selected field should be correctly set");
// Focusing the input again should open the same popover
fieldSelector.$el.trigger('focusin');
await testUtils.nextTick();
assert.ok($fieldSelectorPopover.is(":visible"),
"field selector popover should be visible");
// The field selector popover should contain the list of "partner"
// fields. "Product" should be among them.
$lis = $fieldSelectorPopover.find("li");
var $productLi = $();
$lis.each(function () {
var $li = $(this);
if ($li.html().indexOf("Product") >= 0) {
$productLi = $li;
}
});
assert.strictEqual($productLi.length, 1,
"field selector popover should contain the 'Product' field");
// Clicking on the "Product" field should update the popover to show
// the product fields (so only "Product Name" should be there)
await testUtils.dom.click($productLi);
$lis = $fieldSelectorPopover.find("li");
assert.strictEqual($lis.length, 1,
"there should be only one field proposition for 'product' model");
assert.ok($lis.first().html().indexOf("Product Name") >= 0,
"the name of the only suggestion should be 'Product Name'");
// Clicking on "Product Name" should close the popover and set the chain
// to "product_id.name"
await testUtils.dom.click($lis.first());
assert.notOk($fieldSelectorPopover.is("visible"),
"field selector popover should be closed now");
assert.strictEqual(getValueFromDOM($value), "Product -> Product Name",
"field selector value should be displayed with two tags: 'Product' and 'Product Name'");
// Remove the current selection and recreate it again
fieldSelector.$el.trigger('focusin');
await testUtils.nextTick();
await testUtils.dom.click(fieldSelector.$('.o_field_selector_prev_page'));
await testUtils.dom.click(fieldSelector.$('.o_field_selector_close'));
fieldSelector.$el.trigger('focusin');
await testUtils.nextTick();
$fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible");
$lis = $fieldSelectorPopover.find("li");
$productLi = $();
$lis.each(function () {
var $li = $(this);
if ($li.html().indexOf("Product") >= 0) {
$productLi = $li;
}
});
assert.strictEqual($productLi.length, 1,
"field selector popover should contain the 'Product' field");
await testUtils.dom.click($productLi);
$lis = $fieldSelectorPopover.find("li");
await testUtils.dom.click($lis.first());
assert.notOk($fieldSelectorPopover.is("visible"),
"field selector popover should be closed now");
assert.strictEqual(getValueFromDOM($value), "Product -> Product Name",
"field selector value should be displayed with two tags: 'Product' and 'Product Name'");
fieldSelector.destroy();
function getValueFromDOM($dom) {
return _.map($dom.find(".o_field_selector_chain_part"), function (part) {
return $(part).text().trim();
}).join(" -> ");
}
});
QUnit.test("default field chain should set the page data correctly", async function (assert) {
assert.expect(3);
var $target = $("#qunit-fixture");
// Create the field selector and its mock environment
// passing 'product_id' as a prefilled field-chain
var fieldSelector = new ModelFieldSelector(null, "partner", ['product_id'], {
readonly: false,
debugMode: true,
});
await testUtils.mock.addMockEnvironment(fieldSelector.popover, {data: this.data});
await fieldSelector.appendTo($target);
// Focusing the field selector input should open a field selector popover
fieldSelector.$el.trigger('focusin');
var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible");
assert.strictEqual($fieldSelectorPopover.length, 1,
"field selector popover should be visible");
// The field selector popover should contain the list of "product"
// fields. "Product Name" should be among them.
var $lis = $fieldSelectorPopover.find("li");
assert.strictEqual($lis.length, 1,
"there should be only one field proposition for 'product' model");
assert.ok($lis.first().html().indexOf("Product Name") >= 0,
"the name of the only suggestion should be 'Product Name'");
fieldSelector.destroy();
});
QUnit.test("use the filter option", async function (assert) {
assert.expect(2);
var $target = $("#qunit-fixture");
// Create the field selector and its mock environment
var fieldSelector = new ModelFieldSelector(null, "partner", [], {
readonly: false,
filter: function (field) {
return field.type === 'many2one';
},
});
await testUtils.mock.addMockEnvironment(fieldSelector.popover, {data: this.data});
await fieldSelector.appendTo($target);
fieldSelector.$el.trigger('focusin');
await testUtils.nextTick();
var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible");
var $lis = $fieldSelectorPopover.find("li");
assert.strictEqual($lis.length, 1, "there should only be one element");
assert.strictEqual($lis.text().trim(), "Product", "the available field should be the many2one");
fieldSelector.destroy();
});
QUnit.test("default `showSearchInput` option", async function (assert) {
assert.expect(6);
var $target = $("#qunit-fixture");
// Create the field selector and its mock environment
var fieldSelector = new ModelFieldSelector(null, "partner", [], {
readonly: false,
});
await testUtils.mock.addMockEnvironment(fieldSelector.popover, {data: this.data});
await fieldSelector.appendTo($target);
fieldSelector.$el.trigger('focusin');
await testUtils.nextTick();
var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible");
var $searchInput = $fieldSelectorPopover.find(".o_field_selector_search input");
assert.strictEqual($searchInput.length, 1, "there should be a search input");
// without search
assert.strictEqual($fieldSelectorPopover.find("li").length, 3, "there should be three available fields");
assert.strictEqual($fieldSelectorPopover.find("li").text().trim().replace(/\s+/g, ' '), "Bar Foo Product", "the available field should be correct");
await testUtils.fields.editAndTrigger($searchInput, 'xx', 'keyup');
assert.strictEqual($fieldSelectorPopover.find("li").length, 0, "there shouldn't be any element");
await testUtils.fields.editAndTrigger($searchInput, 'Pro', 'keyup');
assert.strictEqual($fieldSelectorPopover.find("li").length, 1, "there should only be one element");
assert.strictEqual($fieldSelectorPopover.find("li").text().trim().replace(/\s+/g, ' '), "Product", "the available field should be the Product");
fieldSelector.destroy();
});
QUnit.test("false `showSearchInput` option", async function (assert) {
assert.expect(1);
var $target = $("#qunit-fixture");
// Create the field selector and its mock environment
var fieldSelector = new ModelFieldSelector(null, "partner", [], {
readonly: false,
showSearchInput: false,
});
await testUtils.mock.addMockEnvironment(fieldSelector.popover, { data: this.data });
await fieldSelector.appendTo($target);
fieldSelector.$el.trigger('focusin');
await testUtils.nextTick();
var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible");
var $searchInput = $fieldSelectorPopover.find(".o_field_selector_search input");
assert.strictEqual($searchInput.length, 0, "there should be no search input");
fieldSelector.destroy();
});
QUnit.test("create a field chain with value 1 i.e. TRUE_LEAF", async function (assert) {
assert.expect(1);
var $target = $("#qunit-fixture");
//create the field selector with domain value ["1"]
var fieldSelector = new ModelFieldSelector(null, "partner", ["1"], {
readonly: false,
showSearchInput: false,
});
await testUtils.mock.addMockEnvironment(fieldSelector.popover, {data: this.data});
await fieldSelector.appendTo($target);
var $fieldName = fieldSelector.$('.o_field_selector_chain_part');
assert.strictEqual($fieldName.text().trim(), "1",
"field name value should be 1.");
fieldSelector.destroy();
});
QUnit.test("create a field chain with value 0 i.e. FALSE_LEAF", async function (assert) {
assert.expect(1);
var $target = $("#qunit-fixture");
//create the field selector with domain value ["0"]
var fieldSelector = new ModelFieldSelector(null, "partner", ["0"], {
readonly: false,
showSearchInput: false,
});
await testUtils.mock.addMockEnvironment(fieldSelector.popover, {data: this.data});
await fieldSelector.appendTo($target);
var $fieldName = fieldSelector.$('.o_field_selector_chain_part');
assert.strictEqual($fieldName.text().trim(), "0",
"field name value should be 0.");
fieldSelector.destroy();
});
});
});
});

View file

@ -0,0 +1,80 @@
odoo.define('web.name_and_signature_tests', function (require) {
"use strict";
const { NameAndSignature } = require("web.name_and_signature");
const testUtils = require("web.test_utils");
const MockedNameAndSignature = NameAndSignature.extend({
events: {
...NameAndSignature.prototype.events,
'signature_changed': () => {},
},
_onChangeSignature: () => {},
_drawCurrentName: () => {},
});
async function MockedNameAndSignatureGenerator (options) {
const parent = $("#qunit-fixture");
const mockedNameAndSignature = new MockedNameAndSignature(parent, options);
await testUtils.mock.addMockEnvironment(mockedNameAndSignature, {
mockRPC: function (route, args) {
if (route == "/web/sign/get_fonts/") {
return Promise.resolve();
}
}
});
await mockedNameAndSignature.appendTo(parent);
await mockedNameAndSignature.resetSignature();
return mockedNameAndSignature;
}
QUnit.module('widgets legacy', {}, function () {
QUnit.module('name_and_signature', {
beforeEach: function () {
this.defaultName = 'Don Toliver'
},
}, function () {
QUnit.test("test name_and_signature widget", async function (assert) {
assert.expect(5);
const nameAndSignature = await MockedNameAndSignatureGenerator({
defaultName: this.defaultName
});
assert.equal(nameAndSignature.signMode, 'auto');
const nameInput = nameAndSignature.$el.find('.o_web_sign_name_input');
assert.ok(nameInput.length);
assert.equal(this.defaultName, nameInput.val());
const drawButton = nameAndSignature.$el.find('.o_web_sign_draw_button');
assert.ok(drawButton.length);
await drawButton.click();
assert.equal(nameAndSignature.signMode, 'draw');
});
QUnit.test("test name_and_signature widget without name", async function (assert) {
assert.expect(4);
const nameAndSignature = await MockedNameAndSignatureGenerator({});
assert.equal(nameAndSignature.signMode, 'auto');
assert.ok(nameAndSignature.signatureAreaHidden);
const nameInput = nameAndSignature.$el.find('.o_web_sign_name_input');
assert.ok(nameInput.length);
await nameInput.val(this.defaultName).trigger('input');
assert.notOk(nameAndSignature.signatureAreaHidden);
});
QUnit.test("test name_and_signature widget with noInputName and default name", async function (assert) {
assert.expect(1);
const nameAndSignature = await MockedNameAndSignatureGenerator({
noInputName: true,
defaultName: this.defaultName
});
assert.equal(nameAndSignature.signMode, 'auto');
});
QUnit.test("test name_and_signature widget with noInputName", async function (assert) {
assert.expect(1);
const nameAndSignature = await MockedNameAndSignatureGenerator({
noInputName: true,
});
assert.equal(nameAndSignature.signMode, 'draw');
});
});
});
});

View file

@ -0,0 +1,150 @@
odoo.define('web.week_days_tests', function (require) {
"use strict";
const FormView = require('web.FormView');
const testUtils = require('web.test_utils');
QUnit.module('WeeklyRecurrence', {
beforeEach() {
this.data = {
partner: {
fields: {
id: {strin: "id", type:"integer"},
mon: {string: "Mon", type: "boolean"},
tue: {string: "Tue", type: "boolean"},
wed: {string: "Wed", type: "boolean"},
thu: {string: "Thu", type: "boolean"},
fri: {string: "Fri", type: "boolean"},
sat: {string: "Sat", type: "boolean"},
sun: {string: "Sun", type: "boolean"},
},
records: [
{
id: 1,
mon: false,
tue: false,
wed: false,
thu: false,
fri: false,
sat: false,
sun: false,
},
],
},
};
},
}, function () {
QUnit.module('WeeklyRecurrenceWidget');
QUnit.test('simple week recurrence widget', async function (assert) {
assert.expect(14);
let writeCall = 0;
const form = await testUtils.createView({
View: FormView,
model: 'partner',
data: this.data,
res_id: 1,
arch:
`<form string="Partners">
<sheet>
<group>
<widget name="week_days"/>
</group>
</sheet>
</form>`,
mockRPC: function (route, args) {
if (args.method === 'write') {
writeCall++;
if (writeCall === 1) {
assert.ok(args.args[1].sun,
"value of sunday should be true");
this.data.partner.records[0].sun = args.args[1].sun;
}
if (writeCall === 2) {
assert.notOk(args.args[1].sun,
"value of sunday should be false");
assert.ok(args.args[1].mon,
"value of monday should be true");
assert.ok(args.args[1].tue,
"value of tuesday should be true");
this.data.partner.records[0].sun = args.args[1].sun;
this.data.partner.records[0].mon = args.args[1].mon;
this.data.partner.records[0].tue = args.args[1].tue;
}
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
assert.containsN(form, 'input:disabled', 7,
"all inputs should be disabled in readonly mode");
const labelsTexts = [...form.el.querySelectorAll('.o_recurrent_weekday_label')].map(el => el.innerText.trim());
assert.deepEqual(labelsTexts, ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
"labels should be short week names");
await testUtils.form.clickEdit(form);
assert.containsNone(form, 'input:disabled', 7,
"all inputs should be enabled in readonly mode");
await testUtils.dom.click(form.el.querySelector('input[id^="sun"]'));
assert.ok(form.el.querySelector('input[id^="sun"]').checked,
"sunday checkbox should be checked");
await testUtils.form.clickSave(form);
await testUtils.form.clickEdit(form);
await testUtils.dom.click(form.el.querySelector('input[id^="mon"]'));
assert.ok(form.el.querySelector('input[id^="mon"]').checked,
"monday checkbox should be checked");
await testUtils.dom.click(form.el.querySelector('input[id^="tue"]'));
assert.ok(form.el.querySelector('input[id^="tue"]').checked,
"tuesday checkbox should be checked");
// uncheck Sunday checkbox and check write call
await testUtils.dom.click(form.el.querySelector('input[id^="sun"]'));
assert.notOk(form.el.querySelector('input[id^="sun"]').checked,
"sunday checkbox should be unchecked");
await testUtils.form.clickSave(form);
assert.notOk(form.el.querySelector('input[id^="sun"]').checked,
"sunday checkbox should be unchecked");
assert.ok(form.el.querySelector('input[id^="mon"]').checked,
"monday checkbox should be checked");
assert.ok(form.el.querySelector('input[id^="tue"]').checked,
"tuesday checkbox should be checked");
form.destroy();
});
QUnit.test('week recurrence widget show week start as per language configuration', async function (assert) {
assert.expect(1);
const form = await testUtils.createView({
View: FormView,
model: 'partner',
data: this.data,
res_id: 1,
arch:
`<form string="Partners">
<sheet>
<group>
<widget name="week_days"/>
</group>
</sheet>
</form>`,
translateParameters: {
week_start: 5,
},
});
const labelsTexts = [...form.el.querySelectorAll('.o_recurrent_weekday_label')].map(el => el.innerText.trim());
assert.deepEqual(labelsTexts, ['Fri', 'Sat', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu'],
"labels should be short week names");
form.destroy();
});
});
});