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

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,128 @@
/** @odoo-module **/
import { createWebClient, doAction, getActionManagerServerData, loadState } from "@web/../tests/webclient/helpers";
import { click, getFixture, legacyExtraNextTick } from "@web/../tests/helpers/utils";
let serverData;
let target;
QUnit.module('ActionManager', {
beforeEach() {
serverData = getActionManagerServerData();
target = getFixture();
Object.assign(serverData, {
actions: {
1: {
id: 1,
name: 'Partners Action 1',
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'list'], [false, 'kanban'], [false, 'form']],
},
2: {
id: 2,
name: 'Partners Action 2',
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'list'], [false, 'form']],
},
},
views: {
'partner,false,kanban': `
<kanban>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<field name="foo"/>
</div>
</t>
</templates>
</kanban>`,
'partner,false,list': '<tree><field name="foo"/></tree>',
'partner,false,form':
`<form>
<group>
<field name="display_name"/>
</group>
</form>`,
'partner,false,search': '<search><field name="foo" string="Foo"/></search>',
},
models: {
partner: {
fields: {
foo: { string: "Foo", type: "char" },
},
records: [
{ id: 1, display_name: "First record", foo: "yop" },
],
},
},
});
},
});
QUnit.test('uses a mobile-friendly view by default (if possible)', async function (assert) {
const webClient = await createWebClient({ serverData });
// should default on a mobile-friendly view (kanban) for action 1
await doAction(webClient, 1);
assert.containsNone(target, '.o_list_view');
assert.containsOnce(target, '.o_kanban_view');
// there is no mobile-friendly view for action 2, should use the first one (list)
await doAction(webClient, 2);
assert.containsOnce(target, '.o_list_view');
assert.containsNone(target, '.o_kanban_view');
});
QUnit.test('lazy load mobile-friendly view', async function (assert) {
const mockRPC = (route, args) => {
assert.step(args.method || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await loadState(webClient, {
action: 1,
view_type: 'form',
});
assert.containsNone(target, '.o_list_view');
assert.containsNone(target, '.o_kanban_view');
assert.containsOnce(target, '.o_form_view');
// go back to lazy loaded view
await click(target, '.o_control_panel .breadcrumb .o_back_button');
await legacyExtraNextTick();
assert.containsNone(target, '.o_form_view');
assert.containsNone(target, '.o_list_view');
assert.containsOnce(target, '.o_kanban_view');
assert.verifySteps([
'/web/webclient/load_menus',
'/web/action/load',
'get_views',
'onchange', // default_get/onchange to open form view
'web_search_read', // web search read when coming back to Kanban
]);
});
QUnit.test('view switcher button should be displayed in dropdown on mobile screens', async function (assert) {
// This test will spawn a kanban view (mobile friendly).
// so, the "legacy" code won't be tested here.
const webClient = await createWebClient({ serverData });
await doAction(webClient, 1);
assert.containsOnce(target.querySelector('.o_control_panel'), '.o_cp_switch_buttons > .o-dropdown > button');
assert.containsNone(target.querySelector('.o_control_panel'), '.o_cp_switch_buttons .o-dropdown--menu .o_switch_view.o_kanban');
assert.containsNone(target.querySelector('.o_control_panel'), '.o_cp_switch_buttons .o-dropdown--menu button.o_switch_view');
assert.hasClass(target.querySelector('.o_control_panel .o_cp_switch_buttons > .o-dropdown > button > i'), 'oi-view-kanban');
await click(target, '.o_control_panel .o_cp_switch_buttons > .o-dropdown > button');
assert.hasClass(target.querySelector('.o_cp_switch_buttons .o-dropdown--menu button.o_switch_view.o_kanban'), 'active');
assert.doesNotHaveClass(target.querySelector('.o_cp_switch_buttons .o-dropdown--menu button.o_switch_view.o_list'), 'active');
assert.hasClass(target.querySelector('.o_cp_switch_buttons .o-dropdown--menu button.o_switch_view.o_kanban'), 'oi-view-kanban');
});

View File

@@ -0,0 +1,160 @@
odoo.define('web_mobile.barcode.tests', function (require) {
"use strict";
const fieldRegistry = require('web.field_registry');
const FormView = require('web.FormView');
const { FieldMany2One } = require('web.relational_fields');
const { createView, dom, mock } = require('web.test_utils');
const FieldMany2OneBarcode = require('web_mobile.barcode_fields');
const BarcodeScanner = require('@web/webclient/barcode/barcode_scanner');
const NAME_SEARCH = "name_search";
const PRODUCT_PRODUCT = 'product.product';
const SALE_ORDER_LINE = 'sale_order_line';
const PRODUCT_FIELD_NAME = 'product_id';
const ARCHS = {
'product.product,false,kanban': `
<kanban><templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"><field name="display_name"/></div>
</t>
</templates></kanban>`,
'product.product,false,search': '<search></search>',
};
async function mockRPC(route, args) {
const result = await this._super(...arguments);
if (args.method === NAME_SEARCH && args.model === PRODUCT_PRODUCT) {
const records = this.data[PRODUCT_PRODUCT].records
.filter((record) => record.barcode === args.kwargs.name)
.map((record) => [record.id, record.name]);
return records.concat(result);
}
return result;
}
QUnit.module('web_mobile', {
beforeEach() {
this.data = {
[PRODUCT_PRODUCT]: {
fields: {
id: {type: 'integer'},
name: {},
barcode: {},
},
records: [{
id: 111,
name: 'product_cable_management_box',
barcode: '601647855631',
}, {
id: 112,
name: 'product_n95_mask',
barcode: '601647855632',
}, {
id: 113,
name: 'product_surgical_mask',
barcode: '601647855633',
}],
},
[SALE_ORDER_LINE]: {
fields: {
id: {type: 'integer'},
[PRODUCT_FIELD_NAME]: {
string: PRODUCT_FIELD_NAME,
type: 'many2one',
relation: PRODUCT_PRODUCT
},
product_uom_qty: {type: 'integer'}
}
},
};
},
}, function () {
QUnit.test("web_mobile: barcode button in a mobile environment with single results", async function (assert) {
assert.expect(2);
// simulate a mobile environment
fieldRegistry.add('many2one_barcode', FieldMany2OneBarcode);
mock.patch(BarcodeScanner, {
isBarcodeScannerSupported: () => true,
scanBarcode: async () => this.data[PRODUCT_PRODUCT].records[0].barcode,
});
const form = await createView({
View: FormView,
arch: `
<form>
<sheet>
<field name="${PRODUCT_FIELD_NAME}" widget="many2one_barcode"/>
</sheet>
</form>`,
data: this.data,
model: SALE_ORDER_LINE,
archs: ARCHS,
mockRPC,
});
const $scanButton = form.$('.o_barcode');
assert.equal($scanButton.length, 1, "has scanner button");
await dom.click($scanButton);
const selectedId = form.renderer.state.data[PRODUCT_FIELD_NAME].res_id;
assert.equal(selectedId, this.data[PRODUCT_PRODUCT].records[0].id,
`product found and selected (${this.data[PRODUCT_PRODUCT].records[0].barcode})`);
form.destroy();
fieldRegistry.add('many2one_barcode', FieldMany2One);
mock.unpatch(BarcodeScanner);
});
QUnit.test("web_mobile: barcode button in a mobile environment with multiple results", async function (assert) {
assert.expect(4);
// simulate a mobile environment
fieldRegistry.add('many2one_barcode', FieldMany2OneBarcode);
mock.patch(BarcodeScanner, {
isBarcodeScannerSupported: () => true,
scanBarcode: async () => "mask",
});
const form = await createView({
View: FormView,
arch: `
<form>
<sheet>
<field name="${PRODUCT_FIELD_NAME}" widget="many2one_barcode"/>
</sheet>
</form>`,
data: this.data,
model: SALE_ORDER_LINE,
archs: ARCHS,
mockRPC,
});
const $scanButton = form.$('.o_barcode');
assert.equal($scanButton.length, 1, "has scanner button");
await dom.click($scanButton);
const $modal = $('.o_modal_full .modal-lg');
assert.equal($modal.length, 1, 'there should be one modal opened in full screen');
assert.equal($modal.find('.o_legacy_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 2,
'there should be 2 records displayed');
await dom.click($modal.find('.o_legacy_kanban_view .o_kanban_record:first'));
const selectedId = form.renderer.state.data[PRODUCT_FIELD_NAME].res_id;
assert.equal(selectedId, this.data[PRODUCT_PRODUCT].records[1].id,
`product found and selected (${this.data[PRODUCT_PRODUCT].records[1].barcode})`);
form.destroy();
fieldRegistry.add('many2one_barcode', FieldMany2One);
mock.unpatch(BarcodeScanner);
});
});
});

View File

@@ -0,0 +1,155 @@
odoo.define('web_mobile.barcode.tests', function (require) {
"use strict";
const fieldRegistry = require('web.field_registry');
const FormView = require('web.FormView');
const { FieldMany2One } = require('web.relational_fields');
const { createView, dom, mock } = require('web.test_utils');
const FieldMany2OneBarcode = require('web_mobile.barcode_fields');
const BarcodeScanner = require('@web/webclient/barcode/barcode_scanner');
const NAME_SEARCH = "name_search";
const PRODUCT_PRODUCT = 'product.product';
const SALE_ORDER_LINE = 'sale_order_line';
const PRODUCT_FIELD_NAME = 'product_id';
const ARCHS = {
'product.product,false,list': `<tree>
<field name="display_name"/>
</tree>`,
'product.product,false,search': '<search></search>',
};
async function mockRPC(route, args) {
const result = await this._super(...arguments);
if (args.method === NAME_SEARCH && args.model === PRODUCT_PRODUCT) {
const records = this.data[PRODUCT_PRODUCT].records
.filter((record) => record.barcode === args.kwargs.name)
.map((record) => [record.id, record.name]);
return records.concat(result);
}
return result;
}
QUnit.module('web_mobile', {
beforeEach() {
this.data = {
[PRODUCT_PRODUCT]: {
fields: {
id: {type: 'integer'},
name: {},
barcode: {},
},
records: [{
id: 111,
name: 'product_cable_management_box',
barcode: '601647855631',
}, {
id: 112,
name: 'product_n95_mask',
barcode: '601647855632',
}, {
id: 113,
name: 'product_surgical_mask',
barcode: '601647855633',
}],
},
[SALE_ORDER_LINE]: {
fields: {
id: {type: 'integer'},
[PRODUCT_FIELD_NAME]: {
string: PRODUCT_FIELD_NAME,
type: 'many2one',
relation: PRODUCT_PRODUCT
},
}
},
};
},
}, function () {
QUnit.test("web_mobile: barcode button in a mobile environment with single results", async function (assert) {
assert.expect(2);
// simulate a mobile environment
fieldRegistry.add('many2one_barcode', FieldMany2OneBarcode);
mock.patch(BarcodeScanner, {
isBarcodeScannerSupported: () => true,
scanBarcode: async () => this.data[PRODUCT_PRODUCT].records[0].barcode,
});
const form = await createView({
View: FormView,
arch: `
<form>
<sheet>
<field name="${PRODUCT_FIELD_NAME}" widget="many2one_barcode"/>
</sheet>
</form>`,
data: this.data,
model: SALE_ORDER_LINE,
archs: ARCHS,
mockRPC,
});
const $scanButton = form.$('.o_barcode');
assert.containsOnce(form, $scanButton, "has scanner button");
await dom.click($scanButton);
const selectedId = form.renderer.state.data[PRODUCT_FIELD_NAME].res_id;
assert.equal(selectedId, this.data[PRODUCT_PRODUCT].records[0].id,
`product found and selected (${this.data[PRODUCT_PRODUCT].records[0].barcode})`);
form.destroy();
fieldRegistry.add('many2one_barcode', FieldMany2One);
mock.unpatch(BarcodeScanner);
});
QUnit.test("web_mobile: barcode button in a mobile environment with multiple results", async function (assert) {
// simulate a mobile environment
fieldRegistry.add('many2one_barcode', FieldMany2OneBarcode);
mock.patch(BarcodeScanner, {
isBarcodeScannerSupported: () => true,
scanBarcode: async () => "mask"
});
const form = await createView({
View: FormView,
arch: `
<form>
<sheet>
<field name="${PRODUCT_FIELD_NAME}" widget="many2one_barcode"/>
</sheet>
</form>`,
data: this.data,
model: SALE_ORDER_LINE,
archs: ARCHS,
mockRPC,
});
const $scanButton = form.$('.o_barcode');
assert.containsOnce(form, $scanButton, "has scanner button");
await dom.click($scanButton);
const $modal = $('.modal-dialog.modal-lg');
assert.containsOnce($('body'), $modal, 'there should be one modal opened in full screen');
assert.containsN($modal, '.o_legacy_list_view .o_data_row', 2,
'there should be 2 records displayed');
await dom.click($modal.find('.o_legacy_list_view .o_data_row:first'));
const selectedId = form.renderer.state.data[PRODUCT_FIELD_NAME].res_id;
assert.equal(selectedId, this.data[PRODUCT_PRODUCT].records[1].id,
`product found and selected (${this.data[PRODUCT_PRODUCT].records[1].barcode})`);
form.destroy();
fieldRegistry.add('many2one_barcode', FieldMany2One);
mock.unpatch(BarcodeScanner);
});
});
});

View File

@@ -0,0 +1,82 @@
odoo.define('web.action_menus_mobile_tests', function (require) {
"use strict";
const ActionMenus = require('web.ActionMenus');
const Registry = require('web.Registry');
const testUtils = require('web.test_utils');
const { createComponent } = testUtils;
QUnit.module('Components', {
beforeEach() {
this.action = {
res_model: 'hobbit',
};
this.view = {
type: 'form',
};
this.props = {
activeIds: [23],
context: {},
items: {
action: [
{ action: { id: 1 }, name: "What's taters, precious ?", id: 1 },
],
print: [
{ action: { id: 2 }, name: "Po-ta-toes", id: 2 },
],
},
};
// Patch the registry of the action menus
this.actionMenusRegistry = ActionMenus.registry;
ActionMenus.registry = new Registry();
},
afterEach() {
ActionMenus.registry = this.actionMenusRegistry;
},
}, function () {
QUnit.module('ActionMenus');
QUnit.test('Auto close the print dropdown after click inside an item', async function (assert) {
assert.expect(6);
const actionMenus = await createComponent(ActionMenus, {
env: {
device: {
isMobile: true
},
action: this.action,
view: this.view,
},
intercepts: {
'do-action': ev => assert.step('do-action'),
},
props: this.props,
async mockRPC(route, args) {
switch (route) {
case '/web/action/load':
const expectedContext = {
active_id: 23,
active_ids: [23],
active_model: 'hobbit',
};
assert.deepEqual(args.context, expectedContext);
assert.step('load-action');
return {context: {}, flags: {}};
default:
return this._super(...arguments);
}
},
});
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Print");
assert.containsOnce(actionMenus.el, '.dropdown-menu-start',
"should display the dropdown menu");
await testUtils.controlPanel.toggleMenuItem(actionMenus, "Po-ta-toes");
assert.containsNone(actionMenus.el, '.dropdown-menu-start',
"should not display the dropdown menu");
assert.verifySteps(['load-action', 'do-action']);
});
});
});

View File

@@ -0,0 +1,245 @@
odoo.define('web.control_panel_mobile_tests', function (require) {
"use strict";
const FormView = require('web.FormView');
const testUtils = require('web.test_utils');
const cpHelpers = require('@web/../tests/search/helpers');
const { browser } = require("@web/core/browser/browser");
const { patchWithCleanup, getFixture } = require("@web/../tests/helpers/utils");
const { createControlPanel, createView } = testUtils;
const { createWebClient, doAction, getActionManagerServerData } = require('@web/../tests/webclient/helpers');
let serverData;
let target;
QUnit.module('Control Panel', {
beforeEach: function () {
target = getFixture();
this.actions = [{
id: 1,
name: "Yes",
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'list']],
}];
this.archs = {
'partner,false,list': '<tree><field name="foo"/></tree>',
'partner,false,search': `
<search>
<filter string="Active" name="my_projects" domain="[('boolean_field', '=', True)]"/>
<field name="foo" string="Foo"/>
</search>`,
};
this.data = {
partner: {
fields: {
foo: { string: "Foo", type: "char" },
boolean_field: { string: "I am a boolean", type: "boolean" },
},
records: [
{ id: 1, display_name: "First record", foo: "yop" },
],
},
};
const actions = {};
this.actions.forEach((act) => {
actions[act.xml_id || act.id] = act;
});
serverData = getActionManagerServerData();
Object.assign(serverData, { models: this.data, views: this.archs, actions });
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
},
}, function () {
QUnit.test('basic rendering', async function (assert) {
assert.expect(2);
const webClient = await createWebClient({serverData});
await doAction(webClient, 1);
assert.containsNone(target, '.o_control_panel .o_mobile_search',
"search options are hidden by default");
assert.containsOnce(target, '.o_control_panel .o_enable_searchview',
"should display a button to toggle the searchview");
});
QUnit.test("control panel appears at top on scroll event", async function (assert) {
assert.expect(12);
const MAX_HEIGHT = 800;
const MIDDLE_HEIGHT = 400;
const DELTA_TEST = 20;
const form = await createView({
View: FormView,
arch:
'<form>' +
'<sheet>' +
`<div style="height: ${2 * MAX_HEIGHT}px"></div>` +
'</sheet>' +
'</form>',
data: this.data,
model: 'partner',
res_id: 1,
});
const controlPanelEl = document.querySelector('.o_control_panel');
const controlPanelHeight = controlPanelEl.offsetHeight;
// Force container to have a scrollbar
controlPanelEl.parentElement.style.maxHeight = `${MAX_HEIGHT}px`;
const scrollAndAssert = async (targetHeight, expectedTopValue, hasStickyClass) => {
if (targetHeight !== null) {
controlPanelEl.parentElement.scrollTo(0, targetHeight);
await testUtils.nextTick();
}
const expectedPixelValue = `${expectedTopValue}px`;
assert.strictEqual(controlPanelEl.style.top, expectedPixelValue,
`Top must be ${expectedPixelValue} (after scroll to ${targetHeight})`);
if (hasStickyClass) {
assert.hasClass(controlPanelEl, 'o_mobile_sticky');
} else {
assert.doesNotHaveClass(controlPanelEl, 'o_mobile_sticky');
}
}
// Initial position (scrollTop: 0)
await scrollAndAssert(null, 0, false);
// Scroll down 800px (scrollTop: 800)
await scrollAndAssert(MAX_HEIGHT, -controlPanelHeight, true);
// Scoll up 20px (scrollTop: 780)
await scrollAndAssert(MAX_HEIGHT - DELTA_TEST, -controlPanelHeight + DELTA_TEST, true);
// Scroll up 380px (scrollTop: 400)
await scrollAndAssert(MIDDLE_HEIGHT, 0, true);
// Scroll down 200px (scrollTop: 800)
await scrollAndAssert(MAX_HEIGHT, -controlPanelHeight, true);
// Scroll up 400px (scrollTop: 0)
await scrollAndAssert(0, -controlPanelHeight, false);
form.destroy();
});
QUnit.test("mobile search: basic display", async function (assert) {
assert.expect(4);
const fields = {
birthday: { string: "Birthday", type: "date", store: true, sortable: true },
};
const searchMenuTypes = ["filter", "groupBy", "comparison", "favorite"];
const params = {
cpModelConfig: {
arch: `
<search>
<filter name="birthday" date="birthday"/>
</search>`,
fields,
searchMenuTypes,
},
cpProps: { fields, searchMenuTypes },
};
const controlPanel = await createControlPanel(params);
// Toggle search bar controls
await testUtils.dom.click(controlPanel.el.querySelector("button.o_enable_searchview"));
// Open search view
await testUtils.dom.click(controlPanel.el.querySelector("button.o_toggle_searchview_full"));
// Toggle filter date
// Note: 'document.body' is used instead of 'controlPanel' because the
// search view is directly in the body.
await cpHelpers.toggleFilterMenu(document);
await cpHelpers.toggleMenuItem(document, "Birthday");
await cpHelpers.toggleMenuItemOption(document, "Birthday", 0);
assert.containsOnce(document.body, ".o_filter_menu");
assert.containsOnce(document.body, ".o_group_by_menu");
assert.containsOnce(document.body, ".o_comparison_menu");
assert.containsOnce(document.body, ".o_favorite_menu");
});
QUnit.test('mobile search: activate a filter through quick search', async function (assert) {
assert.expect(7);
let searchRPCFlag = false;
const mockRPC = (route, args) => {
if (searchRPCFlag && args.method === "web_search_read") {
assert.deepEqual(args.kwargs.domain, [['foo', 'ilike', 'A']],
"domain should have been properly transferred to list view");
}
};
const webClient = await createWebClient({serverData, mockRPC});
await doAction(webClient, 1);
assert.containsOnce(document.body, 'button.o_enable_searchview.oi-search',
"should display a button to open the searchview");
assert.containsNone(document.body, '.o_searchview_input_container',
"Quick search input should be hidden");
// open the search view
await testUtils.dom.click(document.querySelector('button.o_enable_searchview'));
assert.containsOnce(document.body, '.o_toggle_searchview_full',
"should display a button to expand the searchview");
assert.containsOnce(document.body, '.o_searchview_input_container',
"Quick search input should now be visible");
searchRPCFlag = true;
// use quick search input (search view is directly put in the body)
await cpHelpers.editSearch(document.body, "A");
await cpHelpers.validateSearch(document.body);
// close quick search
await testUtils.dom.click(document.querySelector('button.o_enable_searchview.fa-arrow-left'));
assert.containsNone(document.body, '.o_toggle_searchview_full',
"Expand icon shoud be hidden");
assert.containsNone(document.body, '.o_searchview_input_container',
"Quick search input should be hidden");
});
QUnit.test('mobile search: activate a filter in full screen search view', async function (assert) {
assert.expect(3);
const webClient = await createWebClient({ serverData });
await doAction(webClient, 1);
assert.containsNone(document.body, '.o_mobile_search');
// open the search view
await testUtils.dom.click(target.querySelector('button.o_enable_searchview'));
// open it in full screen
await testUtils.dom.click(target.querySelector('.o_toggle_searchview_full'));
assert.containsOnce(document.body, '.o_mobile_search');
await cpHelpers.toggleFilterMenu(document.body);
await cpHelpers.toggleMenuItem(document.body, "Active");
// closing search view
await testUtils.dom.click(
[...document.querySelectorAll('.o_mobile_search_button')].find(
e => e.innerText.trim() === "FILTER"
)
);
assert.containsNone(document.body, '.o_mobile_search');
});
});
});

View File

@@ -0,0 +1,409 @@
odoo.define('web_enterprise.form_tests', function (require) {
"use strict";
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
QUnit.module('web_enterprise', {
beforeEach: function () {
this.data = {
partner: {
fields: {
display_name: { string: "Displayed name", type: "char" },
trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
},
records: [{
id: 1,
display_name: "first record",
trululu: 4,
}, {
id: 2,
display_name: "second record",
trululu: 1,
}, {
id: 4,
display_name: "aaa",
}],
},
};
},
}, function () {
QUnit.module('Mobile FormView');
QUnit.test('statusbar buttons are correctly rendered in mobile', async function (assert) {
assert.expect(5);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<header>' +
'<button string="Confirm"/>' +
'<button string="Do it"/>' +
'</header>' +
'<sheet>' +
'<group>' +
'<button name="display_name"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_statusbar_buttons a:contains(Action)').length, 1,
"statusbar should contain a button 'Action'");
assert.containsOnce(form, '.o_statusbar_buttons .dropdown-menu',
"statusbar should contain a dropdown");
assert.containsNone(form, '.o_statusbar_buttons .dropdown-menu:visible',
"dropdown should be hidden");
// open the dropdown
await testUtils.dom.click(form.$('.o_statusbar_buttons a'));
assert.containsOnce(form, '.o_statusbar_buttons .dropdown-menu:visible',
"dropdown should be visible");
assert.containsN(form, '.o_statusbar_buttons .dropdown-menu > button', 2,
"dropdown should contain 2 buttons");
form.destroy();
});
QUnit.test('statusbar "Action" button should be displayed only if there are multiple visible buttons', async function (assert) {
assert.expect(4);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<header>' +
'<button string="Confirm" attrs=\'{"invisible": [["display_name", "=", "first record"]]}\'/>' +
'<button string="Do it" attrs=\'{"invisible": [["display_name", "=", "first record"]]}\'/>' +
'</header>' +
'<sheet>' +
'<group>' +
'<field name="display_name"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 1,
});
// if all buttons are invisible then there should be no action button
assert.containsNone(form, '.o_statusbar_buttons > btn-group > .dropdown-toggle',
"'Action' dropdown is not displayed as there are no visible buttons");
// there should be two invisible buttons
assert.containsN(form, '.o_statusbar_buttons > button.o_invisible_modifier', 2,
"Status bar should have two buttons with 'o_invisible_modifier' class");
// change display_name to update buttons modifiers and make it visible
await testUtils.form.clickEdit(form);
await testUtils.fields.editInput(form.$('input[name=display_name]'), 'test');
await testUtils.form.clickSave(form);
assert.containsOnce(form, '.o_statusbar_buttons a:contains(Action)',
"statusbar should contain a button 'Action'");
assert.containsOnce(form, '.o_statusbar_buttons .dropdown-menu',
"statusbar should contain a dropdown");
form.destroy();
});
QUnit.test('statusbar "Action" button not displayed in edit mode with .oe_read_only button', async function (assert) {
assert.expect(2);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<header>
<button string="Share" type="action" class="oe_highlight oe_read_only"/>
<button string="Email" type="action" class="oe_highlight oe_read_only"/>
</header>
<sheet>
<group>
<field name="display_name"/>
</group>
</sheet>
</form>
`,
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
assert.containsNone(form, '.o_statusbar_buttons a:contains(Action)',
"'Action' button should not be there");
await testUtils.form.clickSave(form);
assert.containsOnce(form, '.o_statusbar_buttons a:contains(Action)',
"'Action' button should be there");
form.destroy();
});
QUnit.test(`statusbar "Action" button shouldn't be displayed for only one visible button`, async function (assert) {
assert.expect(3);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `<form>
<header>
<button string="Hola" attrs='{"invisible": [["display_name", "=", "first record"]]}'/>
<button string="Ciao"/>
</header>
<sheet>
<group>
<field name="display_name"/>
</group>
</sheet>
</form>`,
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
// There should be a simple statusbar button and no action dropdown
assert.containsNone(form, '.o_statusbar_buttons a:contains(Action)', "should have no 'Action' dropdown");
assert.containsOnce(form, '.o_statusbar_buttons > button > span:contains(Ciao)', "should have a simple statusbar button 'Ciao'");
// change display_name to update buttons modifiers and make both buttons visible
await testUtils.fields.editInput(form.$('input[name=display_name]'), 'test');
// Now there should an action dropdown, because there are two visible buttons
assert.containsOnce(form, '.o_statusbar_buttons a:contains(Action)', "should have no 'Action' dropdown");
form.destroy();
});
QUnit.test(`statusbar widgets should appear in the statusbar dropdown only if there are multiple items`, async function (assert) {
assert.expect(4);
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form string="Partners">
<header>
<widget name="attach_document" string="Attach document"/>
<button string="Ciao" attrs='{"invisible": [["display_name", "=", "first record"]]}'/>
</header>
<sheet>
<group>
<field name="display_name"/>
</group>
</sheet>
</form>
`,
res_id: 2,
viewOptions: {
mode: 'edit',
},
});
const dropdownActionButton = '.o_statusbar_buttons a:contains(Action)';
// Now there should an action dropdown, because there are two visible buttons
assert.containsOnce(form, dropdownActionButton, "should have 'Action' dropdown");
assert.containsN(form, `.o_statusbar_buttons .dropdown-menu > button`,
2, "should have 2 buttons in the dropdown");
// change display_name to update buttons modifiers and make one button visible
await testUtils.fields.editInput(form.$('input[name=display_name]'), 'first record');
// There should be a simple statusbar button and no action dropdown
assert.containsNone(form, dropdownActionButton, "shouldn't have 'Action' dropdown");
assert.containsOnce(form, `.o_statusbar_buttons > button:visible`,
"should have 1 button visible in the statusbar");
form.destroy();
});
QUnit.test(`Quick Edition: quick edit many2one`, async function (assert) {
assert.expect(1);
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<sheet>
<group>
<field name="trululu" />
</group>
</sheet>
</form>
`,
archs: {
'partner,false,kanban': `
<kanban>
<templates><t t-name="kanban-box">
<div class="oe_kanban_global_click">
<field name="display_name"/>
</div>
</t></templates>
</kanban>
`,
'partner,false,search': '<search></search>',
},
res_id: 2,
});
await testUtils.dom.click(form.$('.o_form_label'));
await testUtils.nextTick(); // wait for quick edit
const $modal = $('.o_modal_full .modal-lg');
assert.equal($modal.length, 1, 'there should be one modal opened in full screen');
form.destroy();
});
QUnit.test('statusbar "Action" dropdown should keep its open/close state', async function (assert) {
assert.expect(5);
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<header>
<button string="Just more than one"/>
<button string="Confirm" attrs='{"invisible": [["display_name", "=", ""]]}'/>
<button string="Do it" attrs='{"invisible": [["display_name", "!=", ""]]}'/>
</header>
<sheet>
<field name="display_name"/>
</sheet>
</form>
`,
});
const dropdownMenuSelector = '.o_statusbar_buttons .dropdown-menu';
assert.containsOnce(form, dropdownMenuSelector,
"statusbar should contain a dropdown");
assert.doesNotHaveClass(form.el.querySelector(dropdownMenuSelector), 'show',
"dropdown should be closed");
// open the dropdown
await testUtils.dom.click(form.el.querySelector('.o_statusbar_buttons .dropdown-toggle'));
assert.hasClass(form.el.querySelector(dropdownMenuSelector), 'show',
"dropdown should be opened");
// change display_name to update buttons' modifiers
await testUtils.fields.editInput(form.el.querySelector('input[name="display_name"]'), 'test');
assert.containsOnce(form, dropdownMenuSelector,
"statusbar should contain a dropdown");
assert.hasClass(form.el.querySelector(dropdownMenuSelector), 'show',
"dropdown should still be opened");
form.destroy();
});
QUnit.test(`statusbar "Action" dropdown's open/close state shouldn't be modified after 'onchange'`, async function (assert) {
assert.expect(5);
let resolveOnchange;
const onchangePromise = new Promise(resolve => {
resolveOnchange = resolve;
});
this.data.partner.onchanges = {
display_name: async () => {},
};
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<header>
<button name="create" string="Create Invoice" type="action"/>
<button name="send" string="Send by Email" type="action"/>
</header>
<sheet>
<field name="display_name" />
</sheet>
</form>
`,
async mockRPC(route, { method, args: [, , changedField] }) {
return method === 'onchange' && changedField === 'display_name' ? onchangePromise : this._super(...arguments);
},
});
const dropdownMenuSelector = '.o_statusbar_buttons .dropdown-menu';
assert.containsOnce(form, dropdownMenuSelector,
"statusbar should contain a dropdown");
assert.doesNotHaveClass(form.el.querySelector(dropdownMenuSelector), 'show',
"dropdown should be closed");
await testUtils.fields.editInput(form.el.querySelector('input[name="display_name"]'), 'before onchange');
await testUtils.dom.click(form.el.querySelector('.o_statusbar_buttons .dropdown-toggle'));
assert.hasClass(form.el.querySelector(dropdownMenuSelector), 'show',
"dropdown should be opened");
resolveOnchange({ value: { display_name: 'after onchange' } });
await testUtils.nextTick();
assert.strictEqual(form.el.querySelector('input[name="display_name"]').value, 'after onchange');
assert.hasClass(form.el.querySelector(dropdownMenuSelector), 'show',
"dropdown should still be opened");
form.destroy();
});
QUnit.test("preserve current scroll position on form view while closing dialog", async function (assert) {
assert.expect(6);
const form = await createView({
View: FormView,
arch: `<form>
<sheet>
<p style='height:500px'></p>
<field name="trululu"/>
<p style='height:500px'></p>
</sheet>
</form>`,
archs: {
"partner,false,kanban": `<kanban>
<templates><t t-name="kanban-box">
<div class="oe_kanban_global_click"><field name="display_name"/></div>
</t></templates>
</kanban>`,
"partner,false,search": "<search></search>",
},
data: this.data,
model: "partner",
res_id: 2,
debug: true, // need to be in the viewport to check scrollPosition
viewOptions: { mode: "edit" },
});
const scrollPosition = { top: 265, left: 0 };
window.scrollTo(scrollPosition);
assert.strictEqual(window.scrollY, scrollPosition.top, "Should have scrolled 265 px vertically");
assert.strictEqual(window.scrollX, scrollPosition.left, "Should be 0 px from left as it is");
// click on m2o field
await testUtils.dom.click(form.$(".o_field_many2one input"));
assert.strictEqual(window.scrollY, 0, "Should have scrolled to top (0) px");
assert.containsOnce($("body"), ".modal.o_modal_full", "there should be a many2one modal opened in full screen");
// click on back button
await testUtils.dom.click($(".modal").find(".modal-header .fa-arrow-left"));
assert.strictEqual(window.scrollY, scrollPosition.top, "Should have scrolled back to 265 px vertically");
assert.strictEqual(window.scrollX, scrollPosition.left, "Should be 0 px from left as it is");
form.destroy();
});
});
});

View File

@@ -0,0 +1,498 @@
odoo.define('web_enterprise.relational_fields_mobile_tests', function (require) {
"use strict";
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
QUnit.module('web_enterprise', {}, function () {
QUnit.module('relational_fields', {
beforeEach: function () {
this.data = {
partner: {
fields: {
display_name: { string: "Displayed name", type: "char" },
trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
sibling_ids: {string: "Sibling", type: "many2many", relation: 'partner'},
p: { string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu' },
},
records: [{
id: 1,
display_name: "first record",
trululu: 4,
}, {
id: 2,
display_name: "second record",
trululu: 1,
}, {
id: 4,
display_name: "aaa",
}],
},
};
}
}, function () {
QUnit.module('FieldStatus');
QUnit.test('statusbar is rendered correclty on small devices', async function (assert) {
assert.expect(7);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch:
'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar"/></header>' +
'<field name="display_name"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_statusbar_status > button:contains(aaa)').length, 1,
"should have only one visible status in mobile, the active one");
assert.containsOnce(form, '.o_statusbar_status .dropdown-menu',
"should have a dropdown containing all status");
assert.containsNone(form, '.o_statusbar_status .dropdown-menu:visible',
"dropdown should be hidden");
// open the dropdown
testUtils.dom.click(form.$('.o_statusbar_status > button'));
assert.containsOnce(form, '.o_statusbar_status .dropdown-menu:visible',
"dropdown should be visible");
assert.containsN(form, '.o_statusbar_status .dropdown-menu button', 3,
"should have 3 status");
assert.containsN(form, '.o_statusbar_status button:disabled', 3,
"all status should be disabled");
var $activeStatus = form.$('.o_statusbar_status .dropdown-menu button[data-value=4]');
assert.hasClass($activeStatus,'btn-primary', "active status should be btn-primary");
form.destroy();
});
QUnit.test('statusbar with no status on extra small screens', async function (assert) {
assert.expect(9);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar"/></header>' +
'</form>',
res_id: 4,
});
assert.hasClass(form.$('.o_statusbar_status'),'o_field_empty',
'statusbar widget should have class o_field_empty');
assert.strictEqual(form.$('.o_statusbar_status').children().length, 2,
'statusbar widget should have two children');
assert.containsOnce(form, '.o_statusbar_status button.dropdown-toggle',
'statusbar widget should have a button');
assert.strictEqual(form.$('.o_statusbar_status button.dropdown-toggle').text().trim(), '',
'statusbar button has no text'); // Behavior as of saas-15, might be improved
assert.containsOnce(form, '.o_statusbar_status .dropdown-menu',
'statusbar widget should have a dropdown menu');
assert.containsN(form, '.o_statusbar_status .dropdown-menu button', 3,
'statusbar widget dropdown menu should have 3 buttons');
assert.strictEqual(form.$('.o_statusbar_status .dropdown-menu button').eq(0).text().trim(), 'first record',
'statusbar widget dropdown first button should display the first record display_name');
assert.strictEqual(form.$('.o_statusbar_status .dropdown-menu button').eq(1).text().trim(), 'second record',
'statusbar widget dropdown second button should display the second record display_name');
assert.strictEqual(form.$('.o_statusbar_status .dropdown-menu button').eq(2).text().trim(), 'aaa',
'statusbar widget dropdown third button should display the third record display_name');
form.destroy();
});
QUnit.test('clickable statusbar widget on mobile view', async function (assert) {
assert.expect(5);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<header><field name="trululu" widget="statusbar" options=\'{"clickable": "1"}\'/></header>' +
'</form>',
res_id: 1,
});
var $selectedStatus = form.$('.o_statusbar_status button[data-value="4"]');
assert.hasClass($selectedStatus, 'btn-primary');
assert.hasClass($selectedStatus,'disabled');
var selector = '.o_statusbar_status button.btn-secondary:not(.dropdown-toggle):not(:disabled)';
assert.containsN(form, selector, 2, "other status should be btn-secondary and not disabled");
await testUtils.dom.click(form.$('.o_statusbar_status .dropdown-toggle'));
await testUtils.dom.clickFirst(form.$(selector));
var $status = form.$('.o_statusbar_status button[data-value="1"]');
assert.hasClass($status, 'btn-primary');
assert.hasClass($status, 'disabled');
form.destroy();
});
QUnit.module('FieldMany2One');
QUnit.test("many2one in a enterprise environment", async function (assert) {
assert.expect(7);
var form = await createView({
View: FormView,
arch:
'<form>' +
'<sheet>' +
'<field name="trululu"/>' +
'</sheet>' +
'</form>',
archs: {
'partner,false,kanban': '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click"><field name="display_name"/></div>' +
'</t></templates>' +
'</kanban>',
'partner,false,search': '<search></search>',
},
data: this.data,
model: 'partner',
res_id: 2,
viewOptions: {mode: 'edit'},
});
var $input = form.$('.o_field_many2one input');
assert.doesNotHaveClass($input, 'ui-autocomplete-input',
"autocomplete should not be visible in a mobile environment");
await testUtils.dom.click($input);
var $modal = $('.o_modal_full .modal-lg');
assert.equal($modal.length, 1, 'there should be one modal opened in full screen');
assert.containsOnce($modal, '.o_legacy_kanban_view',
'kanban view should be open in SelectCreateDialog');
assert.containsOnce($modal, '.o_cp_searchview',
'should have Search view inside SelectCreateDialog');
assert.containsNone($modal.find(".o_control_panel .o_cp_buttons"), '.o-kanban-button-new',
"kanban view in SelectCreateDialog should not have Create button");
assert.strictEqual($modal.find(".o_legacy_kanban_view .o_kanban_record:not(.o_kanban_ghost)").length, 3,
"popup should load 3 records in kanban");
await testUtils.dom.click($modal.find('.o_legacy_kanban_view .o_kanban_record:first'));
assert.strictEqual($input.val(), 'first record',
'clicking kanban card should select record for many2one field');
form.destroy();
});
QUnit.test("hide/show element using selection_mode in kanban view in a enterprise environment", async function (assert) {
assert.expect(5);
this.data.partner.fields.foo = {string: "Foo", type: "char", default: "My little Foo Value"};
var form = await createView({
View: FormView,
arch:
'<form>' +
'<sheet>' +
'<field name="trululu"/>' +
'</sheet>' +
'</form>',
archs: {
'partner,false,kanban': '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
'<field name="display_name"/>' +
'</div>' +
'<div class="o_sibling_tags" t-if="!selection_mode">' +
'<field name="sibling_ids"/>' +
'</div>' +
'<div class="o_foo" t-if="selection_mode">' +
'<field name="foo"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
'partner,false,search': '<search></search>',
},
data: this.data,
model: 'partner',
res_id: 2,
viewOptions: {mode: 'edit'},
});
var $input = form.$('.o_field_many2one input');
assert.doesNotHaveClass($input, 'ui-autocomplete-input',
"autocomplete should not be visible in a mobile environment");
await testUtils.dom.click($input);
var $modal = $('.o_modal_full .modal-lg');
assert.equal($modal.length, 1, 'there should be one modal opened in full screen');
assert.containsOnce($modal, '.o_legacy_kanban_view',
'kanban view should be open in SelectCreateDialog');
assert.containsNone($modal, '.o_legacy_kanban_view .o_sibling_tags',
'o_sibling_tags div should not be available as div have condition on selection_mode');
assert.containsN($modal, '.o_legacy_kanban_view .o_foo', 3,
'o_foo div should be available as div have condition on selection_mode');
form.destroy();
});
QUnit.test("kanban_view_ref attribute opens specific kanban view given as a reference in a mobile environment", async function (assert) {
assert.expect(5);
var form = await createView({
View: FormView,
arch:
'<form>' +
'<sheet>' +
'<field name="trululu" kanban_view_ref="2"/>' +
'</sheet>' +
'</form>',
archs: {
'partner,1,kanban': '<kanban class="kanban1">' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
'<field name="display_name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
'partner,2,kanban': '<kanban class="kanban2">' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
'<div>' +
'<field name="display_name"/>' +
'</div>' +
'<div>' +
'<field name="trululu"/>' +
'</div>' +
'</div>' +
'</t></templates>' +
'</kanban>',
'partner,false,search': '<search></search>',
},
data: this.data,
model: 'partner',
res_id: 2,
viewOptions: {mode: 'edit'},
});
var $input = form.$('.o_field_many2one input');
assert.doesNotHaveClass($input, 'ui-autocomplete-input',
"autocomplete should not be visible in a mobile environment");
await testUtils.dom.click($input);
var $modal = $('.o_modal_full .modal-lg');
assert.equal($modal.length, 1, 'there should be one modal opened in full screen');
assert.containsOnce($modal, '.o_legacy_kanban_view',
'kanban view should be open in SelectCreateDialog');
assert.hasClass($modal.find('.o_legacy_kanban_view'), 'kanban2',
'kanban view with id 2 should be opened as it is given as kanban_view_ref');
assert.strictEqual($modal.find('.o_legacy_kanban_view .o_kanban_record:first').text(),
'first recordaaa',
'kanban with two fields should be opened');
form.destroy();
});
QUnit.test("many2one dialog on mobile: clear button header", async function (assert) {
assert.expect(7);
const form = await createView({
View: FormView,
arch: `
<form>
<sheet>
<field name="trululu"/>
</sheet>
</form>
`,
archs: {
'partner,false,kanban': `
<kanban>
<templates><t t-name="kanban-box">
<div class="oe_kanban_global_click"><field name="display_name"/></div>
</t></templates>
</kanban>
`,
'partner,false,search': '<search></search>',
},
data: this.data,
model: 'partner',
res_id: 2,
viewOptions: {mode: 'edit'},
});
let $input = form.$('.o_field_many2one input');
assert.doesNotHaveClass($input, 'ui-autocomplete-input',
"autocomplete should not be visible in a mobile environment");
await testUtils.dom.click($input);
assert.containsOnce($('body'), '.modal.o_modal_full',
"there should be a modal opened in full screen");
assert.containsN($('.modal'), '.o_legacy_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 3,
"popup should load 3 records in kanban");
await testUtils.dom.click($('.modal').find('.o_legacy_kanban_view .o_kanban_record:first'));
assert.strictEqual($input.val(), 'first record',
'clicking kanban card should select record for many2one field');
await testUtils.dom.click($input);
// clear button.
assert.containsOnce($('.modal').find('.modal-header'), '.o_clear_button',
"there should be a Clear button in the modal header");
await testUtils.dom.click($('.modal').find('.modal-header .o_clear_button'));
assert.containsNone($('body'), '.modal', "there should be no more modal");
$input = form.$('.o_field_many2one input');
assert.strictEqual($input.val(), "", "many2one should be cleared");
form.destroy();
});
QUnit.module('FieldMany2Many');
QUnit.test("many2many_tags in a mobile environment", async function (assert) {
assert.expect(10);
var rpcReadCount = 0;
var form = await createView({
View: FormView,
arch:
'<form>' +
'<sheet>' +
'<field name="sibling_ids" widget="many2many_tags"/>' +
'</sheet>' +
'</form>',
archs: {
'partner,false,kanban': '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div class="oe_kanban_global_click"><field name="display_name"/></div>' +
'</t></templates>' +
'</kanban>',
'partner,false,search': '<search></search>',
},
data: this.data,
model: 'partner',
res_id: 2,
viewOptions: {mode: 'edit'},
mockRPC: function (route, args) {
if (args.method === "read" && args.model === "partner") {
if (rpcReadCount === 0) {
assert.deepEqual(args.args[0], [2], "form should initially show partner 2");
} else if (rpcReadCount === 1) {
assert.deepEqual(args.args[0], [1], "partner with id 1 should be selected");
}
rpcReadCount++;
}
return this._super.apply(this, arguments);
},
});
var $input = form.$(".o_field_widget .o_input");
assert.strictEqual($input.find(".badge").length, 0,
"many2many_tags should have no tags");
await testUtils.dom.click($input);
var $modal = $('.o_modal_full .modal-lg');
assert.equal($modal.length, 1, 'there should be one modal opened in full screen');
assert.containsOnce($modal, '.o_legacy_kanban_view',
'kanban view should be open in SelectCreateDialog');
assert.containsOnce($modal, '.o_cp_searchview',
'should have Search view inside SelectCreateDialog');
assert.containsNone($modal.find(".o_control_panel .o_cp_buttons"), '.o-kanban-button-new',
"kanban view in SelectCreateDialog should not have Create button");
assert.strictEqual($modal.find(".o_legacy_kanban_view .o_kanban_record:not(.o_kanban_ghost)").length, 3,
"popup should load 3 records in kanban");
await testUtils.dom.click($modal.find('.o_legacy_kanban_view .o_kanban_record:first'));
assert.strictEqual(rpcReadCount, 2, "there should be a read for current form record and selected sibling");
assert.strictEqual(form.$(".o_field_widget.o_input .badge").length, 1,
"many2many_tags should have partner coucou3");
form.destroy();
});
QUnit.module('FieldOne2Many');
QUnit.test('one2many on mobile: remove header button', async function (assert) {
assert.expect(9);
this.data.partner.records[0].p = [1, 2, 4];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form string="Partners">
<field name="p"/>
</form>
`,
archs: {
'partner,false,form': `
<form string="Partner">
<field name="display_name"/>
</form>
`,
'partner,false,kanban': `
<kanban>
<templates><t t-name="kanban-box">
<div class="oe_kanban_global_click">
<field name="display_name"/>
</div>
</t></templates>
</kanban>
`,
},
res_id: 1,
mockRPC(route, args) {
if (route === '/web/dataset/call_kw/partner/write') {
const commands = args.args[1].p;
assert.strictEqual(commands.length, 3,
'should have generated three commands');
assert.ok(commands[0][0] === 4 && commands[0][1] === 2,
'should have generated the command 4 (LINK_TO) with id 2');
assert.ok(commands[1][0] === 4 && commands[1][1] === 4,
'should have generated the command 2 (LINK_TO) with id 1');
assert.ok(commands[2][0] === 2 && commands[2][1] === 1,
'should have generated the command 2 (DELETE) with id 2');
}
return this._super.apply(this, arguments);
},
});
await testUtils.form.clickEdit(form);
assert.containsN(form, '.o_legacy_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 3,
"should have 3 records in kanban");
await testUtils.dom.click(form.$('.o_legacy_kanban_view .o_kanban_record:first'));
assert.containsOnce($('body'), '.modal.o_modal_full',
"there should be a modal opened in full screen");
// remove button.
assert.containsOnce($('.modal').find('.modal-header'), '.o_btn_remove',
"there should be a 'Remove' button in the modal header");
await testUtils.dom.click($('.modal').find('.modal-header .o_btn_remove'));
assert.containsNone($('body'), '.modal', "there should be no more modal");
assert.containsN(form, '.o_legacy_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 2,
"should have 2 records in kanban");
// save and check that the correct command has been generated
await testUtils.form.clickSave(form);
form.destroy();
});
});
});
});

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();
});
});
});