合并企业版代码(未测试,先提交到测试分支)
This commit is contained in:
186
web_studio/static/tests/legacy/action_editor_action_tests.js
Normal file
186
web_studio/static/tests/legacy/action_editor_action_tests.js
Normal file
@@ -0,0 +1,186 @@
|
||||
odoo.define('web_studio.ActionEditorActionTests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
const { openStudio, registerStudioDependencies } = require("@web_studio/../tests/helpers");
|
||||
const { doAction } = require("@web/../tests/webclient/helpers");
|
||||
const { getFixture, legacyExtraNextTick } = require("@web/../tests/helpers/utils");
|
||||
const { createEnterpriseWebClient } = require("@web_enterprise/../tests/helpers");
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
QUnit.module('Studio', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
kikou: {
|
||||
fields: {
|
||||
display_name: { type: "char", string: "Display Name" },
|
||||
start: { type: 'datetime', store: 'true', string: "start date" },
|
||||
},
|
||||
},
|
||||
'res.groups': {
|
||||
fields: {
|
||||
display_name: { string: "Display Name", type: "char" },
|
||||
},
|
||||
records: [{
|
||||
id: 4,
|
||||
display_name: "Admin",
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
const views = {
|
||||
"kikou,1,list": `<tree><field name="display_name" /></tree>`,
|
||||
"kikou,2,form": `<form><field name="display_name" /></form>`,
|
||||
"kikou,false,search": `<search />`,
|
||||
};
|
||||
serverData = {models: this.data, views};
|
||||
target = getFixture();
|
||||
registerStudioDependencies();
|
||||
}
|
||||
}, function () {
|
||||
|
||||
QUnit.module('ActionEditorAction');
|
||||
|
||||
QUnit.test('add a gantt view', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (route === '/web_studio/add_view_type') {
|
||||
assert.strictEqual(args.view_type, 'gantt',
|
||||
"should add the correct view");
|
||||
return Promise.resolve(false);
|
||||
} else if (args.method === 'fields_get') {
|
||||
assert.strictEqual(args.model, 'kikou',
|
||||
"should read fields on the correct model");
|
||||
}
|
||||
};
|
||||
|
||||
const webClient = await createEnterpriseWebClient({ serverData, mockRPC });
|
||||
await doAction(webClient, {
|
||||
xml_id: "some.xml_id",
|
||||
type: "ir.actions.act_window",
|
||||
res_model: 'kikou',
|
||||
view_mode: 'list',
|
||||
views: [[1, 'list'], [2, 'form']],
|
||||
}, {clearBreadcrumbs: true});
|
||||
await openStudio(target, {noEdit: true});
|
||||
|
||||
await testUtils.dom.click($(target).find('.o_web_studio_view_type[data-type="gantt"] .o_web_studio_thumbnail'));
|
||||
await legacyExtraNextTick();
|
||||
|
||||
assert.containsOnce($, '.o_web_studio_new_view_dialog',
|
||||
"there should be an opened dialog to select gantt attributes");
|
||||
assert.strictEqual($('.o_web_studio_new_view_dialog select[name="date_start"]').val(), 'start',
|
||||
"date start should be prefilled (mandatory)");
|
||||
assert.strictEqual($('.o_web_studio_new_view_dialog select[name="date_stop"]').val(), 'start',
|
||||
"date stop should be prefilled (mandatory)");
|
||||
});
|
||||
|
||||
QUnit.test('disable the view from studio', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
xml_id: "kikou.action",
|
||||
name: 'Kikou Action',
|
||||
res_model: 'kikou',
|
||||
type: 'ir.actions.act_window',
|
||||
view_mode: 'list,form',
|
||||
views: [[1, 'list'], [2, 'form']],
|
||||
}
|
||||
};
|
||||
|
||||
const views = {
|
||||
'kikou,1,list': `<tree><field name="display_name"/></tree>`,
|
||||
'kikou,1,search': `<search></search>`,
|
||||
'kikou,2,form': `<form><field name="display_name"/></form>`,
|
||||
};
|
||||
Object.assign(serverData, {actions, views});
|
||||
|
||||
let loadActionStep = 0;
|
||||
const mockRPC = (route, args) => {
|
||||
if (route === '/web_studio/edit_action') {
|
||||
return true;
|
||||
} else if (route === '/web/action/load') {
|
||||
loadActionStep++;
|
||||
/**
|
||||
* step 1: initial action/load
|
||||
* step 2: on disabling list view
|
||||
*/
|
||||
if (loadActionStep === 2) {
|
||||
return {
|
||||
name: 'Kikou Action',
|
||||
res_model: 'kikou',
|
||||
view_mode: 'form',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[2, 'form']],
|
||||
id: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const webClient = await createEnterpriseWebClient({ serverData, mockRPC });
|
||||
|
||||
await doAction(webClient, 1);
|
||||
await openStudio(target);
|
||||
|
||||
await testUtils.dom.click(target.querySelector('.o_web_studio_menu_item a'));
|
||||
|
||||
// make list view disable and form view only will be there in studio view
|
||||
await testUtils.dom.click($(target).find('div[data-type="list"] .o_web_studio_more'));
|
||||
await testUtils.dom.click($(target).find('div[data-type="list"] a[data-action="disable_view"]'));
|
||||
// reloadAction = false;
|
||||
assert.hasClass(
|
||||
$(target).find('div[data-type="list"]'),
|
||||
'o_web_studio_inactive',
|
||||
"list view should have become inactive");
|
||||
|
||||
// make form view disable and it should prompt the alert dialog
|
||||
await testUtils.dom.click($(target).find('div[data-type="form"] .o_web_studio_more'));
|
||||
await testUtils.dom.click($(target).find('div[data-type="form"] a[data-action="disable_view"]'));
|
||||
assert.containsOnce(
|
||||
$,
|
||||
'.o_technical_modal',
|
||||
"should display a modal when attempting to disable last view");
|
||||
assert.strictEqual(
|
||||
$('.o_technical_modal .modal-body').text().trim(),
|
||||
"You cannot deactivate this view as it is the last one active.",
|
||||
"modal should tell that last view cannot be disabled");
|
||||
});
|
||||
|
||||
QUnit.test('add groups on action', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
xml_id: "some.xml_id",
|
||||
type: "ir.actions.act_window",
|
||||
res_model: 'kikou',
|
||||
view_mode: 'list',
|
||||
views: [[1, 'list'], [2, 'form']],
|
||||
},
|
||||
};
|
||||
Object.assign(serverData, {actions});
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (route === '/web_studio/edit_action') {
|
||||
assert.strictEqual(args.args.groups_id[0], 4,
|
||||
"group admin should be applied on action");
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
};
|
||||
const webClient = await createEnterpriseWebClient({ serverData, mockRPC });
|
||||
await doAction(webClient, 1, {clearBreadcrumbs: true});
|
||||
await openStudio(target, {noEdit: true});
|
||||
|
||||
await testUtils.fields.many2one.clickOpenDropdown('groups_id');
|
||||
await testUtils.fields.many2one.clickHighlightedItem('groups_id');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
204
web_studio/static/tests/legacy/edit_menu_tests.js
Normal file
204
web_studio/static/tests/legacy/edit_menu_tests.js
Normal file
@@ -0,0 +1,204 @@
|
||||
odoo.define('web_studio.EditMenu_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
const { prepareWowlFormViewDialogs } = require("@web/../tests/views/helpers");
|
||||
|
||||
var EditMenu = require('web_studio.EditMenu');
|
||||
|
||||
QUnit.module('Studio', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
'ir.ui.menu': {
|
||||
fields: {},
|
||||
records: [{
|
||||
id: 1,
|
||||
name: 'Menu 1',
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Menu 2',
|
||||
}, {
|
||||
id: 21,
|
||||
name: 'Submenu 1',
|
||||
}, {
|
||||
id: 22,
|
||||
name: 'Submenu 2',
|
||||
}]
|
||||
}
|
||||
};
|
||||
this.menu_data = {
|
||||
childrenTree: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Menu 1',
|
||||
parent_id: false,
|
||||
childrenTree: [],
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Menu 2',
|
||||
parent_id: false,
|
||||
childrenTree: [
|
||||
{
|
||||
childrenTree: [],
|
||||
id: 21,
|
||||
name: 'Submenu 1',
|
||||
parent_id: 2,
|
||||
|
||||
}, {
|
||||
childrenTree: [],
|
||||
id: 21,
|
||||
name: 'Submenu 2',
|
||||
parent_id: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
this.archs = {
|
||||
'ir.ui.menu,false,form':
|
||||
'<form>'+
|
||||
'<sheet>' +
|
||||
'<field name="name"/>' +
|
||||
'</sheet>' +
|
||||
'</form>'
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
|
||||
QUnit.module('EditMenu');
|
||||
|
||||
QUnit.test('edit menu behavior', async function(assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var $target = $('#qunit-fixture');
|
||||
|
||||
var edit_menu = new EditMenu.MenuItem(null, this.menu_data, 2);
|
||||
await edit_menu.appendTo($target);
|
||||
|
||||
await testUtils.mock.addMockEnvironment(edit_menu, {
|
||||
data: this.data,
|
||||
archs: this.archs,
|
||||
});
|
||||
assert.strictEqual($('.o_web_studio_edit_menu_modal').length, 0,
|
||||
"there should not be any modal in the dom");
|
||||
assert.containsOnce(edit_menu, '.o_web_edit_menu',
|
||||
"there should be an edit menu link");
|
||||
|
||||
// open the dialog to edit the menu
|
||||
await testUtils.dom.click(edit_menu.$('.o_web_edit_menu'));
|
||||
assert.strictEqual($('.o_web_studio_edit_menu_modal').length, 1,
|
||||
"there should be a modal in the dom");
|
||||
|
||||
edit_menu.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('edit menu dialog', async function(assert) {
|
||||
assert.expect(23);
|
||||
|
||||
const dialog = new EditMenu.Dialog(null, this.menu_data, 2);
|
||||
dialog.open();
|
||||
await dialog.opened();
|
||||
|
||||
let customizeCalls = 0;
|
||||
|
||||
await testUtils.mock.addMockEnvironment(dialog, {
|
||||
data: this.data,
|
||||
archs: this.archs,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === "/web/dataset/call_kw/ir.ui.menu/customize") {
|
||||
if (customizeCalls === 0) {
|
||||
assert.deepEqual(args.kwargs, {
|
||||
to_delete: [],
|
||||
to_move: {
|
||||
2: { sequence: 1 },
|
||||
21: { parent_menu_id: 2, sequence: 0 },
|
||||
},
|
||||
});
|
||||
}
|
||||
customizeCalls++;
|
||||
return Promise.reject();
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
await prepareWowlFormViewDialogs({ models: this.data, views: this.archs });
|
||||
|
||||
const $modal = $('.modal');
|
||||
assert.containsOnce(dialog, 'ul.oe_menu_editor',
|
||||
"there should be the list of menus");
|
||||
assert.containsOnce(dialog, 'ul.oe_menu_editor > li',
|
||||
"there should be only one main menu");
|
||||
assert.strictEqual(dialog.$('ul.oe_menu_editor > li').data('menu-id'), 2,
|
||||
"the main menu should have the menu-id 2");
|
||||
assert.containsOnce(dialog, 'ul.oe_menu_editor > li > div button.js_edit_menu',
|
||||
"there should be a button to edit the menu");
|
||||
assert.containsOnce(dialog, 'ul.oe_menu_editor > li > div button.js_delete_menu',
|
||||
"there should be a button to remove the menu");
|
||||
assert.containsN(dialog, 'ul.oe_menu_editor > li > ul > li', 2,
|
||||
"there should be two submenus");
|
||||
assert.containsOnce($modal, '.js_add_menu',
|
||||
"there should be a link to add new menu");
|
||||
|
||||
// open the dialog to create a new menu
|
||||
await testUtils.dom.click($modal.find('.js_add_menu'));
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual($('.o_web_studio_add_menu_modal').length, 1,
|
||||
"there should be a modal in the dom");
|
||||
assert.strictEqual($('.o_web_studio_add_menu_modal input[name="name"]').length, 1,
|
||||
"there should be an input for the name in the dialog");
|
||||
await testUtils.dom.click($('.o_web_studio_add_menu_modal .o_field_widget[name="model_choice"] [data-value="new"]'));
|
||||
assert.isNotVisible($('.o_web_studio_add_menu_modal .o_field_many2one'),
|
||||
"there should be no visible many2one for the model in the dialog");
|
||||
// Define a name for new model
|
||||
$('input[name="name"]').val("new_model");
|
||||
await testUtils.dom.click($('.o_web_studio_add_menu_modal .btn-primary'));
|
||||
assert.containsOnce($, '.o_web_studio_model_configurator input[name="use_partner"]',
|
||||
"the ModelConfigurator should show the available model options");
|
||||
await testUtils.dom.click($('.o_web_studio_model_configurator .o_web_studio_model_configurator_previous'));
|
||||
assert.containsNone($, '.o_web_studio_model_configurator',
|
||||
"the ModelConfigurator should be gone");
|
||||
await testUtils.dom.click($('.o_web_studio_add_menu_modal .o_field_widget[name="model_choice"] [data-value="existing"]'));
|
||||
assert.strictEqual($('.o_web_studio_add_menu_modal .o_field_many2one').filter(':visible').length, 1,
|
||||
"there should be a visible many2one for the model in the dialog");
|
||||
|
||||
// add menu and close the modal
|
||||
await testUtils.fields.editInput($('.o_web_studio_add_menu_modal input[name="name"]'), "AA");
|
||||
await testUtils.dom.click($('.o_web_studio_add_menu_modal .btn-primary'));
|
||||
await testUtils.dom.click($('.o_web_studio_add_menu_modal .btn-secondary'));
|
||||
|
||||
// move submenu above root menu
|
||||
await testUtils.dom.dragAndDrop(dialog.$('li li .input-group:first'), dialog.$('.input-group:first'));
|
||||
assert.strictEqual(dialog.to_move[2].sequence, dialog.to_move[21].sequence + 1,
|
||||
"Root menu is after moved submenu");
|
||||
|
||||
// open the dialog to edit the menu
|
||||
await testUtils.dom.click(dialog.$('.js_edit_menu:nth(1)'));
|
||||
assert.strictEqual($('.o_dialog .o_form_view').length, 1,
|
||||
"there should be a form view dialog in the dom");
|
||||
assert.strictEqual($('.o_dialog .o_form_view .o_field_widget[name="name"] input').val(), "Menu 2",
|
||||
"the edited menu should be menu 2");
|
||||
// confirm the edition
|
||||
assert.strictEqual(customizeCalls, 0, "current changes have not been saved");
|
||||
await testUtils.dom.click($('.o_dialog .o_form_button_save'));
|
||||
assert.strictEqual(customizeCalls, 1, "current changes are saved after editing a menu");
|
||||
|
||||
// delete the last menu
|
||||
await testUtils.dom.click(dialog.$('.js_delete_menu:nth(2)'));
|
||||
assert.containsNone(dialog, 'ul.oe_menu_editor > li > ul > li',
|
||||
"there should be no submenu after deletion");
|
||||
assert.strictEqual(dialog.to_delete.length, 1,
|
||||
"there should be one menu to delete");
|
||||
|
||||
await testUtils.dom.click(dialog.$('.js_delete_menu:first'));
|
||||
await testUtils.dom.click(dialog.$('.js_delete_menu:first'));
|
||||
await testUtils.dom.click($modal.find('footer .btn-primary'));
|
||||
assert.containsN($(document), '.modal', 2,
|
||||
"should have 2 dialogs");
|
||||
assert.strictEqual($('.modal:not(.o_inactive_modal) .modal-title').text(), "Alert",
|
||||
"should have alert dialog if we delete all menus");
|
||||
|
||||
dialog.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
51
web_studio/static/tests/legacy/mock_server.js
Normal file
51
web_studio/static/tests/legacy/mock_server.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import MockServer from 'web.MockServer';
|
||||
|
||||
MockServer.include({
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
MockServer.currentMockServer = this;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_performRpc: function (route) {
|
||||
if (route === '/web_studio/get_default_value') {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
if (route === '/web_studio/activity_allowed') {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Mocks method "_return_view" that generates the return value of a call
|
||||
* to edit_view. It's basically an object similar to the result of a call
|
||||
* to get_views. It is used in mockRPC functions that mock edit_view calls.
|
||||
*
|
||||
* @param {string} arch
|
||||
* @param {string} model
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockReturnView(arch, model) {
|
||||
const view = this.getView({ arch, model });
|
||||
const models = {};
|
||||
for (const modelName of view.models) {
|
||||
models[modelName] = this.fieldsGet(modelName);
|
||||
}
|
||||
return Promise.resolve({
|
||||
models,
|
||||
views: { [view.type]: view },
|
||||
});
|
||||
},
|
||||
});
|
||||
54
web_studio/static/tests/legacy/new_model_tests.js
Normal file
54
web_studio/static/tests/legacy/new_model_tests.js
Normal file
@@ -0,0 +1,54 @@
|
||||
odoo.define('web_studio.NewModeltests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const NewModel = require('web_studio.NewModel');
|
||||
const testUtils = require('web.test_utils');
|
||||
|
||||
QUnit.module('Studio', function () {
|
||||
|
||||
QUnit.module('NewModel');
|
||||
|
||||
QUnit.test('Add New Model', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const $target = $('#qunit-fixture');
|
||||
|
||||
const newModel = new NewModel.NewModelItem();
|
||||
await newModel.appendTo($target);
|
||||
|
||||
testUtils.mock.addMockEnvironment(newModel, {
|
||||
mockRPC: function (route, args) {
|
||||
if (route === "/web_studio/create_new_menu") {
|
||||
assert.strictEqual(args.menu_name, "ABCD", "Model name should be ABCD.")
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsNone($, '.o_web_studio_new_model_modal',
|
||||
"there should not be any modal in the dom");
|
||||
assert.containsOnce(newModel, '.o_web_create_new_model',
|
||||
"there should be an add new model link");
|
||||
|
||||
await testUtils.dom.click($('.o_web_create_new_model'));
|
||||
assert.containsOnce($, '.o_web_studio_new_model_modal',
|
||||
"there should be a modal in the dom");
|
||||
const $modal = $('.modal');
|
||||
assert.containsOnce($modal, 'input[name="name"]',
|
||||
"there should be an input for the name in the dialog");
|
||||
|
||||
await testUtils.fields.editInput($modal.find('input[name="name"]'), "ABCD");
|
||||
await testUtils.dom.click($modal.find('.btn-primary'));
|
||||
const $configuratorModal = $('.o_web_studio_model_configurator');
|
||||
assert.containsOnce($configuratorModal, 'input[name="use_partner"]',
|
||||
"the ModelConfigurator should show the available model options");
|
||||
|
||||
await testUtils.dom.click($configuratorModal.find('.o_web_studio_model_configurator_next'));
|
||||
assert.containsNone($, '.o_web_studio_model_configurator',
|
||||
"the ModelConfigurator should be gone");
|
||||
|
||||
newModel.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
odoo.define('web_studio.ReportEditorAction_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const { controlPanel } = require('web.test_utils');
|
||||
const { getPagerValue, pagerNext } = controlPanel;
|
||||
|
||||
const { getFixture } = require("@web/../tests/helpers/utils");
|
||||
const { doAction } = require("@web/../tests/webclient/helpers");
|
||||
const { openStudio, registerStudioDependencies,getReportServerData } = require("@web_studio/../tests/helpers");
|
||||
const { createEnterpriseWebClient } = require("@web_enterprise/../tests/helpers");
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
QUnit.module('Studio', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
foo: {
|
||||
fields: {},
|
||||
records: [{ id: 22 }, { id: 23 }],
|
||||
},
|
||||
"ir.actions.report": {
|
||||
fields: { model: { type: "char" }, report_name: { type: "char" }, report_type:{ type: "char" }},
|
||||
records: [{ id: 11, model: "foo", report_name: "foo_report", report_type: "pdf" }],
|
||||
},
|
||||
"ir.model": {
|
||||
fields: {},
|
||||
},
|
||||
};
|
||||
const reportServerData = getReportServerData();
|
||||
const actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
xml_id: "kikou.action",
|
||||
name: 'Kikou Action',
|
||||
res_model: 'foo',
|
||||
type: 'ir.actions.act_window',
|
||||
view_mode: 'list,form',
|
||||
views: [[1, 'form']],
|
||||
}
|
||||
};
|
||||
const views = Object.assign({
|
||||
"foo,2,form": `<form><field name="display_name" /></form>`,
|
||||
"foo,false,search": `<search />`,
|
||||
}, reportServerData.views);
|
||||
serverData = {actions, models: this.data, views};
|
||||
Object.assign(serverData.models, reportServerData.models);
|
||||
registerStudioDependencies();
|
||||
target = getFixture();
|
||||
},
|
||||
}, function () {
|
||||
QUnit.module('ReportEditorAction');
|
||||
|
||||
QUnit.test('use pager', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const reportHTML = `
|
||||
<html>
|
||||
<head/>
|
||||
<body>
|
||||
<div id="wrapwrap">
|
||||
<main>
|
||||
<div class="page"/>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
switch (route) {
|
||||
case "/web_studio/get_report_views":
|
||||
return { report_html: reportHTML };
|
||||
case "/web_studio/get_widgets_available_options":
|
||||
case "/web_studio/read_paperformat":
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const webClient = await createEnterpriseWebClient({ serverData, mockRPC });
|
||||
await doAction(webClient, 1);
|
||||
await openStudio(target, {report: 11});
|
||||
|
||||
assert.strictEqual(getPagerValue(target), "1");
|
||||
await pagerNext(target);
|
||||
assert.strictEqual(getPagerValue(target), "2");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,800 @@
|
||||
odoo.define('web_studio.ReportEditorComponents_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
var studioTestUtils = require('web_studio.testUtils');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var studioTestUtils = require('web_studio.testUtils');
|
||||
var editComponentsRegistry = require('web_studio.reportEditComponentsRegistry');
|
||||
var reportNewComponentsRegistry = require('web_studio.reportNewComponentsRegistry');
|
||||
|
||||
|
||||
QUnit.module('Studio', {}, function () {
|
||||
|
||||
QUnit.module('ReportComponents', {
|
||||
before: function() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
studioTestUtils.createSidebar({}).then(function (sidebar) {
|
||||
sidebar.destroy();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
beforeEach: function () {
|
||||
this.widgetsOptions = {
|
||||
monetary: {
|
||||
company_id: {
|
||||
type: "model",
|
||||
string: "Company",
|
||||
description: "Company used for the original currency (only used for t-esc)",
|
||||
default_value: "Company used to render the template",
|
||||
params: "res.company"
|
||||
},
|
||||
date: {
|
||||
type: "date",
|
||||
string: "Date",
|
||||
description: "Date used for the original currency (only used for t-esc)",
|
||||
default_value: "Current date"
|
||||
},
|
||||
from_currency: {
|
||||
type: "model",
|
||||
string: "Original currency",
|
||||
params: "res.currency"
|
||||
},
|
||||
display_currency: {
|
||||
type: "model",
|
||||
string: "Display currency",
|
||||
required: "value_to_html",
|
||||
params: "res.currency"
|
||||
}
|
||||
},
|
||||
relative: {
|
||||
now: {
|
||||
type: "datetime",
|
||||
string: "Reference date",
|
||||
description: "Date to compare with the field value.",
|
||||
default_value: "Current date"
|
||||
}
|
||||
},
|
||||
image: {},
|
||||
text: {},
|
||||
html: {},
|
||||
many2many: {},
|
||||
date: {
|
||||
format: {
|
||||
type: "string",
|
||||
string: "Date format"
|
||||
}
|
||||
},
|
||||
datetime: {
|
||||
time_only: {
|
||||
type: "boolean",
|
||||
string: "Display only the time"
|
||||
},
|
||||
hide_seconds: {
|
||||
type: "boolean",
|
||||
string: "Hide seconds"
|
||||
},
|
||||
format: {
|
||||
type: "string",
|
||||
string: "Pattern to format"
|
||||
}
|
||||
},
|
||||
qweb: {},
|
||||
many2one: {},
|
||||
integer: {},
|
||||
float_time: {},
|
||||
contact: {
|
||||
separator: {
|
||||
type: "selection",
|
||||
params : {
|
||||
type: "selection",
|
||||
selection: [
|
||||
[" ", "Space"],
|
||||
[",", "Comma"],
|
||||
["-", "Dash"],
|
||||
["|", "Vertical bar"],
|
||||
["/", "Slash"]
|
||||
],
|
||||
placeholder: 'Linebreak',
|
||||
},
|
||||
string: "Address separator",
|
||||
description: "Separator use to split the addresse from the display_name.",
|
||||
default_value: false,
|
||||
},
|
||||
no_marker: {
|
||||
type: "boolean",
|
||||
string: "Hide marker",
|
||||
description: "Don't display the font awsome marker"
|
||||
},
|
||||
country_image: {
|
||||
type: "boolean",
|
||||
string: "Displayed contry image",
|
||||
description: "Display the country image if the field is present on the record"
|
||||
},
|
||||
fields: {
|
||||
type: "array",
|
||||
string: "Displayed fields",
|
||||
description: "List of contact fields to display in the widget",
|
||||
default_value: [
|
||||
"name",
|
||||
"address",
|
||||
"phone",
|
||||
"mobile",
|
||||
"email"
|
||||
],
|
||||
params: {
|
||||
type: "selection",
|
||||
params: [
|
||||
{'field_name': 'name', 'label': 'Name'},
|
||||
{'field_name': 'address', 'label': 'Address'},
|
||||
{'field_name': 'phone', 'label': 'Phone'},
|
||||
{'field_name': 'mobile', 'label': 'Mobile'},
|
||||
{'field_name': 'email', 'label': 'Email'},
|
||||
{'field_name': 'vat', 'label': 'VAT'},
|
||||
]
|
||||
}
|
||||
},
|
||||
no_tag_br: {
|
||||
type: "boolean",
|
||||
string: "Use comma",
|
||||
description: "Use comma instead of the <br> tag to display the address"
|
||||
},
|
||||
phone_icons: {
|
||||
type: "boolean",
|
||||
string: "Displayed phone icons",
|
||||
description: "Display the phone icons even if no_marker is True"
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
unit: {
|
||||
type: "select",
|
||||
string: "Date unit",
|
||||
description: "Date unit used for comparison and formatting",
|
||||
default_value: "hour",
|
||||
params: [
|
||||
[ "year", "year" ],
|
||||
[ "month", "month" ],
|
||||
[ "week", "week" ],
|
||||
[ "day", "day" ],
|
||||
[ "hour", "hour" ],
|
||||
[ "minute", "minute" ],
|
||||
[ "second", "second" ]
|
||||
]
|
||||
},
|
||||
round: {
|
||||
type: "select",
|
||||
string: "Rounding unit",
|
||||
description: "Date unit used for the rounding. If the value is given, this must be smaller than the unit",
|
||||
default_value: "Same unit as \"unit\" option",
|
||||
params: [
|
||||
[ "year", "year" ],
|
||||
[ "month", "month" ],
|
||||
[ "week", "week" ],
|
||||
[ "day", "day" ],
|
||||
[ "hour", "hour" ],
|
||||
[ "minute", "minute" ],
|
||||
[ "second", "second" ]
|
||||
]
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
selection: {
|
||||
type: "selection",
|
||||
string: "Selection",
|
||||
default_value: "Use the field information",
|
||||
required: true
|
||||
}
|
||||
},
|
||||
barcode: {
|
||||
type: {
|
||||
type: "string",
|
||||
string: "Barcode type",
|
||||
description: "Barcode type, eg: UPCA, EAN13, Code128",
|
||||
default_value: "Code128"
|
||||
},
|
||||
width: {
|
||||
type: "integer",
|
||||
string: "Width",
|
||||
default_value: 600
|
||||
},
|
||||
height: {
|
||||
type: "integer",
|
||||
string: "Height",
|
||||
default_value: 100
|
||||
},
|
||||
humanreadable: {
|
||||
type: "integer",
|
||||
string: "Human Readable",
|
||||
default_value: 0
|
||||
}
|
||||
},
|
||||
float: {
|
||||
precision: {
|
||||
type: "integer",
|
||||
string: "Rounding precision"
|
||||
}
|
||||
}
|
||||
};
|
||||
this.data = studioTestUtils.getData({
|
||||
'model.test': {
|
||||
fields: {
|
||||
name: {string: "Name", type: "char"},
|
||||
image: {string: "Image", type: "binary"},
|
||||
child: {string: "Child", type: 'many2one', relation: 'model.test.child', searchable: true},
|
||||
child_bis: {string: "Child Bis", type: 'many2one', relation: 'model.test.child', searchable: true},
|
||||
children: {string: "Children", type: 'many2many', relation: 'model.test.child', searchable: true},
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
'model.test.child': {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
company_id: { string: "Company", type: "many2one", relation: 'res.company', searchable: true },
|
||||
currency_id: { string: "Currency", type: "many2one", relation: 'res.currency', searchable: true },
|
||||
date: { string: "Date", type: "datetime", searchable: true },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
'res.company': {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
'res.currency': {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
});
|
||||
studioTestUtils.patch();
|
||||
},
|
||||
afterEach: function () {
|
||||
studioTestUtils.unpatch();
|
||||
},
|
||||
}, function () {
|
||||
QUnit.module('New');
|
||||
|
||||
QUnit.test('field', async function (assert) {
|
||||
assert.expect(2);
|
||||
var parent = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
data: this.data,
|
||||
});
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
var InlineField = reportNewComponentsRegistry.get('Inline')[1];
|
||||
|
||||
var tOptions = new InlineField(parent, {
|
||||
models: {
|
||||
'model.test': 'Toto',
|
||||
},
|
||||
});
|
||||
|
||||
tOptions.add({
|
||||
targets: [{
|
||||
data: {},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
contextOrder: ['toto'],
|
||||
context: {
|
||||
toto: 'model.test',
|
||||
},
|
||||
parent: {
|
||||
children: [],
|
||||
attrs: {},
|
||||
}
|
||||
},
|
||||
}]
|
||||
}).then(function (res) {
|
||||
assert.deepEqual(res.inheritance,
|
||||
[{content: '<span t-field="toto.child"></span>', xpath: '/my/node/path/', view_id: 99, position: undefined}],
|
||||
"Should send the operation");
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
|
||||
await testUtils.dom.triggerEvents($('.o_web_studio_field_modal .o_field_selector'), ['focus']);
|
||||
await testUtils.dom.click($('.o_web_studio_field_modal .o_field_selector_close'));
|
||||
await testUtils.dom.click($('.o_web_studio_field_modal .btn-primary'));
|
||||
|
||||
assert.strictEqual($('.modal main[role="alert"]').length, 1,
|
||||
"Should display an alert because the field name of the record is wrong");
|
||||
await testUtils.dom.click($('.modal:has(main[role="alert"]) .btn-primary'));
|
||||
|
||||
await testUtils.dom.triggerEvents($('.o_web_studio_field_modal .o_field_selector'), ['focus']);
|
||||
await testUtils.dom.click($('.o_web_studio_field_modal .o_field_selector_item[data-name="child"]'));
|
||||
await testUtils.dom.click($('.o_web_studio_field_modal .btn-primary'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('add a binary field', async function (assert) {
|
||||
assert.expect(1);
|
||||
var parent = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
data: this.data,
|
||||
});
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
var InlineField = reportNewComponentsRegistry.get('Inline')[1];
|
||||
var tOptions = new InlineField(parent, {
|
||||
models: {
|
||||
'model.test': 'Kikou',
|
||||
},
|
||||
});
|
||||
|
||||
tOptions.add({
|
||||
targets: [{
|
||||
data: {},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
contextOrder: ['toto'],
|
||||
context: {
|
||||
toto: 'model.test',
|
||||
},
|
||||
parent: {
|
||||
children: [],
|
||||
attrs: {},
|
||||
}
|
||||
},
|
||||
}]
|
||||
}).then(function (res) {
|
||||
assert.deepEqual(res.inheritance,
|
||||
[{content: '<span t-field="toto.image" t-options-widget=""image""></span>', xpath: '/my/node/path/', view_id: 99, position: undefined}],
|
||||
"image widget should be set");
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
|
||||
await testUtils.dom.triggerEvents($('.o_web_studio_field_modal .o_field_selector'), ['focus']);
|
||||
await testUtils.dom.click($('.o_web_studio_field_modal .o_field_selector_item[data-name="image"]'));
|
||||
await testUtils.dom.click($('.o_web_studio_field_modal .btn-primary'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('Edit');
|
||||
|
||||
QUnit.test('column component with valid classes', async function (assert) {
|
||||
assert.expect(2);
|
||||
var parent = new Widget();
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
var column = new (editComponentsRegistry.get('column'))(parent, {
|
||||
node: {
|
||||
attrs: {
|
||||
class: 'col-5 offset-3',
|
||||
},
|
||||
},
|
||||
});
|
||||
await column.appendTo(parent.$el);
|
||||
|
||||
assert.strictEqual(column.$('input[name="size"]').val(), "5",
|
||||
"the size should be correctly set");
|
||||
assert.strictEqual(column.$('input[name="offset"]').val(), "3",
|
||||
"the offset should be correctly set");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('column component with invalid classes', async function (assert) {
|
||||
assert.expect(2);
|
||||
var parent = new Widget();
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
var column = new (editComponentsRegistry.get('column'))(parent, {
|
||||
node: {
|
||||
attrs: {
|
||||
class: 'col- offset-kikou',
|
||||
},
|
||||
},
|
||||
});
|
||||
await column.appendTo(parent.$el);
|
||||
|
||||
assert.strictEqual(column.$('input[name="size"]').val(), "",
|
||||
"the size should be unkown");
|
||||
assert.strictEqual(column.$('input[name="offset"]').val(), "",
|
||||
"the offset should be unkown");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('hidden "width" for layout component with col nodes', async function (assert) {
|
||||
assert.expect(1);
|
||||
var parent = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parent, this);
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
var layout = new (editComponentsRegistry.get('layout'))(parent, {
|
||||
node: {
|
||||
tag: 'div',
|
||||
attrs: {
|
||||
class: 'col- offset-kikou',
|
||||
},
|
||||
$nodes: $(),
|
||||
},
|
||||
});
|
||||
await layout.appendTo(parent.$el);
|
||||
|
||||
assert.containsNone(layout.$('.o_web_studio_width'),
|
||||
"the width attribute shouldn't be displayed for div.col nodes");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('tOptions component', async function (assert) {
|
||||
assert.expect(3);
|
||||
var parent = new Widget();
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
var tOptions = new (editComponentsRegistry.get('tOptions'))(parent, {
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
node: {
|
||||
attrs: {
|
||||
't-options': '{"widget": "text"}',
|
||||
't-options-widget': '"image"',
|
||||
't-options-other-options': 'True',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: null,
|
||||
});
|
||||
await tOptions.appendTo(parent.$el);
|
||||
assert.strictEqual(tOptions.$('select').val(), 'image',
|
||||
"Should select the image widget");
|
||||
assert.containsNone(tOptions, '.o_web_studio_toption_option',
|
||||
"there should be no available option");
|
||||
|
||||
// unset the `widget`
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
intercepts: {
|
||||
view_change: function (ev) {
|
||||
assert.deepEqual(ev.data.operation.new_attrs, {'t-options-widget': '""'},
|
||||
"should correctly delete the group");
|
||||
},
|
||||
},
|
||||
});
|
||||
tOptions.$('select').val('').trigger('change');
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('tOptions component parse expression', async function (assert) {
|
||||
assert.expect(5);
|
||||
var parent = new Widget();
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
|
||||
var fields = this.data['model.test.child'].fields;
|
||||
fields.company_id = {string: "Company", type: "many2one", relation: 'res.company', searchable: true};
|
||||
fields.currency_id = {string: "Currency", type: "many2one", relation: 'res.currency', searchable: true};
|
||||
fields.date = {string: "Date", type: "datetime", searchable: true};
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
data: this.data,
|
||||
});
|
||||
|
||||
var tOptions = new (editComponentsRegistry.get('tOptions'))(parent, {
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
node: {
|
||||
attrs: {
|
||||
't-options': 'dict(from_currency=o.child.currency_id, date=o.child.date)',
|
||||
't-options-widget': '"monetary"',
|
||||
't-options-company_id': 'o.child.company_id',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
context: {"o": "model.test"},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: {"model.test": "Model Test"},
|
||||
});
|
||||
|
||||
await tOptions.appendTo(parent.$el);
|
||||
assert.strictEqual(tOptions.$('select').val(), 'monetary',
|
||||
"Should select the image widget");
|
||||
assert.containsN(tOptions, '.o_web_studio_toption_option', 4,
|
||||
"there should be 4 available options for the monetary widget");
|
||||
assert.strictEqual(tOptions.$('.o_web_studio_toption_option_monetary_from_currency .o_field_selector_value').text().replace(/\s+/g, ''),
|
||||
"o(ModelTest)ChildCurrency",
|
||||
"Should display the currency field");
|
||||
assert.strictEqual(tOptions.$('.o_web_studio_toption_option_monetary_date .o_field_selector_value').text().replace(/\s+/g, ''),
|
||||
"o(ModelTest)ChildDate",
|
||||
"Should display the data field");
|
||||
assert.strictEqual(tOptions.$('.o_web_studio_toption_option_monetary_company_id .o_field_selector_value').text().replace(/\s+/g, ''),
|
||||
"o(ModelTest)ChildCompany",
|
||||
"Should display the company field");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('tEsc component with parsable expression', async function (assert) {
|
||||
assert.expect(1);
|
||||
var parent = new Widget();
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
data: this.data,
|
||||
});
|
||||
|
||||
var tOptions = new (editComponentsRegistry.get('tEsc'))(parent, {
|
||||
node: {
|
||||
attrs: {
|
||||
't-esc': 'o.child.company_id',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
context: {"o": "model.test"},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: {"model.test": "Model Test"},
|
||||
});
|
||||
await tOptions.appendTo(parent.$el);
|
||||
await testUtils.nextTick();
|
||||
// the component value is parsable so we display it with ModelFieldSelector
|
||||
assert.strictEqual(tOptions.$('.o_field_selector_value').text().replace(/\s+/g, ''),
|
||||
"o(ModelTest)ChildCompany",
|
||||
"Should display the company field");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('tEsc component with non-parsable expression', async function (assert) {
|
||||
assert.expect(1);
|
||||
var parent = new Widget();
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
data: this.data,
|
||||
});
|
||||
|
||||
var tOptions = new (editComponentsRegistry.get('tEsc'))(parent, {
|
||||
node: {
|
||||
attrs: {
|
||||
't-esc': 'o.child.getCompany()',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
context: {"o": "model.test"},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: {"model.test": "Model Test"},
|
||||
});
|
||||
await tOptions.appendTo(parent.$el);
|
||||
await testUtils.nextTick();
|
||||
// the component can not parse the value so we display a simple input
|
||||
assert.strictEqual(tOptions.$('input[name="t-esc"]').val(),
|
||||
"o.child.getCompany()",
|
||||
"Should display the company field");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('contact: many2many_select', async function (assert) {
|
||||
assert.expect(11);
|
||||
var parent = new Widget();
|
||||
|
||||
$('ul.ui-autocomplete').remove(); // clean the body to avoid errors due to another test
|
||||
|
||||
var optionsFields;
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
intercepts: {
|
||||
view_change: function (ev) {
|
||||
assert.deepEqual(ev.data.operation.new_attrs['t-options-fields'], optionsFields,
|
||||
'Should save the contact options');
|
||||
|
||||
params.node.attrs['t-options-fields'] = JSON.stringify(ev.data.operation.new_attrs['t-options-fields']);
|
||||
},
|
||||
},
|
||||
});
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
|
||||
var params = {
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
node: {
|
||||
attrs: {
|
||||
't-options': '{"widget": "contact"}',
|
||||
't-options-no_marker': 'True',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: null,
|
||||
};
|
||||
|
||||
var tOptions = new (editComponentsRegistry.get('tOptions'))(parent, params);
|
||||
await tOptions.appendTo(parent.$el);
|
||||
assert.containsN(tOptions, '.o_web_studio_toption_option', 3,
|
||||
"there should be 3 available options for the contact widget (they are filtered)");
|
||||
assert.strictEqual(tOptions.$('.o_badge_text').text(), 'NameAddressPhoneMobileEmail', 'Should display default value');
|
||||
await testUtils.dom.click(tOptions.$('.o_input_dropdown input'));
|
||||
assert.strictEqual($('ul.ui-autocomplete .ui-menu-item').length, 1, 'Should not display the unselected items');
|
||||
assert.strictEqual($('ul.ui-autocomplete .o_m2o_dropdown_option').length, 0, 'Should not display create button');
|
||||
|
||||
optionsFields = ["name", "address", "phone", "mobile", "email", "vat"];
|
||||
await testUtils.dom.click($('ul.ui-autocomplete .ui-menu-item:contains(VAT)'));
|
||||
tOptions.destroy();
|
||||
|
||||
tOptions = new (editComponentsRegistry.get('tOptions'))(parent, params);
|
||||
await tOptions.appendTo(parent.$el);
|
||||
assert.strictEqual(tOptions.$('.o_badge_text').text(), 'NameAddressPhoneMobileEmailVAT', 'Should display the new value');
|
||||
await testUtils.dom.click(tOptions.$('.o_input_dropdown input'));
|
||||
assert.strictEqual($('ul.ui-autocomplete .ui-menu-item').length, 0, 'Should not display the unselected items');
|
||||
await testUtils.dom.click(tOptions.$('.o_input_dropdown input'));
|
||||
|
||||
optionsFields = ["address", "phone", "mobile", "email", "vat"];
|
||||
await testUtils.dom.click(tOptions.$('.o_field_many2manytags .o_delete:first'));
|
||||
assert.strictEqual(tOptions.$('.o_badge_text').text(), 'AddressPhoneMobileEmailVAT', 'Should display the new value without "name"');
|
||||
|
||||
optionsFields = ["phone", "mobile", "email", "vat"];
|
||||
await testUtils.dom.click(tOptions.$('.o_field_many2manytags .o_delete:first'));
|
||||
assert.strictEqual(tOptions.$('.o_badge_text').text(), 'PhoneMobileEmailVAT', 'Should display the new value without "address"');
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('contact: address separator', async function (assert) {
|
||||
assert.expect(3);
|
||||
var parent = new Widget();
|
||||
|
||||
var addressSeparator;
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
intercepts: {
|
||||
view_change: function (ev) {
|
||||
assert.strictEqual(ev.data.operation.new_attrs['t-options-separator'], addressSeparator,
|
||||
'Should save the selected address separator');
|
||||
},
|
||||
},
|
||||
});
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
|
||||
var params = {
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
node: {
|
||||
attrs: {
|
||||
't-options': '{"widget": "contact"}',
|
||||
't-options-no_marker': 'True',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: null,
|
||||
};
|
||||
|
||||
var tOptions = new (editComponentsRegistry.get('tOptions'))(parent, params);
|
||||
await tOptions.appendTo(parent.$el);
|
||||
var separators = _.map(tOptions.$('.o_web_studio_toption_option_contact_separator .o_field_widget option'), function (option) {
|
||||
return JSON.parse($(option).val());
|
||||
});
|
||||
assert.deepEqual(separators, [false, " ", ",", "-", "|", "/"], 'There should be a selection field with proper values');
|
||||
assert.strictEqual(tOptions.$('.o_web_studio_toption_option_contact_separator .o_field_widget option:selected').text(), 'Linebreak',
|
||||
'Default value should be "LineBrak"');
|
||||
addressSeparator = '","';
|
||||
await testUtils.fields.editSelect(tOptions.$('.o_web_studio_toption_option_contact_separator .o_field_widget'), addressSeparator);
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('contact: no_marker boolean field', async function (assert) {
|
||||
assert.expect(2);
|
||||
var parent = new Widget();
|
||||
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
intercepts: {
|
||||
view_change: function (ev) {
|
||||
assert.strictEqual(ev.data.operation.new_attrs["t-options-no_marker"], false,
|
||||
'Toggling no_marker checkbox should change the option value');
|
||||
},
|
||||
},
|
||||
});
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
|
||||
var params = {
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
node: {
|
||||
attrs: {
|
||||
't-options': '{"widget": "contact"}',
|
||||
't-options-no_marker': 'True',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: null,
|
||||
};
|
||||
var tOptions = new (editComponentsRegistry.get('tOptions'))(parent, params);
|
||||
await tOptions.appendTo(parent.$el);
|
||||
|
||||
assert.containsOnce(tOptions, '.o_web_studio_toption_option_contact_no_marker input:checked',
|
||||
"no_marker checkbox is checked initially");
|
||||
await testUtils.dom.click(tOptions.$('.o_web_studio_toption_option_contact_no_marker input'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('no search more in many2many_select', async function (assert) {
|
||||
assert.expect(3);
|
||||
var parent = new Widget();
|
||||
|
||||
$('ul.ui-autocomplete').remove(); // clean the body to avoid errors due to another test
|
||||
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
|
||||
// to display more options in the many2many_select
|
||||
this.widgetsOptions.contact.fields.default_value = [];
|
||||
|
||||
var tOptions = new (editComponentsRegistry.get('tOptions'))(parent, {
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
node: {
|
||||
attrs: {
|
||||
't-options': '{"widget": "contact"}',
|
||||
't-options-no_marker': 'True',
|
||||
'data-oe-id': 99,
|
||||
'data-oe-xpath': '/my/node/path/',
|
||||
},
|
||||
},
|
||||
context: {},
|
||||
state: null,
|
||||
models: null,
|
||||
});
|
||||
await tOptions.appendTo(parent.$el);
|
||||
|
||||
assert.strictEqual(tOptions.$('.o_badge_text').text(), '', 'Should display default value');
|
||||
await testUtils.dom.click(tOptions.$('.o_input_dropdown input'));
|
||||
assert.strictEqual($('ul.ui-autocomplete .ui-menu-item').length, 6 , 'Should not display the unselected items');
|
||||
assert.strictEqual($('ul.ui-autocomplete .o_m2o_dropdown_option').length, 0, 'Should not display create button nor the search more');
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('groups component', async function (assert) {
|
||||
assert.expect(3);
|
||||
var parent = new Widget();
|
||||
await parent.appendTo($('#qunit-fixture'));
|
||||
var groups = new (editComponentsRegistry.get('groups'))(parent, {
|
||||
widgets: this.widgets,
|
||||
node: {
|
||||
tag: 'span',
|
||||
attrs: {
|
||||
studio_groups: "[" +
|
||||
"{\"name\": \"group_A\", \"display_name\": \"My Awesome Group\", \"id\": 42}," +
|
||||
"{\"name\": \"group_13\", \"display_name\": \"Kikou\", \"id\": 13}" +
|
||||
"]",
|
||||
},
|
||||
},
|
||||
});
|
||||
await groups.appendTo(parent.$el);
|
||||
|
||||
assert.containsN(groups, '.o_field_many2manytags .o_badge_text', 2,
|
||||
"there should be displayed two groups");
|
||||
assert.strictEqual(groups.$('.o_field_many2manytags').text().replace(/\s/g, ''), "MyAwesomeGroupKikou",
|
||||
"the groups should be correctly set");
|
||||
|
||||
// delete a group
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
intercepts: {
|
||||
view_change: function (ev) {
|
||||
assert.deepEqual(ev.data.operation.new_attrs, {groups: [13]},
|
||||
"should correctly delete the group");
|
||||
},
|
||||
},
|
||||
});
|
||||
await testUtils.dom.click(groups.$('.o_field_many2manytags .o_delete:first'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,914 @@
|
||||
odoo.define('web_studio.ReportEditorSidebar_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const { nextTick } = require("@web/../tests/helpers/utils");
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var studioTestUtils = require('web_studio.testUtils');
|
||||
|
||||
QUnit.module('Studio', {}, function () {
|
||||
|
||||
QUnit.module('ReportEditorSidebar', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
'report.paperformat': {
|
||||
fields: {
|
||||
display_name: {string: "Name", type: "char"},
|
||||
},
|
||||
records: [{
|
||||
id: 42,
|
||||
display_name: 'My Awesome Format',
|
||||
}],
|
||||
},
|
||||
'res.groups': {
|
||||
fields: {
|
||||
display_name: {string: "Name", type: "char"},
|
||||
},
|
||||
records: [{
|
||||
id: 6,
|
||||
display_name: 'Group6',
|
||||
}, {
|
||||
id: 7,
|
||||
display_name: 'Group7',
|
||||
}],
|
||||
},
|
||||
'x_mymodel': {
|
||||
fields: {
|
||||
display_name: {string: "Name", type: "char"},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
this.widgetsOptions = {
|
||||
image: {},
|
||||
integer: {},
|
||||
text: {},
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
QUnit.test("basic rendering", async function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(5);
|
||||
|
||||
studioTestUtils.createSidebar({
|
||||
state: { mode: 'report' },
|
||||
report: {},
|
||||
}).then(async function (sidebar) {
|
||||
|
||||
assert.hasClass(sidebar.$('.o_web_studio_sidebar_header [name="report"]'),'active',
|
||||
"the report tab should be active");
|
||||
assert.hasClass(sidebar.$('.o_web_studio_sidebar_header [name="options"]'),'inactive',
|
||||
"the options tab should be inactive");
|
||||
|
||||
testUtils.mock.intercept(sidebar, 'sidebar_tab_changed', function (ev) {
|
||||
assert.step(ev.data.mode);
|
||||
});
|
||||
testUtils.dom.click(sidebar.$('.o_web_studio_sidebar_header [name="new"]'));
|
||||
assert.verifySteps(['new'], "the sidebar should be updated");
|
||||
|
||||
await testUtils.dom.click(sidebar.$('.o_web_studio_sidebar_header [name="options"]'));
|
||||
assert.verifySteps([], "one should not be able to select options");
|
||||
|
||||
sidebar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("'Report' tab behaviour", async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
return studioTestUtils.createSidebar({
|
||||
data: this.data,
|
||||
state: { mode: 'report' },
|
||||
report: {
|
||||
name: 'Kikou',
|
||||
},
|
||||
}).then(async function (sidebar) {
|
||||
|
||||
assert.hasAttrValue(sidebar.$('.o_web_studio_sidebar_header > .active'), 'name', "report",
|
||||
"the 'Report' tab should be active");
|
||||
assert.strictEqual(sidebar.$('input[name="name"]').val(), "Kikou",
|
||||
"the report name should be displayed");
|
||||
|
||||
testUtils.mock.intercept(sidebar, 'studio_edit_report', function (ev) {
|
||||
if (ev.data.name) {
|
||||
assert.deepEqual(ev.data, { name: "wow_report" });
|
||||
} else if ('paperformat_id' in ev.data) {
|
||||
paperformatValues.push(ev.data);
|
||||
} else if (ev.data.groups_id) {
|
||||
assert.deepEqual(ev.data, { groups_id: [7] });
|
||||
}
|
||||
});
|
||||
// edit report name
|
||||
sidebar.$('input[name="name"]').val("wow_report").trigger('change');
|
||||
|
||||
// edit the report paperformat
|
||||
var paperformatValues = [];
|
||||
await testUtils.fields.many2one.clickOpenDropdown('paperformat_id');
|
||||
await testUtils.fields.many2one.clickHighlightedItem('paperformat_id');
|
||||
assert.deepEqual(paperformatValues, [{ paperformat_id: 42 }]);
|
||||
|
||||
// remove the report paperformat
|
||||
sidebar.$('[name="paperformat_id"] input').val('').trigger('keyup').trigger('focusout');
|
||||
await testUtils.nextTick();
|
||||
assert.deepEqual(paperformatValues, [{ paperformat_id: 42 }, { paperformat_id: false }]);
|
||||
|
||||
// edit groups
|
||||
await testUtils.fields.many2one.clickOpenDropdown('groups_id');
|
||||
await testUtils.fields.many2one.clickItem('groups_id', 'Group7');
|
||||
|
||||
sidebar.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("'Add' tab behaviour", function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
studioTestUtils.createSidebar({
|
||||
state: { mode: 'new' },
|
||||
}).then(function (sidebar) {
|
||||
|
||||
assert.hasAttrValue(sidebar.$('.o_web_studio_sidebar_header > .active'), 'name', "new",
|
||||
"the 'Add' tab should be active");
|
||||
assert.ok(sidebar.$('.ui-draggable').length,
|
||||
"there should be draggable components");
|
||||
|
||||
sidebar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("basic 'Options' tab behaviour", function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(4);
|
||||
|
||||
var node = {
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
},
|
||||
};
|
||||
studioTestUtils.createSidebar({
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: [node],
|
||||
},
|
||||
}).then(function (sidebar) {
|
||||
|
||||
assert.hasAttrValue(sidebar.$('.o_web_studio_sidebar_header > .active'), 'name', "options",
|
||||
"the 'Options' tab should be active");
|
||||
assert.containsOnce(sidebar, '.o_web_studio_sidebar_content .collapse',
|
||||
"there should be one node in the accordion");
|
||||
assert.hasClass(sidebar.$('.o_web_studio_sidebar_content .collapse'),'show',
|
||||
"the node should be expanded by default");
|
||||
|
||||
// remove the element
|
||||
testUtils.mock.intercept(sidebar, 'element_removed', function (ev) {
|
||||
assert.deepEqual(ev.data.node, node.node);
|
||||
});
|
||||
testUtils.dom.click(sidebar.$('.o_web_studio_sidebar_content .collapse .o_web_studio_remove'));
|
||||
|
||||
sidebar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("'Options' tab with multiple nodes", async function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
var node1 = {
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
},
|
||||
};
|
||||
|
||||
var node2 = {
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '40',
|
||||
'data-oe-xpath': '/t/t',
|
||||
},
|
||||
tag: 'div',
|
||||
$nodes: $(),
|
||||
},
|
||||
};
|
||||
const sidebar = await studioTestUtils.createSidebar({
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: [node1, node2],
|
||||
},
|
||||
});
|
||||
|
||||
assert.hasAttrValue(sidebar.$('.o_web_studio_sidebar_header > .active'), 'name', "options",
|
||||
"the 'Options' tab should be active");
|
||||
assert.containsN(sidebar, '.o_web_studio_sidebar_content .card', 2,
|
||||
"there should be one node in the accordion");
|
||||
assert.hasClass(sidebar.$('.o_web_studio_sidebar_content .card:has(.o_text:contains(span)) .collapse'),'show',
|
||||
"the 'span' node should be expanded by default");
|
||||
assert.doesNotHaveClass(sidebar.$('.o_web_studio_sidebar_content .card:has(.o_text:contains(div)) .collapse'), 'show',
|
||||
"the 'div' node shouldn't be expanded");
|
||||
assert.strictEqual(sidebar.$('.o_web_studio_sidebar_content .o_web_studio_accordion > .card:last .card-header:first').text().trim(), "span",
|
||||
"the last node should be the span");
|
||||
|
||||
// expand the first node
|
||||
// BS4 collapsing is asynchronous
|
||||
await new Promise((resolve) => {
|
||||
$(document.body).one("hidden.bs.collapse", () => {
|
||||
resolve();
|
||||
});
|
||||
testUtils.dom.click(sidebar.$('.o_web_studio_sidebar_content .o_web_studio_accordion > .card:first [data-bs-toggle="collapse"]:first'));
|
||||
})
|
||||
// await end of transitions: https://getbootstrap.com/docs/5.0/components/collapse/#example
|
||||
await nextTick()
|
||||
assert.doesNotHaveClass(sidebar.$('.o_web_studio_sidebar_content .card:has(.o_text:contains(span)) .collapse:first'), 'show',
|
||||
"the 'span' node should have been closed");
|
||||
assert.hasClass(sidebar.$('.o_web_studio_sidebar_content .card:has(.o_text:contains(div)) .collapse:first'),'show',
|
||||
"the 'div' node should be expanded");
|
||||
|
||||
// reexpand the second node
|
||||
await new Promise((resolve) => {
|
||||
$(document.body).one("shown.bs.collapse", () => {
|
||||
resolve();
|
||||
});
|
||||
testUtils.dom.click(sidebar.$('.o_web_studio_sidebar_content .o_web_studio_accordion > .card:last [data-bs-toggle="collapse"]:first'));
|
||||
})
|
||||
await nextTick();
|
||||
assert.hasClass(sidebar.$('.o_web_studio_sidebar_content .card:has(.o_text:contains(span)) .collapse:first'),'show',
|
||||
"the 'span' node should be expanded again");
|
||||
assert.doesNotHaveClass(sidebar.$('.o_web_studio_sidebar_content .card:has(.o_text:contains(div)) .collapse:first'), 'show',
|
||||
"the 'div' node shouldn't be expanded anymore");
|
||||
|
||||
sidebar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("'Options' tab with layout component can be expanded", function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(3);
|
||||
|
||||
var node = {
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
},
|
||||
};
|
||||
studioTestUtils.createSidebar({
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: [node],
|
||||
},
|
||||
}).then(function (sidebar) {
|
||||
|
||||
assert.containsOnce(sidebar, '.o_web_studio_sidebar_content .collapse',
|
||||
"there should be one node in the accordion");
|
||||
assert.containsOnce(sidebar, '.o_web_studio_sidebar_content .o_web_studio_layout',
|
||||
"there should be a layout component");
|
||||
assert.containsOnce(sidebar, '.o_web_studio_sidebar_content .o_web_studio_layout .o_web_studio_margin',
|
||||
"there should be a margin section in the layout component");
|
||||
|
||||
sidebar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("'Options' tab with layout component can be expanded on open ", function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(1);
|
||||
|
||||
var node = {
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
},
|
||||
};
|
||||
studioTestUtils.createSidebar({
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: [node],
|
||||
},
|
||||
previousState: {
|
||||
"42/t/t/div": { 'layout': { showAll: true } }, // opens the layout expanded
|
||||
},
|
||||
}).then(function (sidebar) {
|
||||
|
||||
assert.equal(sidebar.$('.o_web_studio_width:visible').length, 1);
|
||||
|
||||
sidebar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("'Options' tab with layout component can be expanded on open with hierarchy", function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
var nodes = [
|
||||
{
|
||||
context: {'rec': 'int'},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/t/t/div',
|
||||
't-esc': 'rec',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
}
|
||||
},
|
||||
{
|
||||
context: {'rec': 'int'},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/t/t',
|
||||
't-if': 'o.is_ok',
|
||||
},
|
||||
tag: 'div',
|
||||
$nodes: $(),
|
||||
},
|
||||
},
|
||||
{
|
||||
context: {'rec': 'int'},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/t',
|
||||
't-else': '',
|
||||
},
|
||||
tag: 't',
|
||||
$nodes: $(),
|
||||
},
|
||||
},
|
||||
{
|
||||
context: {},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t',
|
||||
't-foreach': '5',
|
||||
't-as': 'rec',
|
||||
},
|
||||
tag: 't',
|
||||
$nodes: $(),
|
||||
},
|
||||
},
|
||||
{
|
||||
context: {},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t',
|
||||
't-name': 'my.template',
|
||||
},
|
||||
tag: 't',
|
||||
$nodes: $(),
|
||||
},
|
||||
},
|
||||
];
|
||||
nodes[0].node.parent = nodes[1].node;
|
||||
nodes[1].node.children = [nodes[0].node]
|
||||
nodes[1].node.parent = nodes[2].node;
|
||||
nodes[2].node.children = [nodes[1].node]
|
||||
nodes[2].node.parent = nodes[3].node;
|
||||
nodes[3].node.children = [{tag: 'span', attrs: {'t-if': 'false'}, $nodes: $(), parent: nodes[3].node}, nodes[2].node]
|
||||
nodes[3].node.parent = nodes[4].node;
|
||||
nodes[4].node.children = [nodes[3].node]
|
||||
nodes[4].node.parent = null;
|
||||
|
||||
studioTestUtils.createSidebar({
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: nodes,
|
||||
},
|
||||
previousState: {
|
||||
"42/t/t/t/t/div": { 'layout': { showAll: true } }, // opens the layout expanded
|
||||
},
|
||||
}).then(function (sidebar) {
|
||||
|
||||
assert.equal(sidebar.$('.card').length, 5, 'Should have 5 item');
|
||||
assert.equal(sidebar.$('.card h5 > button').get().map(el => el.textContent.replace(/[\s\n]+/g, ' ').trim()).join(' > '), 't > t [foreach="5"] > t > div > span [rec]', 'Should have all ordered parents');
|
||||
|
||||
sidebar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("'Options' tab with widget selection (tOptions) component", function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(4);
|
||||
|
||||
var node = {
|
||||
context: {
|
||||
'doc': 'x_mymodel',
|
||||
},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
't-field': 'doc.id',
|
||||
't-options-widget': '"text"',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
},
|
||||
};
|
||||
studioTestUtils.createSidebar({
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: [node],
|
||||
},
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
}).then(function (sidebar) {
|
||||
|
||||
assert.containsOnce(sidebar, '.o_web_studio_tfield_fieldexpression',
|
||||
"the t-field component should be displayed");
|
||||
assert.containsOnce(sidebar, '.o_web_studio_toption_widget',
|
||||
"the t-options component should be displayed");
|
||||
assert.strictEqual(sidebar.$('.o_web_studio_toption_widget select').text().replace(/\s/g, ''), "imageintegertext",
|
||||
"all widgets should be selectable");
|
||||
assert.strictEqual(sidebar.$('.o_web_studio_toption_widget select').val(), "text",
|
||||
"the correct widget should be selected");
|
||||
|
||||
sidebar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("'Options' tab with FieldSelector does not flicker", async function (assert) {
|
||||
assert.expect(3);
|
||||
var def = testUtils.makeTestPromise();
|
||||
|
||||
var node = {
|
||||
context: {
|
||||
'doc': 'x_mymodel',
|
||||
},
|
||||
node: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
't-field': 'doc.id',
|
||||
't-options-widget': '"text"',
|
||||
},
|
||||
context: {
|
||||
'doc': 'x_mymodel',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
},
|
||||
};
|
||||
var sidebarDef = studioTestUtils.createSidebar({
|
||||
data: this.data,
|
||||
models: {
|
||||
'x_mymodel': 'My Model',
|
||||
},
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: [node],
|
||||
},
|
||||
widgetsOptions: this.widgetsOptions,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.model === 'x_mymodel' && args.method === 'fields_get') {
|
||||
// Block the 'read' call
|
||||
var result = this._super.apply(this, arguments);
|
||||
return Promise.resolve(def).then(_.constant(result));
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual($('.o_web_studio_tfield_fieldexpression').length, 0,
|
||||
"the sidebar should wait its components to be rendered before its insertion");
|
||||
|
||||
// release the fields_get
|
||||
def.resolve();
|
||||
var sidebar = await sidebarDef;
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual($('.o_web_studio_tfield_fieldexpression').length, 1,
|
||||
"the t-field component should be displayed");
|
||||
assert.strictEqual(sidebar.$('.o_web_studio_tfield_fieldexpression .o_field_selector_value').text().replace(/\s/g, ''),
|
||||
"doc(MyModel)ID",
|
||||
"the field chain should be correctly displayed");
|
||||
|
||||
sidebar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Various layout changes', function (assert) {
|
||||
var done = assert.async();
|
||||
// this test is a combinaison of multiple tests, to avoid copy
|
||||
// pasting multiple times de sidebar create/intercept/destroy
|
||||
|
||||
var layoutChangeNode = {
|
||||
attrs: {
|
||||
'data-oe-id': '99',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
},
|
||||
tag: 'div',
|
||||
$nodes: $(),
|
||||
};
|
||||
var layoutChangeTextNode = {
|
||||
attrs: {
|
||||
'data-oe-id': '99',
|
||||
'data-oe-xpath': '/t/t/span',
|
||||
},
|
||||
tag: 'span',
|
||||
$nodes: $(),
|
||||
};
|
||||
var nodeWithAllLayoutPropertiesSet = {
|
||||
tag: "div",
|
||||
attrs: {
|
||||
//width: "1",
|
||||
style: "margin-top:2px;width:1px;margin-right:3px;margin-bottom:4px;margin-left:5px;",
|
||||
class: "o_bold o_italic h3 bg-o-color-3 text-o-color-2 o_underline",
|
||||
'data-oe-id': '99',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
},
|
||||
$nodes: $(),
|
||||
};
|
||||
|
||||
var nodeWithAllLayoutPropertiesFontAndBackgroundSet = {
|
||||
tag: "div",
|
||||
attrs: {
|
||||
//width: "1",
|
||||
style: "margin-top:2px;margin-right:3px;width:1px;margin-bottom:4px;margin-left:5px;background-color:#00FF00;color:#00FF00",
|
||||
class: "o_bold o_italic h3 o_underline",
|
||||
'data-oe-id': '99',
|
||||
'data-oe-xpath': '/t/t/div',
|
||||
},
|
||||
$nodes: $(),
|
||||
};
|
||||
var layoutChangesOperations = [
|
||||
{
|
||||
testName: "add a margin top in pixels",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-top"]',
|
||||
valueToPut: "42",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"margin-top:42px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "add a margin bottom in pixels",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-bottom"]',
|
||||
valueToPut: "42",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"margin-bottom:42px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "add a margin left in pixels",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-left"]',
|
||||
valueToPut: "42",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"margin-left:42px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "add a margin right in pixels",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-right"]',
|
||||
valueToPut: "42",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"margin-right:42px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "add a width",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_width input',
|
||||
valueToPut: "42",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"width:42px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "add a width on a text",
|
||||
nodeToUse: layoutChangeTextNode,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_width input',
|
||||
valueToPut: "42",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"width:42px;display:inline-block\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/span"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "add a class",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_classes input',
|
||||
valueToPut: "new_class",
|
||||
expectedRPC: {
|
||||
new_attrs: {
|
||||
class: "new_class"
|
||||
},
|
||||
type: "attributes",
|
||||
},
|
||||
}, {
|
||||
testName: "set the heading level",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "click",
|
||||
sidebarOperationInputSelector: '.o_web_studio_font_size .dropdown-item-text[data-value="h3"]',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"class\" separator=\" \" add=\"h3\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "set the background color to a theme color",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "mousedown",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_background_colorpicker button[data-color="o-color-3"]',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"class\" separator=\" \" add=\"bg-o-color-3\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "set the background color to a standard color",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "mousedown",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_background_colorpicker button[data-value="#00FF00"]',
|
||||
valueToPut: "h3",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"background-color:#00FF00\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "set the font color to a theme color",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "mousedown",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_font_colorpicker button[data-color="o-color-3"]',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"class\" separator=\" \" add=\"text-o-color-3\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "set the font color to a standard color",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "mousedown",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_font_colorpicker button[data-value="#00FF00"]',
|
||||
valueToPut: "h3",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" add=\"color:#00FF00\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "set the alignment",
|
||||
nodeToUse: layoutChangeNode,
|
||||
eventToTrigger: "click",
|
||||
sidebarOperationInputSelector: '.o_web_studio_text_alignment button[title="end"]',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"class\" separator=\" \" add=\"text-end\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "remove margin top in pixels",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-top"]',
|
||||
valueToPut: "",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" remove=\"margin-top:2px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "remove a margin bottom in pixels",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-bottom"]',
|
||||
valueToPut: "",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" remove=\"margin-bottom:4px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "remove a margin left in pixels",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-left"]',
|
||||
valueToPut: "",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" remove=\"margin-left:5px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "remove a margin right in pixels",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_margin [data-margin="margin-right"]',
|
||||
valueToPut: "",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" remove=\"margin-right:3px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "remove the width",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_width input',
|
||||
valueToPut: "",
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" remove=\"width:1px\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
testName: "remove a class",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "change",
|
||||
sidebarOperationInputSelector: '.o_web_studio_classes input',
|
||||
valueToPut: "o_bold o_italic bg-o-color-3 text-o-color-2 o_underline",
|
||||
expectedRPC: {
|
||||
new_attrs: {
|
||||
class: "o_bold o_italic bg-o-color-3 text-o-color-2 o_underline"
|
||||
},
|
||||
type: "attributes",
|
||||
},
|
||||
}, {
|
||||
testName: "unset the background color to a theme color",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "click",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_background_colorpicker .o_web_studio_reset_color',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"class\" separator=\" \" remove=\"bg-o-color-3\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
},{
|
||||
testName: "unset the background color to a standard color",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesFontAndBackgroundSet,
|
||||
eventToTrigger: "click",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_background_colorpicker .o_web_studio_reset_color',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" remove=\"background-color:#00FF00\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "unset the font color to a theme color",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesSet,
|
||||
eventToTrigger: "click",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_font_colorpicker .o_web_studio_reset_color',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"class\" separator=\" \" remove=\"text-o-color-2\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
}, {
|
||||
testName: "unset the font color to a standard color",
|
||||
nodeToUse: nodeWithAllLayoutPropertiesFontAndBackgroundSet,
|
||||
eventToTrigger: "click",
|
||||
sidebarOperationInputSelector: '.o_web_studio_colors .o_web_studio_font_colorpicker button.o_web_studio_reset_color',
|
||||
expectedRPC: {
|
||||
inheritance: [{
|
||||
content: "<attribute name=\"style\" separator=\";\" remove=\"color:#00FF00\"/>",
|
||||
position: "attributes",
|
||||
view_id: 99,
|
||||
xpath: "/t/t/div"
|
||||
}]
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// there is one assert by operation
|
||||
assert.expect(layoutChangesOperations.length);
|
||||
|
||||
var initialDebugMode = odoo.debug;
|
||||
// show 'class' in the sidebar
|
||||
odoo.debug = true;
|
||||
|
||||
function poll (changeOperation) {
|
||||
var node = {
|
||||
node: changeOperation.nodeToUse,
|
||||
};
|
||||
studioTestUtils.createSidebar({
|
||||
state: {
|
||||
mode: 'properties',
|
||||
nodes: [node],
|
||||
},
|
||||
previousState: {
|
||||
"99/t/t/div": { 'layout': { showAll: true } }, // opens the layout expanded
|
||||
},
|
||||
}).then(function (sidebar) {
|
||||
testUtils.mock.intercept(sidebar, 'view_change', function (ev) {
|
||||
assert.deepEqual(ev.data.operation, changeOperation.expectedRPC, changeOperation.testName);
|
||||
});
|
||||
sidebar.$(changeOperation.sidebarOperationInputSelector)
|
||||
.val(changeOperation.valueToPut)
|
||||
.trigger(changeOperation.eventToTrigger);
|
||||
sidebar.destroy();
|
||||
}).then(function () {
|
||||
if (layoutChangesOperations.length) {
|
||||
poll(layoutChangesOperations.shift());
|
||||
} else {
|
||||
odoo.debug = initialDebugMode;
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
poll(layoutChangesOperations.shift());
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
odoo.define('web_studio.ReportEditor_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var studioTestUtils = require('web_studio.testUtils');
|
||||
|
||||
QUnit.module('Studio', {}, function () {
|
||||
|
||||
QUnit.module('ReportEditor', {
|
||||
beforeEach: function () {
|
||||
},
|
||||
}, function () {
|
||||
QUnit.test('basic report rendering', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var nodesArchs = {
|
||||
42: {
|
||||
attrs: {
|
||||
'data-oe-id': '42',
|
||||
'data-oe-xpath': '/t',
|
||||
name: "Layout",
|
||||
't-name': '42',
|
||||
},
|
||||
},
|
||||
id: 42,
|
||||
key: 'report.layout',
|
||||
parent: null,
|
||||
tag: 't',
|
||||
};
|
||||
var reportHTML = "<html><body><t/></body></html>";
|
||||
var editor = await studioTestUtils.createReportEditor({
|
||||
nodesArchs: nodesArchs,
|
||||
reportHTML: reportHTML,
|
||||
});
|
||||
|
||||
assert.containsOnce(editor, 'iframe',
|
||||
"an iframe should be rendered");
|
||||
assert.hasAttrValue(editor.$('iframe'), 'src', "about:blank",
|
||||
"the source should be correctly set");
|
||||
|
||||
editor.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
354
web_studio/static/tests/legacy/test_utils.js
Normal file
354
web_studio/static/tests/legacy/test_utils.js
Normal file
@@ -0,0 +1,354 @@
|
||||
odoo.define('web_studio.testUtils', function (require) {
|
||||
"use strict";
|
||||
|
||||
const { start } = require('@mail/../tests/helpers/test_utils');
|
||||
|
||||
var dom = require('web.dom');
|
||||
const MockServer = require('web.MockServer');
|
||||
const { ComponentAdapter } = require('web.OwlCompatibility');
|
||||
var QWeb = require('web.QWeb');
|
||||
const { registry } = require('@web/core/registry');
|
||||
var testUtils = require('web.test_utils');
|
||||
var utils = require('web.utils');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var ReportEditor = require('web_studio.ReportEditor');
|
||||
var ReportEditorManager = require('web_studio.ReportEditorManager');
|
||||
var ReportEditorSidebar = require('web_studio.ReportEditorSidebar');
|
||||
var ViewEditorManager = require('web_studio.ViewEditorManager');
|
||||
|
||||
var weTestUtils = require('web_editor.test_utils');
|
||||
|
||||
const { registerCleanup } = require("@web/../tests/helpers/cleanup");
|
||||
|
||||
/**
|
||||
* Create a ReportEditorManager widget.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @return {ReportEditorManager}
|
||||
*/
|
||||
async function createReportEditor(params) {
|
||||
var Parent = Widget.extend({
|
||||
start: function () {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments).then(function () {
|
||||
var $studio = $.parseHTML(
|
||||
"<div class='o_web_studio_client_action'>" +
|
||||
"<div class='o_web_studio_editor_manager o_web_studio_report_editor_manager'/>" +
|
||||
"</div>" +
|
||||
"</div>");
|
||||
self.$el.append($studio);
|
||||
});
|
||||
},
|
||||
});
|
||||
var parent = new Parent();
|
||||
weTestUtils.patch();
|
||||
params.data = weTestUtils.wysiwygData(params.data);
|
||||
await testUtils.mock.addMockEnvironment(parent, params);
|
||||
|
||||
var selector = params.debug ? 'body' : '#qunit-fixture';
|
||||
return parent.appendTo(selector).then(function () {
|
||||
var editor = new ReportEditor(parent, params);
|
||||
// override 'destroy' of editor so that it calls 'destroy' on the parent
|
||||
// instead
|
||||
editor.destroy = function () {
|
||||
// remove the override to properly destroy editor and its children
|
||||
// when it will be called the second time (by its parent)
|
||||
delete editor.destroy;
|
||||
// TODO: call super?
|
||||
parent.destroy();
|
||||
weTestUtils.unpatch();
|
||||
};
|
||||
return editor.appendTo(parent.$('.o_web_studio_editor_manager')).then(function () {
|
||||
return editor;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ReportEditorManager widget.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @return {Promise<ReportEditorManager>}
|
||||
*/
|
||||
async function createReportEditorManager(params) {
|
||||
var parent = new StudioEnvironment();
|
||||
await testUtils.mock.addMockEnvironment(parent, params);
|
||||
weTestUtils.patch();
|
||||
params.data = weTestUtils.wysiwygData(params.data);
|
||||
|
||||
var rem = new ReportEditorManager(parent, params);
|
||||
// also destroy to parent widget to avoid memory leak
|
||||
rem.destroy = function () {
|
||||
delete rem.destroy;
|
||||
parent.destroy();
|
||||
weTestUtils.unpatch();
|
||||
};
|
||||
|
||||
var fragment = document.createDocumentFragment();
|
||||
var selector = params.debug ? 'body' : '#qunit-fixture';
|
||||
if (params.debug) {
|
||||
$('body').addClass('debug');
|
||||
}
|
||||
await parent.prependTo(selector);
|
||||
await rem.appendTo(fragment)
|
||||
// use dom.append to call on_attach_callback
|
||||
dom.append(parent.$('.o_web_studio_client_action'), fragment, {
|
||||
callbacks: [{widget: rem}],
|
||||
in_DOM: true,
|
||||
});
|
||||
await rem.editorIframeDef
|
||||
return rem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sidebar widget.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @return {Promise<ReportEditorSidebar>}
|
||||
*/
|
||||
async function createSidebar(params) {
|
||||
var Parent = Widget.extend({
|
||||
start: function () {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments).then(function () {
|
||||
var $studio = $.parseHTML(
|
||||
"<div class='o_web_studio_client_action'>" +
|
||||
"<div class='o_web_studio_editor_manager o_web_studio_report_editor_manager'/>" +
|
||||
"</div>");
|
||||
self.$el.append($studio);
|
||||
});
|
||||
},
|
||||
});
|
||||
var parent = new Parent();
|
||||
weTestUtils.patch();
|
||||
params.data = weTestUtils.wysiwygData(params.data);
|
||||
await testUtils.mock.addMockEnvironment(parent, params);
|
||||
|
||||
var sidebar = new ReportEditorSidebar(parent, params);
|
||||
sidebar.destroy = function () {
|
||||
// remove the override to properly destroy sidebar and its children
|
||||
// when it will be called the second time (by its parent)
|
||||
delete sidebar.destroy;
|
||||
parent.destroy();
|
||||
weTestUtils.unpatch();
|
||||
};
|
||||
|
||||
var selector = params.debug ? 'body' : '#qunit-fixture';
|
||||
if (params.debug) {
|
||||
$('body').addClass('debug');
|
||||
}
|
||||
parent.appendTo(selector);
|
||||
|
||||
var fragment = document.createDocumentFragment();
|
||||
return sidebar.appendTo(fragment).then(function () {
|
||||
sidebar.$el.appendTo(parent.$('.o_web_studio_editor_manager'));
|
||||
return sidebar;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This class let us instanciate a widget via createWebClient and get it
|
||||
* afterwards in order to use it during tests.
|
||||
*/
|
||||
const { onMounted, onWillUnmount } = owl;
|
||||
class StudioEnvironmentComponent extends ComponentAdapter {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.env = owl.Component.env;
|
||||
onMounted(() => {
|
||||
StudioEnvironmentComponent.currentWidget = this.widget;
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
StudioEnvironmentComponent.currentWidget = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ViewEditorManager widget.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @return {ViewEditorManager}
|
||||
*/
|
||||
async function createViewEditorManager(params) {
|
||||
weTestUtils.patch();
|
||||
params.serverData = params.serverData || {};
|
||||
params.serverData.models = params.serverData.models || {};
|
||||
params.serverData.models = weTestUtils.wysiwygData(params.serverData.models);
|
||||
registry.category('main_components').add('StudioEnvironmentContainer', {
|
||||
Component: StudioEnvironmentComponent,
|
||||
props: { Component: StudioEnvironment },
|
||||
});
|
||||
const { env: wowlEnv } = await start({
|
||||
...params,
|
||||
legacyParams: { withLegacyMockServer: true },
|
||||
});
|
||||
const parent = StudioEnvironmentComponent.currentWidget;
|
||||
const fieldsView = testUtils.mock.getView(MockServer.currentMockServer, params);
|
||||
if (params.viewID) {
|
||||
fieldsView.view_id = params.viewID;
|
||||
}
|
||||
const type = fieldsView.type;
|
||||
|
||||
const viewDescriptions = {
|
||||
fields: fieldsView.fields,
|
||||
relatedModels: {
|
||||
[params.model]: fieldsView.fields,
|
||||
},
|
||||
views: {
|
||||
[type]: {
|
||||
arch: fieldsView.arch,
|
||||
id: fieldsView.view_id || false,
|
||||
custom_view_id: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const vem = new ViewEditorManager(parent, {
|
||||
action: {
|
||||
context: params.context || {},
|
||||
domain: params.domain || [],
|
||||
res_model: params.model,
|
||||
},
|
||||
controllerState: {
|
||||
currentId: 'res_id' in params ? params.res_id : undefined,
|
||||
resIds: 'res_id' in params ? [params.res_id] : undefined,
|
||||
},
|
||||
fields_view: fieldsView,
|
||||
viewType: fieldsView.type,
|
||||
studio_view_id: params.studioViewID,
|
||||
chatter_allowed: params.chatter_allowed,
|
||||
wowlEnv,
|
||||
viewDescriptions,
|
||||
});
|
||||
|
||||
registerCleanup(() => {
|
||||
vem.destroy();
|
||||
weTestUtils.unpatch();
|
||||
});
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
await parent.prependTo(testUtils.prepareTarget(params.debug));
|
||||
await vem.appendTo(fragment);
|
||||
dom.append(parent.$('.o_web_studio_client_action'), fragment, {
|
||||
callbacks: [{widget: vem}],
|
||||
in_DOM: true,
|
||||
});
|
||||
return vem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a list of templates.
|
||||
*
|
||||
* @param {Array<Object>} templates
|
||||
* @param {Object} data
|
||||
* @param {String} [data.dataOeContext]
|
||||
* @returns {string}
|
||||
*/
|
||||
function getReportHTML(templates, data) {
|
||||
_brandTemplates(templates, data && data.dataOeContext);
|
||||
|
||||
var qweb = new QWeb();
|
||||
_.each(templates, function (template) {
|
||||
qweb.add_template(template.arch);
|
||||
});
|
||||
var render = qweb.render('template0', data);
|
||||
return render;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the report views object.
|
||||
*
|
||||
* @param {Array<Object>} templates
|
||||
* @param {Object} [data]
|
||||
* @param {String} [data.dataOeContext]
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getReportViews(templates, data) {
|
||||
_brandTemplates(templates, data && data.dataOeContext);
|
||||
|
||||
var reportViews = {};
|
||||
_.each(templates, function (template) {
|
||||
reportViews[template.view_id] = {
|
||||
arch: template.arch,
|
||||
key: template.key,
|
||||
studio_arch: '</data>',
|
||||
studio_view_id: false,
|
||||
view_id: template.view_id,
|
||||
};
|
||||
});
|
||||
return reportViews;
|
||||
}
|
||||
|
||||
/**
|
||||
* Brands (in place) a list of templates.
|
||||
*
|
||||
* @private
|
||||
* @param {Array<Object>} templates
|
||||
* @param {String} [dataOeContext]
|
||||
*/
|
||||
function _brandTemplates(templates, dataOeContext) {
|
||||
|
||||
_.each(templates, function (template) {
|
||||
brandTemplate(template);
|
||||
});
|
||||
|
||||
function brandTemplate(template) {
|
||||
var doc = $.parseXML(template.arch).documentElement;
|
||||
var rootNode = utils.xml_to_json(doc, true);
|
||||
brandNode([rootNode], rootNode, '');
|
||||
|
||||
function brandNode(siblings, node, xpath) {
|
||||
// do not brand already branded nodes
|
||||
if (_.isObject(node) && !node.attrs['data-oe-id']) {
|
||||
if (node.tag !== 'kikou') {
|
||||
xpath += ('/' + node.tag);
|
||||
var index = _.filter(siblings, {tag: node.tag}).indexOf(node);
|
||||
if (index > 0) {
|
||||
xpath += '[' + index + ']';
|
||||
}
|
||||
node.attrs['data-oe-id'] = template.view_id;
|
||||
node.attrs['data-oe-xpath'] = xpath;
|
||||
node.attrs['data-oe-context'] = dataOeContext || '{}';
|
||||
}
|
||||
|
||||
_.each(node.children, function (child) {
|
||||
brandNode(node.children, child, xpath);
|
||||
});
|
||||
}
|
||||
}
|
||||
template.arch = utils.json_node_to_xml(rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
var StudioEnvironment = Widget.extend({
|
||||
className: 'o_web_client o_in_studio',
|
||||
start: function () {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments).then(function () {
|
||||
// reproduce the DOM environment of Studio
|
||||
var $studio = $.parseHTML(
|
||||
"<div class='o_content'>" +
|
||||
"<div class='o_web_studio_client_action'/>" +
|
||||
"</div>"
|
||||
);
|
||||
self.$el.append($studio);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createReportEditor: createReportEditor,
|
||||
createReportEditorManager: createReportEditorManager,
|
||||
createSidebar: createSidebar,
|
||||
createViewEditorManager: createViewEditorManager,
|
||||
getData: weTestUtils.wysiwygData,
|
||||
getReportHTML: getReportHTML,
|
||||
getReportViews: getReportViews,
|
||||
patch: weTestUtils.patch,
|
||||
unpatch: weTestUtils.unpatch,
|
||||
};
|
||||
|
||||
});
|
||||
990
web_studio/static/tests/legacy/tours/web_studio.js
Normal file
990
web_studio/static/tests/legacy/tours/web_studio.js
Normal file
@@ -0,0 +1,990 @@
|
||||
odoo.define('web_studio.tests.tour', function (require) {
|
||||
"use strict";
|
||||
|
||||
const localStorage = require('web.local_storage');
|
||||
const tour = require('web_tour.tour');
|
||||
|
||||
const { randomString } = require('web_studio.utils');
|
||||
|
||||
let createdAppString = null;
|
||||
let createdMenuString = null;
|
||||
|
||||
tour.register('web_studio_tests_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [{
|
||||
// open studio
|
||||
trigger: '.o_main_navbar .o_web_studio_navbar_item',
|
||||
}, {
|
||||
trigger: '.o_web_studio_new_app',
|
||||
}, {
|
||||
// the next steps are here to create a new app
|
||||
trigger: '.o_web_studio_app_creator_next',
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_name > input',
|
||||
run: 'text ' + (createdAppString = randomString(6)),
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next.is_ready',
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_menu > input',
|
||||
run: 'text ' + (createdMenuString = randomString(6)),
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next.is_ready',
|
||||
}, {
|
||||
// disable chatter in model configurator, we'll test adding it on later
|
||||
trigger: 'input[name="use_mail"]',
|
||||
}, {
|
||||
// disable company if visible, otherwise it might make the test uncertain
|
||||
trigger: 'body',
|
||||
run: () => {
|
||||
const $input = $('input[name="use_company"]');
|
||||
if ($input) {
|
||||
$input.click();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: '.o_web_studio_model_configurator_next',
|
||||
}, {
|
||||
// toggle the home menu outside of studio and come back in studio
|
||||
extra_trigger: '.o_menu_toggle:not(.o_menu_toggle_back)',
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
timeout: 60000, /* previous step reloads registry, etc. - could take a long time */
|
||||
}, {
|
||||
extra_trigger: `.o_web_client:not(.o_in_studio)`, /* wait to be out of studio */
|
||||
trigger: '.o_menu_toggle:not(.o_menu_toggle_back)',
|
||||
timeout: 60000, /* previous step reloads registry, etc. - could take a long time */
|
||||
}, {
|
||||
trigger: '.o_main_navbar .o_web_studio_navbar_item',
|
||||
extra_trigger: '.o_home_menu',
|
||||
}, {
|
||||
// open the app creator and leave it
|
||||
trigger: '.o_web_studio_new_app',
|
||||
}, {
|
||||
extra_trigger: '.o_web_studio_app_creator',
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
}, {
|
||||
// go back to the previous app
|
||||
trigger: '.o_home_menu',
|
||||
run: () => {
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
key: 'Escape',
|
||||
}));
|
||||
},
|
||||
}, {
|
||||
// this should open the previous app outside of studio
|
||||
extra_trigger: `.o_web_client:not(.o_in_studio) .o_menu_brand:contains(${createdAppString})`,
|
||||
// go back to the home menu
|
||||
trigger: '.o_menu_toggle:not(.o_menu_toggle_back)',
|
||||
}, {
|
||||
trigger: 'input.o_search_hidden',
|
||||
// Open Command Palette
|
||||
run: 'text ' + createdMenuString[0],
|
||||
}, {
|
||||
trigger: '.o_command_palette_search input',
|
||||
run: 'text ' + "/" + createdMenuString,
|
||||
}, {
|
||||
// search results should have been updated
|
||||
extra_trigger: `.o_command.focused:contains(${createdAppString} / ${createdMenuString})`,
|
||||
trigger: '.o_command_palette',
|
||||
// Close the Command Palette
|
||||
run: () => {
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
key: 'Escape',
|
||||
}));
|
||||
},
|
||||
}, {
|
||||
// enter Studio
|
||||
trigger: '.o_main_navbar .o_web_studio_navbar_item',
|
||||
}, {
|
||||
// edit an app
|
||||
extra_trigger: '.o_studio_home_menu',
|
||||
trigger: `.o_app[data-menu-xmlid*="studio"]:contains(${createdAppString})`,
|
||||
run: function () {
|
||||
// We can't emulate a hover to display the edit icon
|
||||
const editIcon = this.$anchor[0].querySelector('.o_web_studio_edit_icon');
|
||||
editIcon.style.visibility = 'visible';
|
||||
editIcon.click();
|
||||
},
|
||||
}, {
|
||||
// design the icon
|
||||
// TODO: we initially tested this (change an app icon) at the end but a
|
||||
// long-standing bug (KeyError: ir.ui.menu.display_name, caused by a registry
|
||||
// issue with multiple workers) on runbot prevent us from doing it. It thus have
|
||||
// been moved at the beginning of this test to avoid the registry to be reloaded
|
||||
// before the write on ir.ui.menu.
|
||||
trigger: '.o_web_studio_selector:eq(0)',
|
||||
}, {
|
||||
trigger: '.o_web_studio_palette > .o_web_studio_selector:first',
|
||||
}, {
|
||||
trigger: '.modal-footer .btn.btn-primary',
|
||||
}, {
|
||||
// click on the created app
|
||||
trigger: `.o_app[data-menu-xmlid*="studio"]:contains(${createdAppString})`,
|
||||
}, {
|
||||
// create a new menu
|
||||
trigger: '.o_main_navbar .o_web_edit_menu',
|
||||
}, {
|
||||
trigger: 'footer.modal-footer .js_add_menu',
|
||||
}, {
|
||||
trigger: 'input[name="name"]',
|
||||
run: 'text ' + (createdMenuString = randomString(6)),
|
||||
}, {
|
||||
trigger: 'div[name="model_choice"] input[data-value="existing"]',
|
||||
}, {
|
||||
trigger: '.o_field_many2one[name="model"] input',
|
||||
run: 'text a',
|
||||
}, {
|
||||
trigger: '.ui-autocomplete > .ui-menu-item:first > a',
|
||||
in_modal: false,
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm):not(".disabled")',
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm):not(".disabled")',
|
||||
}, {
|
||||
// check that the Studio menu is still there
|
||||
extra_trigger: '.o_web_studio_menu',
|
||||
// switch to form view
|
||||
trigger: '.o_web_studio_views_icons > a[title="Form"]',
|
||||
}, {
|
||||
// wait for the form editor to be rendered because the sidebar is the same
|
||||
extra_trigger: '.o_web_studio_form_view_editor',
|
||||
// unfold 'Existing Fieldqs' section
|
||||
trigger: '.o_web_studio_existing_fields_header',
|
||||
}, {
|
||||
// add an new field
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_char',
|
||||
run: 'drag_and_drop .o_web_studio_form_view_editor .o_inner_group',
|
||||
}, {
|
||||
// click on the field
|
||||
trigger: '.o_web_studio_form_view_editor .o_wrap_label:first label',
|
||||
// when it's there
|
||||
extra_trigger: 'input[data-type="field_name"]',
|
||||
}, {
|
||||
// rename the label
|
||||
trigger: '.o_web_studio_sidebar_content.o_display_field input[name="string"]',
|
||||
run: 'text My Coucou Field',
|
||||
}, {
|
||||
// verify that the field name has changed and change it
|
||||
trigger: 'input[data-type="field_name"][value="my_coucou_field"]',
|
||||
run: 'text coucou',
|
||||
// the rename operation (/web_studio/rename_field + /web_studio/edit_view)
|
||||
// takes a while and sometimes reaches the default 10s timeout
|
||||
timeout: 20000,
|
||||
}, {
|
||||
// click on "Add" tab
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_new',
|
||||
// the rename operation (/web_studio/rename_field + /web_studio/edit_view)
|
||||
// takes a while and sometimes reaches the default 10s timeout
|
||||
timeout: 20000,
|
||||
async run() {
|
||||
// During the rename, the UI is blocked. When the rpc returns, the UI is
|
||||
// unblocked and the sidebar is re-rendered. Without this, the step is
|
||||
// sometimes executed exactly when the sidebar is about to be replaced,
|
||||
// and it doesn't work. We thus here wait for 1s to ensure that the
|
||||
// sidebar has been re-rendered, before going further.
|
||||
// note1: there's nothing in the DOM that could be used to determine that
|
||||
// we're ready to continue (the sidebar is just replaced by itself, same state)
|
||||
// note2: ideally, it should work whenever we click, but with the current
|
||||
// architecture of studio, it's really hard to fix. Hopefully, when studio
|
||||
// will be converted to owl, this should no longer be an issue.
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
$(".o_web_studio_sidebar .o_web_studio_new").click();
|
||||
}
|
||||
}, {
|
||||
// add a new field
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_char',
|
||||
run: 'drag_and_drop .o_web_studio_form_view_editor .o_inner_group',
|
||||
}, {
|
||||
// rename the field with the same name
|
||||
trigger: 'input[data-type="field_name"]',
|
||||
run: 'text coucou',
|
||||
}, {
|
||||
// an alert dialog should be opened
|
||||
trigger: '.modal-footer > button:first',
|
||||
}, {
|
||||
// rename the label
|
||||
trigger: '.o_web_studio_sidebar_content.o_display_field input[name="string"]',
|
||||
run: 'text COUCOU',
|
||||
}, {
|
||||
// verify that the field name has changed (post-fixed by _1)
|
||||
extra_trigger: 'input[data-type="field_name"][value="coucou_1"]',
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_new',
|
||||
// the rename operation (/web_studio/rename_field + /web_studio/edit_view)
|
||||
// takes a while and sometimes reaches the default 10s timeout
|
||||
timeout: 20000,
|
||||
}, {
|
||||
// add a monetary field --> create a currency field
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_monetary',
|
||||
run: 'drag_and_drop .o_web_studio_form_view_editor .o_inner_group',
|
||||
}, {
|
||||
trigger: '.modal-footer .btn.btn-primary',
|
||||
}, {
|
||||
// verify that the currency field is in the view
|
||||
extra_trigger: '.o_web_studio_form_view_editor .o_wrap_label label:contains("Currency")',
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_new',
|
||||
async run() {
|
||||
// When adding a new field, the UI is blocked. When the rpc returns, the UI is
|
||||
// unblocked and the sidebar is re-rendered. Without this, the step is
|
||||
// sometimes executed exactly when the sidebar is about to be replaced,
|
||||
// and it doesn't work. We thus here wait for 1s to ensure that the
|
||||
// sidebar has been re-rendered, before going further.
|
||||
// note1: there's nothing in the DOM that could be used to determine that
|
||||
// we're ready to continue (the sidebar is just replaced by itself, same state)
|
||||
// note2: ideally, it should work whenever we click, but with the current
|
||||
// architecture of studio, it's really hard to fix. Hopefully, when studio
|
||||
// will be converted to owl, this should no longer be an issue.
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
$(".o_web_studio_sidebar .o_web_studio_new").click();
|
||||
}
|
||||
}, {
|
||||
// add a monetary field
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_monetary',
|
||||
run: 'drag_and_drop (.o_web_studio_form_view_editor .o_inner_group:first .o_web_studio_hook:eq(1))',
|
||||
}, {
|
||||
// verify that the monetary field is in the view
|
||||
extra_trigger: '.o_web_studio_form_view_editor .o_wrap_label:eq(1) label:contains("New Monetary")',
|
||||
// switch the two first fields
|
||||
trigger: '.o_web_studio_form_view_editor .o_inner_group:first .o-draggable:eq(1)',
|
||||
run: 'drag_and_drop_native .o_inner_group:first .o_web_studio_hook:first',
|
||||
}, {
|
||||
// click on "Add" tab
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_new',
|
||||
}, {
|
||||
// verify that the fields have been switched
|
||||
extra_trigger: '.o_web_studio_form_view_editor .o_wrap_label:eq(0) label:contains("New Monetary")',
|
||||
// add a m2m field
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_many2many',
|
||||
run: 'drag_and_drop .o_inner_group:first .o_web_studio_hook:first',
|
||||
}, {
|
||||
// type something in the modal
|
||||
trigger: '.o_field_many2one[name="model"] input',
|
||||
run: 'text a',
|
||||
}, {
|
||||
// select the first model
|
||||
trigger: '.ui-autocomplete > .ui-menu-item:first > a',
|
||||
in_modal: false,
|
||||
}, {
|
||||
trigger: 'button:contains(Confirm)',
|
||||
}, {
|
||||
// select the m2m to set its properties
|
||||
trigger: '.o_wrap_input:has(.o_field_many2many)',
|
||||
timeout: 15000, // creating M2M relations can take some time...
|
||||
}, {
|
||||
// change the `widget` attribute
|
||||
trigger: '.o_web_studio_sidebar select[name="widget"]',
|
||||
run: function () {
|
||||
this.$anchor.val('many2many_tags').trigger('change');
|
||||
},
|
||||
}, {
|
||||
// use colors on the m2m tags
|
||||
trigger: '.o_web_studio_sidebar label[for="option_color_field"]',
|
||||
}, {
|
||||
// add a statusbar
|
||||
trigger: '.o_web_studio_statusbar_hook',
|
||||
}, {
|
||||
trigger: '.modal-footer .btn.btn-primary',
|
||||
}, {
|
||||
trigger: '.o_statusbar_status',
|
||||
}, {
|
||||
// verify that a default value has been set for the statusbar
|
||||
trigger: '.o_web_studio_sidebar select[name="default_value"]:contains(First Status)',
|
||||
}, {
|
||||
trigger: '.o_web_studio_views_icons a[title=Form]',
|
||||
}, {
|
||||
// verify Chatter can be added after changing view to form
|
||||
extra_trigger: '.o_web_studio_add_chatter',
|
||||
// edit action
|
||||
trigger: '.o_web_studio_menu .o_menu_sections li a:contains(Views)',
|
||||
}, {
|
||||
// edit form view
|
||||
trigger: '.o_web_studio_view_category .o_web_studio_view_type[data-type="form"] .o_web_studio_thumbnail',
|
||||
}, {
|
||||
// verify Chatter can be added after changing view to form
|
||||
extra_trigger: '.o_web_studio_add_chatter',
|
||||
// switch in list view
|
||||
trigger: '.o_web_studio_menu .o_web_studio_views_icons a[title="List"]',
|
||||
}, {
|
||||
// wait for the list editor to be rendered because the sidebar is the same
|
||||
extra_trigger: '.o_web_studio_list_view_editor',
|
||||
// unfold 'Existing Fieldqs' section
|
||||
trigger: '.o_web_studio_existing_fields_header',
|
||||
}, {
|
||||
// add an existing field (display_name)
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_char',
|
||||
run: 'drag_and_drop .o_web_studio_list_view_editor th.o_web_studio_hook:first',
|
||||
}, {
|
||||
// verify that the field is correctly named
|
||||
extra_trigger: '.o_web_studio_list_view_editor th:contains("COUCOU")',
|
||||
// leave Studio
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
}, {
|
||||
// come back to the home menu to check if the menu data have changed
|
||||
extra_trigger: '.o_web_client:not(.o_in_studio)',
|
||||
trigger: '.o_menu_toggle:not(.o_menu_toggle_back)',
|
||||
}, {
|
||||
trigger: 'input.o_search_hidden',
|
||||
// Open Command Palette
|
||||
run: 'text ' + createdMenuString[0],
|
||||
}, {
|
||||
trigger: '.o_command_palette_search input',
|
||||
run: 'text ' + "/" + createdMenuString,
|
||||
}, {
|
||||
// search results should have been updated
|
||||
extra_trigger: `.o_command.focused:contains(${createdAppString} / ${createdMenuString})`,
|
||||
trigger: '.o_command_palette',
|
||||
// Close the Command Palette
|
||||
run: () => {
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
key: 'Escape',
|
||||
}));
|
||||
},
|
||||
}, {
|
||||
trigger: '.o_home_menu',
|
||||
// go back again to the app (using keyboard)
|
||||
run: () => {
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
key: 'Escape',
|
||||
}));
|
||||
},
|
||||
}, {
|
||||
// wait to be back in the list view
|
||||
extra_trigger: '.o_list_view',
|
||||
// re-open studio
|
||||
trigger: '.o_web_studio_navbar_item',
|
||||
}, {
|
||||
// modify the list view
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_view'
|
||||
}, {
|
||||
//select field you want to sort and based on that sorting will be applied on List view
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_sidebar_select #sort_field',
|
||||
run: function () {
|
||||
$('#sort_field option:eq(1)').attr('selected', 'selected');
|
||||
$('#sort_field option:eq(1)').change();
|
||||
}
|
||||
}, {
|
||||
//change order of sorting, Select order and change it
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_sidebar_select #sort_order',
|
||||
run: function () {
|
||||
$('#sort_order option:eq(1)').attr('selected', 'selected');
|
||||
$('#sort_order option:eq(1)').change();
|
||||
}
|
||||
}, {
|
||||
// edit action
|
||||
trigger: '.o_web_studio_menu .o_menu_sections li a:contains("Views")',
|
||||
}, {
|
||||
// add a kanban
|
||||
trigger: '.o_web_studio_view_category .o_web_studio_view_type.o_web_studio_inactive[data-type="kanban"] .o_web_studio_thumbnail',
|
||||
}, {
|
||||
// add a dropdown
|
||||
trigger: '.o_dropdown_kanban.o_web_studio_add_dropdown',
|
||||
}, {
|
||||
trigger: '.modal-footer .btn.btn-primary',
|
||||
}, {
|
||||
// select the dropdown for edition
|
||||
trigger: '.o_dropdown_kanban:not(.o_web_studio_add_dropdown)',
|
||||
}, {
|
||||
// enable "Set Cover" feature
|
||||
trigger: '.o_web_studio_sidebar input[name=set_cover]',
|
||||
}, {
|
||||
trigger: '.modal-footer .btn.btn-primary',
|
||||
}, {
|
||||
// edit action
|
||||
trigger: '.o_web_studio_menu .o_menu_sections li a:contains("Views")',
|
||||
}, {
|
||||
// check that the kanban view is now active
|
||||
extra_trigger: '.o_web_studio_view_category .o_web_studio_view_type:not(.o_web_studio_inactive)[data-type="kanban"]',
|
||||
// add an activity view
|
||||
trigger: '.o_web_studio_view_category .o_web_studio_view_type.o_web_studio_inactive[data-type="activity"] .o_web_studio_thumbnail',
|
||||
}, {
|
||||
extra_trigger: '.o_activity_view',
|
||||
// edit action
|
||||
trigger: '.o_web_studio_menu .o_menu_sections li a:contains("Views")',
|
||||
timeout: 20000, // activating a view takes a while and sometimes reaches the default 10s timeout
|
||||
}, {
|
||||
// add a graph view
|
||||
trigger: '.o_web_studio_view_category .o_web_studio_view_type.o_web_studio_inactive[data-type="graph"] .o_web_studio_thumbnail',
|
||||
}, {
|
||||
extra_trigger: '.o_graph_renderer',
|
||||
trigger: '.o_web_studio_menu .o_menu_sections li a:contains("Views")',
|
||||
}, {
|
||||
extra_trigger: '.o_web_studio_views',
|
||||
// edit the search view
|
||||
trigger: '.o_web_studio_view_category .o_web_studio_view_type[data-type="search"] .o_web_studio_thumbnail',
|
||||
}, {
|
||||
extra_trigger: '.o_web_studio_search_view_editor',
|
||||
trigger: '.o_menu_toggle:not(.o_menu_toggle_back)',
|
||||
}, {
|
||||
trigger: '.o_web_studio_home_studio_menu .dropdown-toggle',
|
||||
}, {
|
||||
// export all modifications
|
||||
trigger: '.o_web_studio_export',
|
||||
}, {
|
||||
// click on the created app
|
||||
trigger: '.o_app[data-menu-xmlid*="studio"]:last',
|
||||
}, {
|
||||
// switch to form view
|
||||
trigger: '.o_web_studio_views_icons > a[title="Form"]',
|
||||
}, {
|
||||
extra_trigger: '.o_web_studio_form_view_editor',
|
||||
// click on the view tab
|
||||
trigger: '.o_web_studio_view',
|
||||
}, {
|
||||
// click on the restore default view button
|
||||
trigger: '.o_web_studio_restore',
|
||||
}, {
|
||||
// click on the ok button
|
||||
trigger: '.modal-footer .btn.btn-primary',
|
||||
}, {
|
||||
// checks that the field doesn't exist anymore
|
||||
extra_trigger: 'label.o_form_label:not(:contains("COUCOU"))',
|
||||
trigger: '.o_web_studio_leave > a.btn'
|
||||
}]);
|
||||
|
||||
tour.register('web_studio_hide_fields_tour', {
|
||||
url: "/web#action=studio&mode=home_menu",
|
||||
test: true,
|
||||
}, [{
|
||||
trigger: '.o_web_studio_new_app',
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next',
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_app_creator_name
|
||||
> input`,
|
||||
run: `text ${randomString(6)}`,
|
||||
}, {
|
||||
// make another interaction to show "next" button
|
||||
trigger: `
|
||||
.o_web_studio_selectors
|
||||
.o_web_studio_selector:eq(2)`,
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next',
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_app_creator_menu
|
||||
> input`,
|
||||
run: `text ${randomString(6)}`,
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next',
|
||||
}, {
|
||||
trigger: '.o_web_studio_model_configurator_next',
|
||||
}, {
|
||||
// check that the Studio menu is still there
|
||||
extra_trigger: '.o_web_studio_menu',
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
timeout: 60000, /* previous step reloads registry, etc. - could take a long time */
|
||||
}, {
|
||||
trigger: '.oe_title input',
|
||||
run: 'text Test',
|
||||
}, {
|
||||
trigger: '.o_form_button_save',
|
||||
}, {
|
||||
trigger: '.o_web_studio_navbar_item',
|
||||
}, {
|
||||
extra_trigger: '.o_web_studio_menu',
|
||||
trigger: `
|
||||
.o_web_studio_views_icons
|
||||
> a[title="List"]`,
|
||||
}, {
|
||||
// wait for the list editor to be rendered because the sidebar is the same
|
||||
extra_trigger: '.o_web_studio_list_view_editor',
|
||||
trigger: '.o_web_studio_existing_fields_icon',
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_sidebar
|
||||
.o_web_studio_existing_fields
|
||||
.o_web_studio_component:has(.o_web_studio_component_description:contains(display_name))`,
|
||||
run: 'drag_and_drop .o_web_studio_list_view_editor .o_web_studio_hook',
|
||||
}, {
|
||||
trigger: `
|
||||
.o_list_table
|
||||
th[data-name="display_name"]`,
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_sidebar
|
||||
select[name="optional"]`,
|
||||
run: "text Hide by default",
|
||||
}, {
|
||||
extra_trigger: '.o_list_table:not(:has(th[data-name="display_name"]))',
|
||||
trigger: `
|
||||
.o_web_studio_sidebar_header
|
||||
.o_web_studio_view`,
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_sidebar_checkbox
|
||||
input#show_invisible`,
|
||||
}, {
|
||||
extra_trigger: `
|
||||
.o_list_table
|
||||
th[data-name="display_name"].o_web_studio_show_invisible`,
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
}]);
|
||||
|
||||
tour.register('web_studio_model_option_value_tour', {
|
||||
url: "/web?debug=tests#action=studio&mode=home_menu",
|
||||
test: true,
|
||||
}, [{
|
||||
trigger: '.o_web_studio_new_app',
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next',
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_app_creator_name
|
||||
> input`,
|
||||
run: `text ${randomString(6)}`,
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_selectors
|
||||
.o_web_studio_selector:eq(2)`,
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next',
|
||||
}, {
|
||||
trigger: `
|
||||
.o_web_studio_app_creator_menu
|
||||
> input`,
|
||||
run: `text ${randomString(6)}`,
|
||||
}, {
|
||||
trigger: '.o_web_studio_app_creator_next',
|
||||
}, {
|
||||
// check monetary value in model configurator
|
||||
trigger: 'input[name="use_value"]',
|
||||
}, {
|
||||
// check lines value in model configurator
|
||||
trigger: 'input[name="lines"]',
|
||||
}, {
|
||||
trigger: '.o_web_studio_model_configurator_next',
|
||||
}, {
|
||||
trigger: '.o_web_studio_menu .o_web_studio_views_icons > a[title="Graph"]',
|
||||
timeout: 60000, /* previous step reloads registry, etc. - could take a long time */
|
||||
}, {
|
||||
// wait for the graph editor to be rendered and also check for sample data
|
||||
extra_trigger: '.o_view_sample_data .o_graph_renderer',
|
||||
trigger: '.o_web_studio_menu .o_web_studio_views_icons a[title="Pivot"]',
|
||||
}, {
|
||||
// wait for the pivot editor to be rendered and also check for sample data
|
||||
extra_trigger: '.o_pivot_view .o_view_sample_data .o_view_nocontent_empty_folder',
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
}]);
|
||||
|
||||
tour.register('web_studio_new_report_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [{
|
||||
// open studio
|
||||
trigger: '.o_main_navbar .o_web_studio_navbar_item',
|
||||
}, {
|
||||
// click on the created app
|
||||
trigger: '.o_app[data-menu-xmlid*="studio"]:first',
|
||||
extra_trigger: 'body.o_in_studio',
|
||||
}, {
|
||||
// edit reports
|
||||
trigger: '.o_web_studio_menu li a:contains(Reports)',
|
||||
}, {
|
||||
// create a new report
|
||||
trigger: '.o_control_panel .o-kanban-button-new',
|
||||
}, {
|
||||
// select external layout
|
||||
trigger: '.o_web_studio_report_layout_dialog div[data-layout="web.external_layout"]',
|
||||
}, {
|
||||
// sidebar should display add tab
|
||||
extra_trigger: '.o_web_studio_report_editor_manager .o_web_studio_sidebar_header div.active[name="new"]',
|
||||
// switch to 'Report' tab
|
||||
trigger: '.o_web_studio_report_editor_manager .o_web_studio_sidebar_header div[name="report"]',
|
||||
}, {
|
||||
// edit report name
|
||||
trigger: '.o_web_studio_sidebar input[name="name"]',
|
||||
run: 'text My Awesome Report',
|
||||
}, {
|
||||
// switch to 'Add' in Sidebar
|
||||
trigger: '.o_web_studio_sidebar div[name="new"]',
|
||||
}, {
|
||||
// wait for the iframe to be loaded
|
||||
extra_trigger: '.o_web_studio_report_editor iframe #wrapwrap',
|
||||
// add a 'title' building block
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_component:contains(Title Block)',
|
||||
run: 'drag_and_drop .o_web_studio_report_editor iframe .article > .page',
|
||||
auto: true,
|
||||
}, {
|
||||
// click on the newly added field
|
||||
trigger: '.o_web_studio_report_editor iframe .h2 > span:contains(New Title)',
|
||||
}, {
|
||||
// change the text of the H2 to 'test'
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable',
|
||||
run: function () {
|
||||
this.$anchor.focusIn();
|
||||
this.$anchor[0].firstChild.textContent = 'Test';
|
||||
this.$anchor.keydown();
|
||||
this.$anchor.blur();
|
||||
}
|
||||
}, {
|
||||
// click outside to blur the field
|
||||
trigger: '.o_web_studio_report_editor',
|
||||
extra_trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable:contains(Test)',
|
||||
}, {
|
||||
extra_trigger: '.o_web_studio_report_editor iframe .h2:contains(Test)',
|
||||
// add a new group on the node
|
||||
trigger: '.o_web_studio_sidebar .o_field_many2manytags[name="groups"] input',
|
||||
run: function () {
|
||||
this.$anchor.click();
|
||||
},
|
||||
}, {
|
||||
trigger: '.ui-autocomplete:visible li:contains(Access Rights)',
|
||||
}, {
|
||||
// wait for the group to appear
|
||||
extra_trigger: '.o_web_studio_sidebar .o_field_many2manytags[name="groups"] .o_badge_text:contains(Access Rights)',
|
||||
// switch to 'Add' in Sidebar
|
||||
trigger: '.o_web_studio_sidebar div[name="new"]',
|
||||
}, {
|
||||
// add a 'title' building block Data Table
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_component:contains(Data table)',
|
||||
run: 'drag_and_drop .o_web_studio_report_editor iframe .article > .page',
|
||||
}, {
|
||||
// expand the model selector in the popup
|
||||
trigger: 'div.o_field_selector_value',
|
||||
run: function () {
|
||||
$('div.o_field_selector_value').focusin();
|
||||
}
|
||||
}, {
|
||||
// select the second element of the model (followers)
|
||||
trigger: '.o_field_selector_popover_body > ul > li:contains(Followers)'
|
||||
}, {
|
||||
trigger:'.modal-content button>span:contains(Confirm)', // button
|
||||
extra_trigger:'.o_field_selector_chain_part:contains(Followers)',//content of the field is set
|
||||
}, {
|
||||
// select the content of the first field of the newly added table
|
||||
trigger: '.o_web_studio_report_editor iframe span[t-field="table_line.display_name"]'
|
||||
}, {
|
||||
// change the bound field
|
||||
trigger: '.o_web_studio_sidebar .card:last() div.o_field_selector_value',
|
||||
run: function () {
|
||||
$('.o_web_studio_sidebar .card:last() div.o_field_selector_value').focusin();
|
||||
}
|
||||
}, {
|
||||
trigger: 'ul.o_field_selector_page li:contains(ID)'
|
||||
}, {
|
||||
// update the title of the column
|
||||
extra_trigger: '.o_web_studio_report_editor iframe span[t-field="table_line.id"]',
|
||||
trigger: '.o_web_studio_report_editor iframe table thead span:contains(Name) ', // the name title
|
||||
//extra_trigger: '.o_web_studio_report_editor iframe span[t-field="table_line.display_name"]:not(:contains(YourCompany, Administrator))', // the id has been updated in the iframe
|
||||
}, {
|
||||
// update column title 'name' into another title
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable',
|
||||
run: function () {
|
||||
this.$anchor.focusIn();
|
||||
this.$anchor[0].firstChild.textContent = 'new column title';
|
||||
this.$anchor.keydown();
|
||||
this.$anchor.blur();
|
||||
}
|
||||
}, {
|
||||
// click outside to blur the field
|
||||
trigger: '.o_web_studio_report_editor',
|
||||
extra_trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable:contains(new column title)',
|
||||
}, {
|
||||
// wait to be sure the modification has been correctly applied
|
||||
extra_trigger: '.o_web_studio_report_editor iframe table thead span:contains(new column title) ',
|
||||
// leave the report
|
||||
trigger: '.o_web_studio_breadcrumb .o_back_button:contains(Reports)',
|
||||
}, {
|
||||
// a invisible element cannot be used as a trigger so this small hack is
|
||||
// mandatory for the next step
|
||||
run: function () {
|
||||
$('.o_kanban_record:contains(My Awesome Report) .dropdown-toggle').css('visibility', 'visible');
|
||||
},
|
||||
trigger: '.o_legacy_kanban_view',
|
||||
}, {
|
||||
// open the dropdown
|
||||
trigger: '.o_kanban_record:contains(My Awesome Report) .dropdown-toggle',
|
||||
}, {
|
||||
// duplicate the report
|
||||
trigger: '.o_kanban_record:contains(My Awesome Report) .dropdown-menu a:contains(Duplicate)',
|
||||
}, {
|
||||
// open the duplicate report
|
||||
trigger: '.o_kanban_record:contains(My Awesome Report copy(1))',
|
||||
}, {
|
||||
// switch to 'Report' tab
|
||||
trigger: '.o_web_studio_report_editor_manager .o_web_studio_sidebar_header div[name="report"]',
|
||||
}, {
|
||||
// wait for the duplicated report to be correctly loaded
|
||||
extra_trigger: '.o_web_studio_sidebar input[name="name"][value="My Awesome Report copy(1)"]',
|
||||
// leave Studio
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
}]);
|
||||
|
||||
tour.register('web_studio_new_report_basic_layout_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [{
|
||||
// open studio
|
||||
trigger: '.o_main_navbar .o_web_studio_navbar_item',
|
||||
}, {
|
||||
// click on the created app
|
||||
trigger: '.o_app[data-menu-xmlid*="studio"]:first',
|
||||
extra_trigger: 'body.o_in_studio',
|
||||
}, {
|
||||
// edit reports
|
||||
trigger: '.o_web_studio_menu li a:contains(Reports)',
|
||||
}, {
|
||||
// create a new report
|
||||
trigger: '.o_control_panel .o-kanban-button-new',
|
||||
}, {
|
||||
// select external layout
|
||||
trigger: '.o_web_studio_report_layout_dialog div[data-layout="web.basic_layout"]',
|
||||
}, {
|
||||
// sidebar should display add tab
|
||||
extra_trigger: '.o_web_studio_report_editor_manager .o_web_studio_sidebar_header div.active[name="new"]',
|
||||
// switch to 'Report' tab
|
||||
trigger: '.o_web_studio_report_editor_manager .o_web_studio_sidebar_header div[name="report"]',
|
||||
}, {
|
||||
// edit report name
|
||||
trigger: '.o_web_studio_sidebar input[name="name"]',
|
||||
run: 'text My Awesome basic layout Report',
|
||||
}, {
|
||||
// switch to 'Add' in Sidebar
|
||||
trigger: '.o_web_studio_sidebar div[name="new"]',
|
||||
}, {
|
||||
// wait for the iframe to be loaded
|
||||
extra_trigger: '.o_web_studio_report_editor iframe #wrapwrap',
|
||||
// add a 'title' building block
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_component:contains(Title Block)',
|
||||
run: 'drag_and_drop .o_web_studio_report_editor iframe .article > .page',
|
||||
auto: true,
|
||||
}, {
|
||||
// click on the newly added field
|
||||
trigger: '.o_web_studio_report_editor iframe .h2 > span:contains(New Title)',
|
||||
}, {
|
||||
// change the text of the H2 to 'test'
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable',
|
||||
run: function () {
|
||||
this.$anchor.focusIn();
|
||||
this.$anchor[0].firstChild.textContent = 'Test';
|
||||
this.$anchor.keydown();
|
||||
this.$anchor.blur();
|
||||
}
|
||||
}, {
|
||||
// click outside to blur the field
|
||||
trigger: '.o_web_studio_report_editor',
|
||||
extra_trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable:contains(Test)',
|
||||
}, {
|
||||
extra_trigger: '.o_web_studio_report_editor iframe .h2:contains(Test)',
|
||||
// add a new group on the node
|
||||
trigger: '.o_web_studio_sidebar .o_field_many2manytags[name="groups"] input',
|
||||
run: function () {
|
||||
this.$anchor.click();
|
||||
},
|
||||
}, {
|
||||
trigger: '.ui-autocomplete:visible li:contains(Access Rights)',
|
||||
}, {
|
||||
// wait for the group to appear
|
||||
extra_trigger: '.o_web_studio_sidebar .o_field_many2manytags[name="groups"] .o_badge_text:contains(Access Rights)',
|
||||
// switch to 'Add' in Sidebar
|
||||
trigger: '.o_web_studio_sidebar div[name="new"]',
|
||||
}, {
|
||||
// add a 'title' building block Data Table
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_component:contains(Data table)',
|
||||
run: 'drag_and_drop .o_web_studio_report_editor iframe .article > .page',
|
||||
}, {
|
||||
// expand the model selector in the popup
|
||||
trigger: 'div.o_field_selector_value',
|
||||
run: function () {
|
||||
$('div.o_field_selector_value').focusin();
|
||||
}
|
||||
}, {
|
||||
// select the second element of the model (followers)
|
||||
trigger: '.o_field_selector_popover_body > ul > li:contains(Followers)'
|
||||
}, {
|
||||
trigger:'.modal-content button>span:contains(Confirm)', // button
|
||||
extra_trigger:'.o_field_selector_chain_part:contains(Followers)', //content of the field is set
|
||||
}, {
|
||||
// select the content of the first field of the newly added table
|
||||
trigger: '.o_web_studio_report_editor iframe span[t-field="table_line.display_name"]'
|
||||
}, {
|
||||
// change the bound field
|
||||
trigger: '.o_web_studio_sidebar .card:last() div.o_field_selector_value',
|
||||
run: function () {
|
||||
$('.o_web_studio_sidebar .card:last() div.o_field_selector_value').focusin();
|
||||
}
|
||||
}, {
|
||||
trigger: 'ul.o_field_selector_page li:contains(ID)'
|
||||
}, {
|
||||
// update the title of the column
|
||||
trigger: '.o_web_studio_report_editor iframe table thead span:contains(Name) ', // the name title
|
||||
//extra_trigger: '.o_web_studio_report_editor iframe span[t-field="table_line.display_name"]:not(:contains(YourCompany, Administrator))', // the id has been updated in the iframe
|
||||
}, {
|
||||
// update column title 'name' into another title
|
||||
trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable',
|
||||
run: function () {
|
||||
this.$anchor.focusIn();
|
||||
this.$anchor[0].firstChild.textContent = 'new column title';
|
||||
this.$anchor.keydown();
|
||||
this.$anchor.blur();
|
||||
}
|
||||
}, {
|
||||
// click outside to blur the field
|
||||
trigger: '.o_web_studio_report_editor',
|
||||
extra_trigger: '.o_web_studio_sidebar .o_web_studio_text .note-editable:contains(new column title)',
|
||||
}, {
|
||||
// wait to be sure the modification has been correctly applied
|
||||
extra_trigger: '.o_web_studio_report_editor iframe table thead span:contains(new column title) ',
|
||||
// leave the report
|
||||
trigger: '.o_web_studio_breadcrumb .o_back_button:contains(Reports)',
|
||||
}, {
|
||||
// a invisible element cannot be used as a trigger so this small hack is
|
||||
// mandatory for the next step
|
||||
run: function () {
|
||||
$('.o_kanban_record:contains(My Awesome basic layout Report) .dropdown-toggle').css('visibility', 'visible');
|
||||
},
|
||||
trigger: '.o_legacy_kanban_view',
|
||||
}, {
|
||||
// open the dropdown
|
||||
trigger: '.o_kanban_record:contains(My Awesome basic layout Report) .dropdown-toggle',
|
||||
}, {
|
||||
// duplicate the report
|
||||
trigger: '.o_kanban_record:contains(My Awesome basic layout Report) .dropdown-menu a:contains(Duplicate)',
|
||||
}, {
|
||||
// open the duplicate report
|
||||
trigger: '.o_kanban_record:contains(My Awesome basic layout Report copy(1))',
|
||||
}, {
|
||||
// switch to 'Report' tab
|
||||
trigger: '.o_web_studio_report_editor_manager .o_web_studio_sidebar_header div[name="report"]',
|
||||
}, {
|
||||
// wait for the duplicated report to be correctly loaded
|
||||
extra_trigger: '.o_web_studio_sidebar input[name="name"][value="My Awesome basic layout Report copy(1)"]',
|
||||
// leave Studio
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
}]);
|
||||
|
||||
tour.register('web_studio_approval_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [{
|
||||
// go to Apps menu
|
||||
trigger: '.o_app[data-menu-xmlid="base.menu_management"]',
|
||||
}, {
|
||||
// open studio
|
||||
trigger: '.o_main_navbar .o_web_studio_navbar_item',
|
||||
extra_trigger: '.o_cp_switch_buttons',
|
||||
}, {
|
||||
// switch to form view editor
|
||||
trigger: '.o_web_studio_views_icons > a[title="Form"]',
|
||||
}, {
|
||||
// click on first button it finds that has a node id
|
||||
trigger: '.o_web_studio_form_view_editor button.o-web-studio-editor--element-clickable',
|
||||
}, {
|
||||
// enable approvals for the button
|
||||
trigger: '.o_web_studio_sidebar label[for="studio_approval"]',
|
||||
}, {
|
||||
// set approval message
|
||||
trigger: '.o_web_studio_sidebar_approval input[name*="approval_message"]',
|
||||
run: 'text nope',
|
||||
}, {
|
||||
// add approval rule
|
||||
trigger: '.o_web_studio_sidebar_approval .o_approval_new',
|
||||
extra_trigger: '.o_web_studio_snackbar .fa-check',
|
||||
}, {
|
||||
// set domain on first rule
|
||||
trigger: '.o_web_studio_sidebar_approval .o_approval_domain',
|
||||
extra_trigger: '.o_studio_sidebar_approval_rule:eq(1)',
|
||||
}, {
|
||||
// set stupid domain that is always truthy
|
||||
trigger: '.o_domain_debug_container textarea',
|
||||
run: function () {
|
||||
this.$anchor.focusIn();
|
||||
this.$anchor.val('[["id", "!=", False]]');
|
||||
this.$anchor.change();
|
||||
}
|
||||
}, {
|
||||
// save domain and close modal
|
||||
trigger:' .modal-footer .btn-primary',
|
||||
}, {
|
||||
// add second approval rule when the first is set
|
||||
trigger: '.o_web_studio_sidebar_approval .o_approval_new',
|
||||
extra_trigger: '.o_web_studio_snackbar .fa-check',
|
||||
}, {
|
||||
// enable 'force different users' for one rule (doesn't matter which)
|
||||
trigger: '.o_web_studio_sidebar label[for*="exclusive_user"]',
|
||||
extra_trigger: '.o_web_studio_snackbar .fa-check',
|
||||
}, {
|
||||
// leave studio
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
extra_trigger: '.o_web_studio_snackbar .fa-check',
|
||||
}, {
|
||||
// go back to kanban
|
||||
trigger: '.o_menu_brand',
|
||||
extra_trigger: '.o_web_client:not(.o_in_studio)'
|
||||
}, {
|
||||
// open first record (should be the one that was used, so the button should be there)
|
||||
trigger: '.o_kanban_view .o_kanban_record .o_dropdown_kanban .dropdown-toggle',
|
||||
}, {
|
||||
trigger: '.o_kanban_view .o_kanban_record .o-dropdown--menu .dropdown-item',
|
||||
},{
|
||||
// try to do the action
|
||||
trigger: 'button[studio_approval]',
|
||||
}, {
|
||||
// there should be a warning
|
||||
trigger: '.o_notification.border-warning'
|
||||
}, {
|
||||
trigger: '.breadcrumb .o_back_button'
|
||||
}]);
|
||||
|
||||
tour.register('web_studio_custom_field_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [{
|
||||
// go to Apps menu
|
||||
trigger: '.o_app[data-menu-xmlid="base.menu_management"]',
|
||||
}, {
|
||||
// click on the list view
|
||||
trigger: '.o_switch_view.o_list',
|
||||
}, {
|
||||
// click on optional column dropdown
|
||||
trigger: '.o_optional_columns_dropdown_toggle'
|
||||
}, {
|
||||
// click on add custom field
|
||||
trigger: '.dropdown-item-studio'
|
||||
}, {
|
||||
// go to home menu
|
||||
trigger: '.o_menu_toggle',
|
||||
extra_trigger: '.o_web_client.o_in_studio'
|
||||
}, {
|
||||
//leave studio
|
||||
trigger: '.o_web_studio_leave > a.btn'
|
||||
}, {
|
||||
// studio left.
|
||||
trigger: '.o_app[data-menu-xmlid="base.menu_management"]',
|
||||
extra_trigger: '.o_web_client:not(.o_in_studio)',
|
||||
}]);
|
||||
|
||||
tour.register('web_studio_local_storage_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [{
|
||||
trigger: '.o_app[data-menu-xmlid="base.menu_management"]',
|
||||
run: function () {
|
||||
localStorage.setItem('openStudioOnReload', "main");
|
||||
window.location.reload();
|
||||
},
|
||||
}, {
|
||||
// should be directly in studio mode
|
||||
trigger: '.o_app[data-menu-xmlid="base.menu_management"]',
|
||||
extra_trigger: '.o_web_client.o_in_studio'
|
||||
}, {
|
||||
trigger: '.o_menu_toggle',
|
||||
}, {
|
||||
trigger: '.o_web_studio_leave > a.btn',
|
||||
}, {
|
||||
// studio left.
|
||||
trigger: '.o_app[data-menu-xmlid="base.menu_management"]',
|
||||
extra_trigger: '.o_web_client:not(.o_in_studio)',
|
||||
run: function () {
|
||||
window.location.reload();
|
||||
},
|
||||
}, {
|
||||
// studio left after refresh.
|
||||
trigger: '.o_app[data-menu-xmlid="base.menu_management"]',
|
||||
extra_trigger: '.o_web_client:not(.o_in_studio)'
|
||||
}]);
|
||||
|
||||
});
|
||||
271
web_studio/static/tests/legacy/views/form_tests.js
Normal file
271
web_studio/static/tests/legacy/views/form_tests.js
Normal file
@@ -0,0 +1,271 @@
|
||||
odoo.define('web_studio.form_tests', function (require) {
|
||||
'use strict';
|
||||
|
||||
const FormView = require('web.FormView');
|
||||
const testUtils = require('web.test_utils');
|
||||
require('web_studio.FormRenderer');
|
||||
require('web_studio.FormController');
|
||||
const { legacyExtraNextTick } = require("@web/../tests/helpers/utils");
|
||||
|
||||
const createView = testUtils.createView;
|
||||
|
||||
QUnit.module(
|
||||
'Studio',
|
||||
{
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
display_name: { string: 'Displayed name', type: 'char' },
|
||||
int_field: { string: 'int_field', type: 'integer', sortable: true },
|
||||
bar: { string: 'Bar', type: 'boolean' },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
display_name: 'first record',
|
||||
int_field: 42,
|
||||
bar: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
display_name: 'second record',
|
||||
int_field: 27,
|
||||
bar: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
function () {
|
||||
QUnit.module('Form Approvals');
|
||||
|
||||
QUnit.test('approval widget basic rendering', async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
const form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
debug: true, // need to be in the viewport because of popover
|
||||
arch: `<form string="Partners">
|
||||
<sheet>
|
||||
<header>
|
||||
<button type="object=" name="someMethod" string="Apply Method" studio_approval="True"/>
|
||||
</header>
|
||||
<div name="button_box">
|
||||
<button class="oe_stat_button" studio_approval="True" id="visibleStat">
|
||||
<field name="int_field"/>
|
||||
</button>
|
||||
<button class="oe_stat_button" studio_approval="True"
|
||||
attrs='{"invisible": [["bar", "=", true]]}' id="invisibleStat">
|
||||
<field name="bar"/>
|
||||
</button>
|
||||
</div>
|
||||
<group>
|
||||
<group style="background-color: red">
|
||||
<field name="display_name" studio_approval="True"/>
|
||||
<field name="bar"/>
|
||||
<field name="int_field"/>
|
||||
</group>
|
||||
</group>
|
||||
<button type="object=" name="anotherMethod"
|
||||
string="Apply Second Method" studio_approval="True"/>
|
||||
</sheet>
|
||||
</form>`,
|
||||
res_id: 2,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'get_approval_spec') {
|
||||
assert.step('fetch_approval_spec');
|
||||
return Promise.resolve({
|
||||
rules: [
|
||||
{
|
||||
id: 1,
|
||||
group_id: [1, 'Internal User'],
|
||||
domain: false,
|
||||
can_validate: true,
|
||||
message: false,
|
||||
exclusive_user: false,
|
||||
},
|
||||
],
|
||||
entries: [],
|
||||
groups: [[1, 'Internal User']],
|
||||
});
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
session: { uid: 42 },
|
||||
});
|
||||
|
||||
await legacyExtraNextTick(); // wait for the approval button (owl) to render
|
||||
|
||||
// check that the widget was inserted on visible buttons only
|
||||
assert.containsOnce(form, 'button[name="someMethod"] .o_web_studio_approval');
|
||||
assert.containsOnce(form, '#visibleStat .o_web_studio_approval');
|
||||
assert.containsNone(form, '#invisibleStat .o_web_studio_approval');
|
||||
assert.containsOnce(form, 'button[name="anotherMethod"] .o_web_studio_approval');
|
||||
assert.containsNone(form, '.o_group .o_web_studio_approval');
|
||||
// should have fetched spec for exactly 3 buttons
|
||||
assert.verifySteps(['fetch_approval_spec', 'fetch_approval_spec', 'fetch_approval_spec']);
|
||||
// display popover
|
||||
await testUtils.dom.click('button[name="someMethod"] .o_web_studio_approval');
|
||||
assert.containsOnce($(document), '.o_popover');
|
||||
const popover = $(document).find('.o_popover');
|
||||
assert.containsOnce(popover, '.o_web_studio_approval_no_entry');
|
||||
assert.containsOnce(popover, '.o_web_approval_approve');
|
||||
assert.containsOnce(popover, '.o_web_approval_reject');
|
||||
assert.containsNone(popover, '.o_web_approval_cancel');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('approval check', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: `<form string="Partners">
|
||||
<sheet>
|
||||
<header>
|
||||
<button type="object" id="mainButton" name="someMethod"
|
||||
string="Apply Method" studio_approval="True"/>
|
||||
</header>
|
||||
<group>
|
||||
<group style="background-color: red">
|
||||
<field name="display_name"/>
|
||||
<field name="bar"/>
|
||||
<field name="int_field"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
res_id: 2,
|
||||
mockRPC: function (route, args) {
|
||||
const rule = {
|
||||
id: 1,
|
||||
group_id: [1, 'Internal User'],
|
||||
domain: false,
|
||||
can_validate: true,
|
||||
message: false,
|
||||
exclusive_user: false,
|
||||
};
|
||||
if (args.method === 'get_approval_spec') {
|
||||
assert.step('fetch_approval_spec');
|
||||
return Promise.resolve({
|
||||
rules: [rule],
|
||||
entries: [],
|
||||
groups: [[1, 'Internal User']],
|
||||
});
|
||||
} else if (args.method === 'check_approval') {
|
||||
assert.step('attempt_action');
|
||||
return Promise.resolve({
|
||||
approved: false,
|
||||
rules: [rule],
|
||||
entries: [],
|
||||
});
|
||||
} else if (args.method === 'someMethod') {
|
||||
/* the action of the button should not be
|
||||
called, as the approval is refused! if this
|
||||
code is traversed, the test *must* fail!
|
||||
that's why it's not included in the expected count
|
||||
or in the verifySteps call */
|
||||
assert.step('should_not_happen!');
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click('#mainButton');
|
||||
// first render, handle click, rerender after click
|
||||
assert.verifySteps(['fetch_approval_spec', 'attempt_action', 'fetch_approval_spec']);
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('approval widget basic flow', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
let hasValidatedRule;
|
||||
|
||||
const form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
debug: true, // need to be in the viewport because of popover
|
||||
arch: `<form string="Partners">
|
||||
<sheet>
|
||||
<header>
|
||||
<button type="object=" name="someMethod" string="Apply Method" studio_approval="True"/>
|
||||
</header>
|
||||
<group>
|
||||
<group style="background-color: red">
|
||||
<field name="display_name"/>
|
||||
<field name="bar"/>
|
||||
<field name="int_field"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
res_id: 2,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'get_approval_spec') {
|
||||
const spec = {
|
||||
rules: [
|
||||
{
|
||||
id: 1,
|
||||
group_id: [1, 'Internal User'],
|
||||
domain: false,
|
||||
can_validate: true,
|
||||
message: false,
|
||||
exclusive_user: false,
|
||||
},
|
||||
],
|
||||
entries: [],
|
||||
groups: [[1, 'Internal User']],
|
||||
};
|
||||
if (hasValidatedRule !== undefined) {
|
||||
spec.entries = [
|
||||
{
|
||||
id: 1,
|
||||
approved: hasValidatedRule,
|
||||
user_id: [42, 'Some rando'],
|
||||
write_date: '2020-04-07 12:43:48',
|
||||
rule_id: [1, 'someMethod/partner (Internal User)'],
|
||||
model: 'partner',
|
||||
res_id: 2,
|
||||
},
|
||||
];
|
||||
}
|
||||
return Promise.resolve(spec);
|
||||
} else if (args.method === 'set_approval') {
|
||||
hasValidatedRule = args.kwargs.approved;
|
||||
assert.step(hasValidatedRule ? 'approve_rule' : 'reject_rule');
|
||||
return Promise.resolve(true);
|
||||
} else if (args.method === 'delete_approval') {
|
||||
hasValidatedRule = undefined;
|
||||
assert.step('delete_approval');
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
session: { uid: 42 },
|
||||
});
|
||||
|
||||
await legacyExtraNextTick(); // wait for the approval button (owl) to render
|
||||
|
||||
// display popover and validate a rule, then cancel, then reject
|
||||
await testUtils.dom.click('button[name="someMethod"] .o_web_studio_approval');
|
||||
assert.containsOnce($(document), '.o_popover');
|
||||
await testUtils.dom.click('.o_popover button.o_web_approval_approve');
|
||||
await testUtils.dom.click('.o_popover button.o_web_approval_cancel');
|
||||
await testUtils.dom.click('.o_popover button.o_web_approval_reject');
|
||||
assert.verifySteps(['approve_rule', 'delete_approval', 'reject_rule']);
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
6328
web_studio/static/tests/legacy/views/view_editor_manager_tests.js
Normal file
6328
web_studio/static/tests/legacy/views/view_editor_manager_tests.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user