合并企业版代码(未测试,先提交到测试分支)

This commit is contained in:
qihao.gong@jikimo.com
2023-04-14 17:42:23 +08:00
parent 7a7b3d7126
commit d28525526a
1300 changed files with 513579 additions and 5426 deletions

View File

@@ -0,0 +1,73 @@
odoo.define('web_enterprise.BasicRenderMobileTests', function (require) {
"use strict";
const BasicRenderer = require('web.BasicRenderer');
const FormView = require('web.FormView');
const testUtils = require('web.test_utils');
const createView = testUtils.createView;
QUnit.module('web_enterprise > basic > basic_render_mobile', {
beforeEach: function () {
this.data = {
partner: {
fields: {
display_name: {string: "Displayed name", type: "char", help: 'The name displayed'},
},
records: [
{
id: 1,
display_name: "first record",
},
],
},
};
}
}, function () {
QUnit.module('Basic Render Mobile');
QUnit.test(`field tooltip shouldn't remain displayed in mobile`, async function (assert) {
assert.expect(2);
testUtils.mock.patch(BasicRenderer, {
SHOW_AFTER_DELAY: 0,
_getTooltipOptions: function () {
return Object.assign({}, this._super(...arguments), {
animation: false,
});
},
});
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<sheet>
<group>
<field name="display_name"/>
</group>
</sheet>
</form>
`,
});
const label = form.el.querySelector('label.o_form_label');
await testUtils.dom.triggerEvent(label, 'touchstart');
assert.strictEqual(
document.querySelectorAll('.tooltip .oe_tooltip_string').length,
1, "should have a tooltip displayed"
);
await testUtils.dom.triggerEvent(label, 'touchend');
assert.strictEqual(
document.querySelectorAll('.tooltip .oe_tooltip_string').length,
0, "shouldn't have a tooltip displayed"
);
form.destroy();
testUtils.mock.unpatch(BasicRenderer);
});
});
});

View File

@@ -0,0 +1,216 @@
odoo.define('web_enterprise.kanban_mobile_tests', function (require) {
"use strict";
const KanbanView = require('web.KanbanView');
const { createView, dom} = require('web.test_utils');
QUnit.module('LegacyViews', {
beforeEach() {
this.data = {
partner: {
fields: {
foo: {string: "Foo", type: "char"},
bar: {string: "Bar", type: "boolean"},
int_field: {string: "int_field", type: "integer", sortable: true},
qux: {string: "my float", type: "float"},
product_id: {string: "something_id", type: "many2one", relation: "product"},
category_ids: { string: "categories", type: "many2many", relation: 'category'},
state: { string: "State", type: "selection", selection: [["abc", "ABC"], ["def", "DEF"], ["ghi", "GHI"]]},
date: {string: "Date Field", type: 'date'},
datetime: {string: "Datetime Field", type: 'datetime'},
},
records: [
{id: 1, bar: true, foo: "yop", int_field: 10, qux: 0.4, product_id: 3, state: "abc", category_ids: []},
{id: 2, bar: true, foo: "blip", int_field: 9, qux: 13, product_id: 5, state: "def", category_ids: [6]},
{id: 3, bar: true, foo: "gnap", int_field: 17, qux: -3, product_id: 3, state: "ghi", category_ids: [7]},
{id: 4, bar: false, foo: "blip", int_field: -4, qux: 9, product_id: 5, state: "ghi", category_ids: []},
{id: 5, bar: false, foo: "Hello \"World\"! #peace_n'_love", int_field: -9, qux: 10, state: "jkl", category_ids: []},
]
},
product: {
fields: {
id: {string: "ID", type: "integer"},
name: {string: "Display Name", type: "char"},
},
records: [
{id: 3, name: "hello"},
{id: 5, name: "xmo"},
]
},
category: {
fields: {
name: {string: "Category Name", type: "char"},
color: {string: "Color index", type: "integer"},
},
records: [
{id: 6, name: "gold", color: 2},
{id: 7, name: "silver", color: 5},
]
},
};
},
}, function () {
QUnit.module("KanbanView (legacy) - Mobile")
QUnit.test('kanban with searchpanel: rendering in mobile', async function (assert) {
assert.expect(34);
const kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: `
<kanban>
<templates><t t-name="kanban-box">
<div>
<field name="foo"/>
</div>
</t></templates>
</kanban>
`,
archs: {
'partner,false,search': `
<search>
<searchpanel>
<field name="product_id" expand="1" enable_counters="1"/>
<field name="state" expand="1" select="multi" enable_counters="1"/>
</searchpanel>
</search>
`,
},
mockRPC(route, {method}) {
if (method && method.includes('search_panel_')) {
assert.step(method);
}
return this._super.apply(this, arguments);
},
});
let $sp = kanban.$(".o_search_panel");
assert.containsOnce(kanban, ".o_search_panel.o_search_panel_summary");
assert.containsNone(document.body, "div.o_search_panel.o_searchview.o_mobile_search");
assert.verifySteps([
"search_panel_select_range",
"search_panel_select_multi_range",
]);
assert.containsOnce($sp, ".fa.fa-filter");
assert.containsOnce($sp, ".o_search_panel_current_selection:contains(All)");
// open the search panel
await dom.click($sp);
$sp = $(".o_search_panel");
assert.containsNone(kanban, ".o_search_panel.o_search_panel_summary");
assert.containsOnce(document.body, "div.o_search_panel.o_searchview.o_mobile_search");
assert.containsOnce($sp, ".o_mobile_search_header > button:contains(FILTER)");
assert.containsOnce($sp, "button.o_mobile_search_footer:contains(SEE RESULT)");
assert.containsN($sp, ".o_search_panel_section", 2);
assert.containsOnce($sp, ".o_search_panel_section.o_search_panel_category");
assert.containsOnce($sp, ".o_search_panel_section.o_search_panel_filter");
assert.containsN($sp, ".o_search_panel_category_value", 3);
assert.containsOnce($sp, ".o_search_panel_category_value > header.active", 3);
assert.containsN($sp, ".o_search_panel_filter_value", 3);
// select category
await dom.click($sp.find(".o_search_panel_category_value:contains(hello) header"));
assert.verifySteps([
"search_panel_select_range",
"search_panel_select_multi_range",
]);
// select filter
await dom.click($sp.find(".o_search_panel_filter_value:contains(DEF) input"));
assert.verifySteps([
"search_panel_select_range",
"search_panel_select_multi_range",
]);
// close with back button
await dom.click($sp.find(".o_mobile_search_header button"));
$sp = $(".o_search_panel");
assert.containsOnce(kanban, ".o_search_panel.o_search_panel_summary");
assert.containsNone(document.body, "div.o_search_panel.o_searchview.o_mobile_search");
// selection is kept when closed
assert.containsOnce($sp, ".o_search_panel_current_selection");
assert.containsOnce($sp, ".o_search_panel_category:contains(hello)");
assert.containsOnce($sp, ".o_search_panel_filter:contains(DEF)");
// open the search panel
await dom.click($sp);
$sp = $(".o_search_panel");
assert.containsOnce($sp, ".o_search_panel_category_value > header.active:contains(hello)");
assert.containsOnce($sp, ".o_search_panel_filter_value:contains(DEF) input:checked");
assert.containsNone(kanban, ".o_search_panel.o_search_panel_summary");
assert.containsOnce(document.body, "div.o_search_panel.o_searchview.o_mobile_search");
// close with bottom button
await dom.click($sp.find("button.o_mobile_search_footer"));
assert.containsOnce(kanban, ".o_search_panel.o_search_panel_summary");
assert.containsNone(document.body, "div.o_search_panel.o_searchview.o_mobile_search");
kanban.destroy();
});
QUnit.module('KanbanView Mobile');
QUnit.test('mobile no quick create column when grouping on non m2o field', async function (assert) {
assert.expect(2);
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test o_kanban_small_column" on_create="quick_create">' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'<div><field name="int_field"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['int_field'],
});
assert.containsNone(kanban, '.o_kanban_mobile_add_column', "should not have the add column button");
assert.containsNone(kanban.$('.o_column_quick_create'),
"should not have column quick create tab as we grouped records on integer field");
kanban.destroy();
});
QUnit.test("autofocus quick create form", async function (assert) {
assert.expect(2);
const kanban = await createView({
View: KanbanView,
model: "partner",
data: this.data,
arch: `<kanban on_create="quick_create">
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
groupBy: ["product_id"],
});
// quick create in first column
await dom.click(kanban.$buttons.find(".o-kanban-button-new"));
assert.ok(kanban.$(".o_kanban_group:nth(0) > div:nth(1)").hasClass("o_kanban_quick_create"),
"clicking on create should open the quick_create in the first column");
assert.strictEqual(document.activeElement, kanban.$(".o_kanban_quick_create .o_input:first")[0],
"the first input field should get the focus when the quick_create is opened");
kanban.destroy();
});
});
});

View File

@@ -0,0 +1,229 @@
odoo.define('web_enterprise.list_mobile_tests', function (require) {
"use strict";
const ListRenderer = require('web.ListRenderer');
const ListView = require('web.ListView');
const testUtils = require('web.test_utils');
const { createView, dom, mock } = testUtils;
QUnit.module("LegacyViews", {
beforeEach() {
this.data = {
foo: {
fields: {
foo: { string: "Foo", type: "char" },
bar: { string: "Bar", type: "boolean" },
},
records: [
{ id: 1, bar: true, foo: "yop" },
{ id: 2, bar: true, foo: "blip" },
{ id: 3, bar: true, foo: "gnap" },
{ id: 4, bar: false, foo: "blip" },
],
},
};
mock.patch(ListRenderer, {
init() {
this._super(...arguments);
this.LONG_TOUCH_THRESHOLD = 0;
}
});
},
afterEach() {
mock.unpatch(ListRenderer);
},
}, function () {
QUnit.module("ListView (legacy) - Mobile");
QUnit.test("selection is properly displayed (single page)", async function (assert) {
assert.expect(10);
const list = await createView({
touchScreen: true,
arch: `
<tree>
<field name="foo"/>
<field name="bar"/>
</tree>`,
data: this.data,
model: 'foo',
viewOptions: { hasActionMenus: true },
View: ListView,
});
assert.containsN(list, '.o_data_row', 4);
assert.containsNone(list, '.o_list_selection_box');
// select a record
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchend');
assert.containsOnce(list, '.o_list_selection_box');
assert.containsNone(list.$('.o_list_selection_box'), '.o_list_select_domain');
assert.ok(list.$('.o_list_selection_box').text().includes("1 selected"))
// unselect a record
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchend');
assert.containsNone(list.$('.o_list_selection_box'), '.o_list_select_domain');
// select 2 records
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchend');
await dom.triggerEvent(list.$('.o_data_row:eq(1)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(1)'), 'touchend');
assert.ok(list.$('.o_list_selection_box').text().includes("2 selected"))
assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus');
await testUtils.controlPanel.toggleActionMenu(list);
assert.deepEqual(testUtils.controlPanel.getMenuItemTexts(list), ['Delete'],
'action menu should contain the Delete action');
// unselect all
await dom.click(list.$('.o_discard_selection'));
await testUtils.nextTick();
assert.containsNone(list, '.o_list_selection_box');
list.destroy();
});
QUnit.test("selection box is properly displayed (multi pages)", async function (assert) {
assert.expect(13);
const list = await createView({
touchScreen: true,
arch: `
<tree limit="3">
<field name="foo"/>
<field name="bar"/>
</tree>`,
data: this.data,
model: 'foo',
View: ListView,
viewOptions: { hasActionMenus: true },
});
assert.containsN(list, '.o_data_row', 3);
assert.containsNone(list, '.o_list_selection_box');
// select a record
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchend');
assert.containsOnce(list, '.o_list_selection_box');
assert.containsNone(list.$('.o_list_selection_box'), '.o_list_select_domain');
assert.strictEqual(list.$('.o_list_selection_box').text().replace(/\s+/g, ' '),
" × 1 selected ");
assert.containsOnce(list, '.o_list_selection_box');
assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus');
await testUtils.controlPanel.toggleActionMenu(list);
assert.deepEqual(testUtils.controlPanel.getMenuItemTexts(list), ['Delete'],
'action menu should contain the Delete action');
// select all records of first page
await dom.triggerEvent(list.$('.o_data_row:eq(1)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(1)'), 'touchend');
await dom.triggerEvent(list.$('.o_data_row:eq(2)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(2)'), 'touchend');
assert.containsOnce(list, '.o_list_selection_box');
assert.containsOnce(list.$('.o_list_selection_box'), '.o_list_select_domain');
assert.strictEqual(list.$('.o_list_selection_box').text().replace(/\s+/g, ' ').trim(),
"× 3 selected Select all 4");
// select all domain
await dom.click(list.$('.o_list_selection_box .o_list_select_domain'));
assert.containsOnce(list, '.o_list_selection_box');
assert.strictEqual(list.$('.o_list_selection_box').text().replace(/\s+/g, ' ').trim(),
"× All 4 selected");
list.destroy();
});
QUnit.test("export button is properly hidden", async function (assert) {
assert.expect(2);
const list = await createView({
touchScreen: true,
arch: `
<tree>
<field name="foo"/>
<field name="bar"/>
</tree>`,
data: this.data,
model: 'foo',
View: ListView,
session: {
async user_has_group(group) {
if (group === 'base.group_allow_export') {
return true;
}
return this._super(...arguments);
},
},
});
assert.containsN(list, '.o_data_row', 4);
assert.isNotVisible(list.$buttons.find('.o_list_export_xlsx'));
list.destroy();
});
QUnit.test('editable readonly list view is disabled', async function (assert) {
assert.expect(1);
const list = await createView({
touchScreen: true,
arch: `
<tree>
<field name="foo"/>
</tree>`,
data: this.data,
model: 'foo',
View: ListView,
});
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchstart');
await dom.triggerEvent(list.$('.o_data_row:eq(0)'), 'touchend');
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
assert.containsNone(list, '.o_selected_row .o_field_widget[name=foo]',
"The listview should not contains an edit field");
list.destroy();
});
QUnit.test("add custom field button not shown in mobile (with opt. col.)", async function (assert) {
assert.expect(3);
const list = await testUtils.createView({
arch: `
<tree>
<field name="foo"/>
<field name="bar" optional="hide"/>
</tree>`,
data: this.data,
model: 'foo',
touchScreen: true,
View: ListView,
});
assert.containsOnce(list.$('table'), '.o_optional_columns_dropdown_toggle');
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
const $dropdown = list.$('div.o_optional_columns');
assert.containsOnce($dropdown, 'div.dropdown-item');
assert.containsNone($dropdown, 'button.dropdown-item-studio');
list.destroy();
});
QUnit.test("add custom field button not shown to non-system users (wo opt. col.)", async function (assert) {
assert.expect(1);
const list = await testUtils.createView({
arch: `
<tree>
<field name="foo"/>
<field name="bar"/>
</tree>`,
data: this.data,
model: 'foo',
touchScreen: true,
View: ListView,
});
assert.containsNone(list.$('table'), '.o_optional_columns_dropdown_toggle');
list.destroy();
});
});
});

View File

@@ -0,0 +1,259 @@
odoo.define('web_enterprise.list_tests', function (require) {
"use strict";
const AbstractStorageService = require('web.AbstractStorageService');
const ListRenderer = require('web.ListRenderer');
const ListView = require('web.ListView');
const RamStorage = require('web.RamStorage');
const testUtils = require('web.test_utils');
const { unblockUI } = require('web.framework');
const { patch, unpatch, UnknownPatchError } = require('web.utils');
const PromoteStudioDialog = require('web_enterprise.PromoteStudioDialog');
QUnit.module('web_enterprise', {
beforeEach: function () {
this.data = {
foo: {
fields: {
foo: {string: "Foo", type: "char"},
bar: {string: "Bar", type: "boolean"},
},
records: [
{id: 1, bar: true, foo: "yop"},
{id: 2, bar: true, foo: "blip"},
{id: 3, bar: true, foo: "gnap"},
{id: 4, bar: false, foo: "blip"},
]
}
};
this.RamStorageService = AbstractStorageService.extend({
storage: new RamStorage(),
});
}
}, function () {
QUnit.module('ListView (Legacy)');
QUnit.test("add custom field button with other optional columns - studio not installed", async function (assert) {
assert.expect(11);
let listPatch;
try {
listPatch = unpatch(ListRenderer.prototype, 'web_studio.ListRenderer');
} catch (e) {
if (!(e instanceof UnknownPatchError)) {
throw e;
}
}
testUtils.mock.patch(PromoteStudioDialog, {
_reloadPage: function () {
assert.step('window_reload');
unblockUI(); // the UI is normally unblocked by the reload
}
});
const list = await testUtils.createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree>
<field name="foo"/>
<field name="bar" optional="hide"/>
</tree>`,
session: {
is_system: true
},
action: {
xml_id: "action_43",
},
mockRPC: function (route, args) {
if (args.method === 'search_read' && args.model === 'ir.module.module') {
assert.step('studio_module_id');
return Promise.resolve([{id: 42}]);
}
if (args.method === 'button_immediate_install' && args.model === 'ir.module.module') {
assert.deepEqual(args.args[0], [42], "Should be the id of studio module returned by the search read");
assert.step('studio_module_install');
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
services: {
local_storage: this.RamStorageService,
},
});
assert.ok(list.$('.o_data_row').length > 0);
assert.containsOnce(list.$('table'), '.o_optional_columns_dropdown_toggle');
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
const $dropdown = list.$('div.o_optional_columns');
assert.containsOnce($dropdown, 'div.dropdown-item');
assert.containsOnce($dropdown, 'button.dropdown-item-studio');
await testUtils.dom.click(list.$('div.o_optional_columns button.dropdown-item-studio'));
await testUtils.nextTick();
assert.containsOnce(document.body, '.modal-studio');
await testUtils.dom.click($('.modal-studio .o_install_studio'));
assert.equal(window.localStorage.openStudioOnReload, 'main');
assert.verifySteps(['studio_module_id', 'studio_module_install', 'window_reload']);
// wash localStorage
window.localStorage.openStudioOnReload = false;
testUtils.mock.unpatch(PromoteStudioDialog);
if (listPatch) {
patch(ListRenderer.prototype, 'web_studio.ListRenderer', listPatch);
}
list.destroy();
});
QUnit.test("add custom field button without other optional columns - studio not installed", async function (assert) {
assert.expect(11);
let listPatch;
try {
listPatch = unpatch(ListRenderer.prototype, 'web_studio.ListRenderer');
} catch (e) {
if (!(e instanceof UnknownPatchError)) {
throw e;
}
}
testUtils.mock.patch(PromoteStudioDialog, {
_reloadPage: function () {
assert.step('window_reload');
unblockUI(); // the UI is normally unblocked by the reload
}
});
const list = await testUtils.createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree>
<field name="foo"/>
<field name="bar"/>
</tree>`,
session: {
is_system: true
},
action: {
xml_id: "action_43",
},
mockRPC: function (route, args) {
if (args.method === 'search_read' && args.model === 'ir.module.module') {
assert.step('studio_module_id');
return Promise.resolve([{id: 42}]);
}
if (args.method === 'button_immediate_install' && args.model === 'ir.module.module') {
assert.deepEqual(args.args[0], [42], "Should be the id of studio module returned by the search read");
assert.step('studio_module_install');
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
services: {
local_storage: this.RamStorageService,
},
});
assert.ok(list.$('.o_data_row').length > 0);
assert.containsOnce(list.$('table'), '.o_optional_columns_dropdown_toggle');
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
const $dropdown = list.$('div.o_optional_columns');
assert.containsNone($dropdown, 'div.dropdown-item');
assert.containsOnce($dropdown, 'button.dropdown-item-studio');
await testUtils.dom.click(list.$('div.o_optional_columns button.dropdown-item-studio'));
await testUtils.nextTick();
assert.containsOnce(document.body, '.modal-studio');
await testUtils.dom.click($('.modal-studio .o_install_studio'));
assert.equal(window.localStorage.openStudioOnReload, 'main');
assert.verifySteps(['studio_module_id', 'studio_module_install', 'window_reload']);
// wash localStorage
window.localStorage.openStudioOnReload = false;
if (listPatch) {
patch(ListRenderer.prototype, 'web_studio.ListRenderer', listPatch);
}
testUtils.mock.unpatch(PromoteStudioDialog);
list.destroy();
});
QUnit.test("add custom field button not shown to non-system users (with opt. col.)", async function (assert) {
assert.expect(3);
const list = await testUtils.createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree>
<field name="foo"/>
<field name="bar" optional="hide"/>
</tree>`,
session: {
is_system: false
},
action: {
xml_id: "action_43",
},
});
assert.containsOnce(list.$('table'), '.o_optional_columns_dropdown_toggle');
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
const $dropdown = list.$('div.o_optional_columns');
assert.containsOnce($dropdown, 'div.dropdown-item');
assert.containsNone($dropdown, 'button.dropdown-item-studio');
list.destroy();
});
QUnit.test("add custom field button not shown to non-system users (wo opt. col.)", async function (assert) {
assert.expect(1);
const list = await testUtils.createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree>
<field name="foo"/>
<field name="bar"/>
</tree>`,
session: {
is_system: false
},
action: {
xml_id: "action_43",
},
});
assert.containsNone(list.$('table'), '.o_optional_columns_dropdown_toggle');
list.destroy();
});
QUnit.test("add custom field button not shown with invalid action", async function (assert) {
assert.expect(1);
const list = await testUtils.createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree>
<field name="foo"/>
<field name="bar"/>
</tree>`,
session: {
is_system: true
},
action: {
xml_id: null,
},
});
assert.containsNone(list.$('div.o_optional_columns'), 'button.dropdown-item-studio');
list.destroy();
});
});
});