[REF] web, *: Unique id for field nodes
Before this commit, the field node id (`field_id`) uses the field name for the first occurrence on the arch, and add an underscore and a number for the rest of the occurrences. This can create inconsistencies when sombody assumes that the field_id is equal to the name, and don't take into account the possibility of multiple occurrences. Now, a unique id is created since the first occurrence, this remove all ambiguity between the id and the name. Part-of task-id 3179751 closes odoo/odoo#117799 Related: odoo/enterprise#39511 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -27,12 +27,12 @@
|
||||
run: 'click',
|
||||
},
|
||||
{
|
||||
trigger: '#name',
|
||||
trigger: '#name_0',
|
||||
content: 'Give a name to the new event',
|
||||
run: 'text TEST EVENT',
|
||||
},
|
||||
{
|
||||
trigger: '#start',
|
||||
trigger: '#start_0',
|
||||
content: 'Give a date to the new event',
|
||||
run: `text ${todayDate()}`,
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ registry.category("web_tour.tours").add('create_crm_team_tour', {
|
||||
}, {
|
||||
trigger: 'button.o_list_button_add',
|
||||
}, {
|
||||
trigger: 'input[id="name"]',
|
||||
trigger: 'input[id="name_0"]',
|
||||
run: 'text My CRM Team',
|
||||
}, {
|
||||
trigger: 'button.o-kanban-button-new',
|
||||
|
||||
@@ -18,14 +18,14 @@ const commonSteps = [
|
||||
trigger: ".o_list_button_add",
|
||||
},
|
||||
{
|
||||
trigger: 'input[id="title"]',
|
||||
trigger: 'input[id="title_0"]',
|
||||
run: "text Test Chatbot Sequence",
|
||||
},
|
||||
{
|
||||
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a',
|
||||
},
|
||||
{
|
||||
trigger: "textarea#message",
|
||||
trigger: "textarea#message_0",
|
||||
run: "text Step 1",
|
||||
},
|
||||
{
|
||||
@@ -37,7 +37,7 @@ const commonSteps = [
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
trigger: "textarea#message",
|
||||
trigger: "textarea#message_0",
|
||||
run: "text Step 2",
|
||||
},
|
||||
{
|
||||
@@ -49,7 +49,7 @@ const commonSteps = [
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
trigger: "textarea#message",
|
||||
trigger: "textarea#message_0",
|
||||
run: "text Step 3",
|
||||
},
|
||||
];
|
||||
@@ -90,7 +90,7 @@ registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_with
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
trigger: "textarea#message",
|
||||
trigger: "textarea#message_0",
|
||||
run: "text Step 4",
|
||||
},
|
||||
{
|
||||
@@ -102,7 +102,7 @@ registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_with
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
trigger: "textarea#message",
|
||||
trigger: "textarea#message_0",
|
||||
run: "text Step 5",
|
||||
},
|
||||
{
|
||||
@@ -136,14 +136,16 @@ registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_with
|
||||
clientY: fromPosition.y,
|
||||
})
|
||||
);
|
||||
from.dispatchEvent(new MouseEvent("mousemove", {
|
||||
bubbles: true,
|
||||
which: 1,
|
||||
button: 0,
|
||||
// dragging is only enabled when the mouse have moved from at least 10 pixels from the original position
|
||||
clientX: fromPosition.x + 20,
|
||||
clientY: fromPosition.y + 20,
|
||||
}));
|
||||
from.dispatchEvent(
|
||||
new MouseEvent("mousemove", {
|
||||
bubbles: true,
|
||||
which: 1,
|
||||
button: 0,
|
||||
// dragging is only enabled when the mouse have moved from at least 10 pixels from the original position
|
||||
clientX: fromPosition.x + 20,
|
||||
clientY: fromPosition.y + 20,
|
||||
})
|
||||
);
|
||||
to.dispatchEvent(new Event("mouseenter", { bubbles: true }));
|
||||
from.dispatchEvent(new Event("mouseup", { bubbles: true }));
|
||||
},
|
||||
@@ -152,7 +154,7 @@ registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_with
|
||||
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a',
|
||||
},
|
||||
{
|
||||
trigger: "textarea#message",
|
||||
trigger: "textarea#message_0",
|
||||
run: "text Step 6",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ export class ActivityArchParser extends XMLParser {
|
||||
|
||||
const fieldNodes = {};
|
||||
const templateDocs = {};
|
||||
const fieldNextIds = {};
|
||||
const activeFields = {};
|
||||
|
||||
this.visitXML(xmlDoc, (node) => {
|
||||
@@ -28,9 +29,12 @@ export class ActivityArchParser extends XMLParser {
|
||||
"activity",
|
||||
jsClass
|
||||
);
|
||||
const name = fieldInfo.name;
|
||||
fieldNodes[name] = fieldInfo;
|
||||
node.setAttribute("field_id", name);
|
||||
if (!(fieldInfo.name in fieldNextIds)) {
|
||||
fieldNextIds[fieldInfo.name] = 0;
|
||||
}
|
||||
const fieldId = `${fieldInfo.name}_${fieldNextIds[fieldInfo.name]++}`;
|
||||
fieldNodes[fieldId] = fieldInfo;
|
||||
node.setAttribute("field_id", fieldId);
|
||||
|
||||
addFieldDependencies(
|
||||
activeFields,
|
||||
@@ -47,8 +51,20 @@ export class ActivityArchParser extends XMLParser {
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, field] of Object.entries(fieldNodes)) {
|
||||
activeFields[key] = field;
|
||||
// TODO: generate activeFields for the model based on fieldNodes (merge duplicated fields)
|
||||
for (const fieldNode of Object.values(fieldNodes)) {
|
||||
const fieldName = fieldNode.name;
|
||||
if (activeFields[fieldName]) {
|
||||
const { alwaysInvisible } = fieldNode;
|
||||
activeFields[fieldName] = {
|
||||
...fieldNode,
|
||||
// a field can only be considered to be always invisible
|
||||
// if all its nodes are always invisible
|
||||
alwaysInvisible: activeFields[fieldName].alwaysInvisible && alwaysInvisible,
|
||||
};
|
||||
} else {
|
||||
activeFields[fieldName] = fieldNode;
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
|
||||
@@ -62,15 +62,15 @@ QUnit.test("fieldmany2many tags email (edition)", async (assert) => {
|
||||
"there should be one modal opened to edit the empty email"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$(".modal-content .o_form_view .o_input#name").val(),
|
||||
$(".modal-content .o_form_view .o_input#name_0").val(),
|
||||
"silver",
|
||||
"the opened modal in edit mode should be a form view dialog with the res.partner 14"
|
||||
);
|
||||
assert.containsOnce($, ".modal-content .o_form_view .o_input#email");
|
||||
assert.containsOnce($, ".modal-content .o_form_view .o_input#email_0");
|
||||
|
||||
// set the email and save the modal (will rerender the form view)
|
||||
await testUtils.fields.editInput(
|
||||
$(".modal-content .o_form_view .o_input#email"),
|
||||
$(".modal-content .o_form_view .o_input#email_0"),
|
||||
"coucou@petite.perruche"
|
||||
);
|
||||
await testUtils.dom.click($(".modal-content .o_form_button_save"));
|
||||
|
||||
@@ -58,7 +58,7 @@ QUnit.test(
|
||||
}
|
||||
},
|
||||
});
|
||||
const textarea = $('textarea[id="description"]')[0];
|
||||
const textarea = $('textarea[id="description_0"]')[0];
|
||||
await dom.click(textarea);
|
||||
for (const key of "testing the keydown event") {
|
||||
// trigger each key separately to simulate a user typing
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
trigger: '[name="body_arch"] iframe .o_editable h1',
|
||||
run: () => {},
|
||||
}, {
|
||||
trigger: 'input#subject',
|
||||
trigger: 'input#subject_0',
|
||||
run: 'text Test',
|
||||
}, {
|
||||
trigger: '.o_form_view', // blur previous input
|
||||
|
||||
@@ -23,7 +23,7 @@ registry.category("web_tour.tours").add('test_mrp_manual_consumption', {
|
||||
trigger: ".o_pager_value:first()",
|
||||
},
|
||||
{
|
||||
trigger: "input[id='qty_producing']",
|
||||
trigger: "input[id='qty_producing_0']",
|
||||
run: 'text 8.0',
|
||||
},
|
||||
{
|
||||
@@ -48,7 +48,7 @@ registry.category("web_tour.tours").add('test_mrp_manual_consumption', {
|
||||
run: 'click',
|
||||
},
|
||||
{
|
||||
trigger: "input[id='qty_producing']",
|
||||
trigger: "input[id='qty_producing_0']",
|
||||
run: 'text 2.0',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ const projectSharingSteps = [...stepUtils.goToAppSteps("project.menu_main_pm", '
|
||||
content: 'Select "Edit" as Access mode in the "Share Project" wizard.',
|
||||
}, {
|
||||
trigger: '.o_field_many2many_tags_email[name=partner_ids] input',
|
||||
extra_trigger: 'label[for=partner_ids]:contains("Invite People")',
|
||||
extra_trigger: 'label[for=partner_ids_0]:contains("Invite People")',
|
||||
content: 'Select the user portal as collaborator to the "Project Sharing" project.',
|
||||
run: 'text Georges',
|
||||
}, {
|
||||
|
||||
@@ -28,7 +28,7 @@ registry.category("web_tour.tours").add("sale_tour", {
|
||||
position: "bottom",
|
||||
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
|
||||
}, {
|
||||
trigger: 'input[id=street]',
|
||||
trigger: 'input[id=street_0]',
|
||||
content: _t("Complete your company's data"),
|
||||
position: "bottom",
|
||||
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
|
||||
|
||||
+2
-2
@@ -24,12 +24,12 @@ stepUtils.showAppsMenuItem(),
|
||||
trigger: 'ul.ui-autocomplete > li > a:contains(Azure)',
|
||||
}, {
|
||||
content: "search the pricelist",
|
||||
trigger: 'input[id="pricelist_id"]',
|
||||
trigger: 'input[id="pricelist_id_0"]',
|
||||
// Wait for onchange to come back
|
||||
extra_trigger: "[name=partner_id]:contains(Fremont)",
|
||||
}, {
|
||||
content: "search the pricelist",
|
||||
trigger: 'input[id="pricelist_id"]',
|
||||
trigger: 'input[id="pricelist_id_0"]',
|
||||
run: 'text Custo'
|
||||
}, {
|
||||
content: "select the pricelist",
|
||||
|
||||
@@ -20,12 +20,10 @@ export class FormArchParser extends XMLParser {
|
||||
this.visitXML(xmlDoc, (node) => {
|
||||
if (node.tagName === "field") {
|
||||
const fieldInfo = Field.parseFieldNode(node, models, modelName, "form", jsClass);
|
||||
let fieldId = fieldInfo.name;
|
||||
if (fieldInfo.name in fieldNextIds) {
|
||||
fieldId = `${fieldInfo.name}_${fieldNextIds[fieldInfo.name]++}`;
|
||||
} else {
|
||||
fieldNextIds[fieldInfo.name] = 1;
|
||||
if (!(fieldInfo.name in fieldNextIds)) {
|
||||
fieldNextIds[fieldInfo.name] = 0;
|
||||
}
|
||||
const fieldId = `${fieldInfo.name}_${fieldNextIds[fieldInfo.name]++}`;
|
||||
fieldNodes[fieldId] = fieldInfo;
|
||||
node.setAttribute("field_id", fieldId);
|
||||
if (archParseBoolean(node.getAttribute("default_focus") || "")) {
|
||||
|
||||
@@ -189,7 +189,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
|
||||
const fieldName = el.getAttribute("name");
|
||||
const fieldString = el.getAttribute("string");
|
||||
const fieldId = el.getAttribute("field_id") || fieldName;
|
||||
const fieldId = el.getAttribute("field_id");
|
||||
const labelsForAttr = el.getAttribute("id") || fieldName;
|
||||
const labels = this.getLabels(labelsForAttr);
|
||||
const dynamicLabel = (label) => {
|
||||
@@ -609,7 +609,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
setting.setAttribute("fieldName", toStringExpression(fieldName));
|
||||
setting.setAttribute(
|
||||
"fieldId",
|
||||
toStringExpression(child.getAttribute("field_id") || fieldName)
|
||||
toStringExpression(child.getAttribute("field_id"))
|
||||
);
|
||||
}
|
||||
append(setting, fieldSlot);
|
||||
|
||||
@@ -53,6 +53,7 @@ export class KanbanArchParser extends XMLParser {
|
||||
const tooltipInfo = {};
|
||||
let handleField = null;
|
||||
const fieldNodes = {};
|
||||
const fieldNextIds = {};
|
||||
const widgetNodes = {};
|
||||
let widgetNextId = 0;
|
||||
const jsClass = xmlDoc.getAttribute("js_class");
|
||||
@@ -111,8 +112,12 @@ export class KanbanArchParser extends XMLParser {
|
||||
fieldInfo.forceSave = true;
|
||||
}
|
||||
const name = fieldInfo.name;
|
||||
fieldNodes[name] = fieldInfo;
|
||||
node.setAttribute("field_id", name);
|
||||
if (!(fieldInfo.name in fieldNextIds)) {
|
||||
fieldNextIds[fieldInfo.name] = 0;
|
||||
}
|
||||
const fieldId = `${fieldInfo.name}_${fieldNextIds[fieldInfo.name]++}`;
|
||||
fieldNodes[fieldId] = fieldInfo;
|
||||
node.setAttribute("field_id", fieldId);
|
||||
if (fieldInfo.options.group_by_tooltip) {
|
||||
tooltipInfo[name] = fieldInfo.options.group_by_tooltip;
|
||||
}
|
||||
@@ -140,8 +145,12 @@ export class KanbanArchParser extends XMLParser {
|
||||
// Keep track of last update so images can be reloaded when they may have changed.
|
||||
if (node.tagName === "img") {
|
||||
const attSrc = node.getAttribute("t-att-src");
|
||||
if (attSrc && /\bkanban_image\b/.test(attSrc) && !fieldNodes.write_date) {
|
||||
fieldNodes.write_date = { type: "datetime" };
|
||||
if (
|
||||
attSrc &&
|
||||
/\bkanban_image\b/.test(attSrc) &&
|
||||
!Object.values(fieldNodes).some((f) => f.name === "write_date")
|
||||
) {
|
||||
fieldNodes.write_date_0 = { name: "write_date", type: "datetime" };
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -170,8 +179,19 @@ export class KanbanArchParser extends XMLParser {
|
||||
defaultOrder = stringToOrderBy(handleField);
|
||||
}
|
||||
|
||||
for (const [key, field] of Object.entries(fieldNodes)) {
|
||||
activeFields[key] = field; // TODO process
|
||||
for (const fieldNode of Object.values(fieldNodes)) {
|
||||
const fieldName = fieldNode.name;
|
||||
if (activeFields[fieldName]) {
|
||||
const { alwaysInvisible } = fieldNode;
|
||||
activeFields[fieldName] = {
|
||||
...fieldNode,
|
||||
// a field can only be considered to be always invisible
|
||||
// if all its nodes are always invisible
|
||||
alwaysInvisible: activeFields[fieldName].alwaysInvisible && alwaysInvisible,
|
||||
};
|
||||
} else {
|
||||
activeFields[fieldName] = fieldNode;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -119,7 +119,7 @@ export class KanbanCompiler extends ViewCompiler {
|
||||
compiled = createElement("span", { "t-out": `record["${fieldName}"].value` });
|
||||
} else {
|
||||
compiled = super.compileField(el, params);
|
||||
const fieldId = el.getAttribute("field_id") || el.getAttribute("name");
|
||||
const fieldId = el.getAttribute("field_id");
|
||||
compiled.setAttribute("id", `'${fieldId}_' + __comp__.props.record.id`);
|
||||
}
|
||||
|
||||
|
||||
@@ -312,12 +312,14 @@ export class KanbanRecord extends Component {
|
||||
}
|
||||
case "set_cover": {
|
||||
const { autoOpen, fieldName } = params;
|
||||
const { widget } = archInfo.fieldNodes[fieldName];
|
||||
const widgets = Object.values(archInfo.fieldNodes)
|
||||
.filter((x) => x.name === fieldName)
|
||||
.map((x) => x.widget);
|
||||
const field = record.fields[fieldName];
|
||||
if (
|
||||
field.type === "many2one" &&
|
||||
field.relation === "ir.attachment" &&
|
||||
widget === "attachment_image"
|
||||
widgets.includes("attachment_image")
|
||||
) {
|
||||
this.dialog.add(KanbanCoverImageDialog, { autoOpen, fieldName, record });
|
||||
} else {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { encodeObjectForTemplate } from "@web/views/view_compiler";
|
||||
export class GroupListArchParser extends XMLParser {
|
||||
parse(arch, models, modelName, jsClass) {
|
||||
const fieldNodes = {};
|
||||
const fieldNextIds = {};
|
||||
const buttons = [];
|
||||
let buttonId = 0;
|
||||
this.visitXML(arch, (node) => {
|
||||
@@ -27,12 +28,20 @@ export class GroupListArchParser extends XMLParser {
|
||||
return false;
|
||||
} else if (node.tagName === "field") {
|
||||
const fieldInfo = Field.parseFieldNode(node, models, modelName, "list", jsClass);
|
||||
fieldNodes[fieldInfo.name] = fieldInfo;
|
||||
node.setAttribute("field_id", fieldInfo.name);
|
||||
if (!(fieldInfo.name in fieldNextIds)) {
|
||||
fieldNextIds[fieldInfo.name] = 0;
|
||||
}
|
||||
const fieldId = `${fieldInfo.name}_${fieldNextIds[fieldInfo.name]++}`;
|
||||
fieldNodes[fieldId] = fieldInfo;
|
||||
node.setAttribute("field_id", fieldId);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return { fieldNodes, buttons };
|
||||
const activeFields = {};
|
||||
for (const fieldNode of Object.values(fieldNodes)) {
|
||||
activeFields[fieldNode.name] = fieldNode;
|
||||
}
|
||||
return { fieldNodes, activeFields, buttons };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +83,7 @@ export class ListArchParser extends XMLParser {
|
||||
const treeAttr = {};
|
||||
let nextId = 0;
|
||||
const activeFields = {};
|
||||
const fieldNextIds = {};
|
||||
this.visitXML(arch, (node) => {
|
||||
if (node.tagName !== "button") {
|
||||
buttonGroup = undefined;
|
||||
@@ -101,8 +111,12 @@ export class ListArchParser extends XMLParser {
|
||||
}
|
||||
} else if (node.tagName === "field") {
|
||||
const fieldInfo = this.parseFieldNode(node, models, modelName);
|
||||
fieldNodes[fieldInfo.name] = fieldInfo;
|
||||
node.setAttribute("field_id", fieldInfo.name);
|
||||
if (!(fieldInfo.name in fieldNextIds)) {
|
||||
fieldNextIds[fieldInfo.name] = 0;
|
||||
}
|
||||
const fieldId = `${fieldInfo.name}_${fieldNextIds[fieldInfo.name]++}`;
|
||||
fieldNodes[fieldId] = fieldInfo;
|
||||
node.setAttribute("field_id", fieldId);
|
||||
if (fieldInfo.widget === "handle") {
|
||||
handleField = fieldInfo.name;
|
||||
}
|
||||
@@ -158,7 +172,7 @@ export class ListArchParser extends XMLParser {
|
||||
const groupByArchInfo = groupListArchParser.parse(groupByArch, models, coModelName);
|
||||
groupBy.buttons[fieldName] = groupByArchInfo.buttons;
|
||||
groupBy.fields[fieldName] = {
|
||||
activeFields: groupByArchInfo.fieldNodes,
|
||||
activeFields: groupByArchInfo.activeFields,
|
||||
fieldNodes: groupByArchInfo.fieldNodes,
|
||||
fields: models[coModelName],
|
||||
};
|
||||
@@ -233,8 +247,19 @@ export class ListArchParser extends XMLParser {
|
||||
treeAttr.defaultOrder = stringToOrderBy(handleField);
|
||||
}
|
||||
|
||||
for (const [key, field] of Object.entries(fieldNodes)) {
|
||||
activeFields[key] = field; // TODO process
|
||||
for (const fieldNode of Object.values(fieldNodes)) {
|
||||
const fieldName = fieldNode.name;
|
||||
if (activeFields[fieldName]) {
|
||||
const { alwaysInvisible } = fieldNode;
|
||||
activeFields[fieldName] = {
|
||||
...fieldNode,
|
||||
// a field can only be considered to be always invisible
|
||||
// if all its nodes are always invisible
|
||||
alwaysInvisible: activeFields[fieldName].alwaysInvisible && alwaysInvisible,
|
||||
};
|
||||
} else {
|
||||
activeFields[fieldName] = fieldNode;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -361,7 +361,7 @@ export class ViewCompiler {
|
||||
*/
|
||||
compileField(el, params) {
|
||||
const fieldName = el.getAttribute("name");
|
||||
const fieldId = el.getAttribute("field_id") || fieldName;
|
||||
const fieldId = el.getAttribute("field_id");
|
||||
|
||||
const field = createElement("Field");
|
||||
field.setAttribute("id", `'${fieldId}'`);
|
||||
|
||||
@@ -44,7 +44,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="timmy" widget="many2many_tags" placeholder="foo"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.strictEqual(target.querySelector("#timmy").placeholder, "foo");
|
||||
assert.strictEqual(target.querySelector("#timmy_0").placeholder, "foo");
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyTagsField placeholder should be empty", async function (assert) {
|
||||
@@ -57,6 +57,6 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="timmy" widget="many2many_tags"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.strictEqual(target.querySelector("#timmy").placeholder, "");
|
||||
assert.strictEqual(target.querySelector("#timmy_0").placeholder, "");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
QUnit.test(
|
||||
"label with empty string compiles to FormLabel with empty string",
|
||||
async (assert) => {
|
||||
const arch = /*xml*/ `<form><field name="test"/><label for="test" string=""/></form>`;
|
||||
const arch = /*xml*/ `<form><field field_id="test" name="test"/><label for="test" string=""/></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
@@ -62,7 +62,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
);
|
||||
|
||||
QUnit.test("properly compile simple div with field", async (assert) => {
|
||||
const arch = /*xml*/ `<form><div class="someClass">lol<field name="display_name"/></div></form>`;
|
||||
const arch = /*xml*/ `<form><div class="someClass">lol<field field_id="display_name" name="display_name"/></div></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
@@ -80,8 +80,8 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<group>
|
||||
<group><field name="display_name"/></group>
|
||||
<group><field name="charfield"/></group>
|
||||
<group><field field_id="display_name" name="display_name"/></group>
|
||||
<group><field field_id="charfield" name="charfield"/></group>
|
||||
</group>
|
||||
</form>`;
|
||||
const expected = /*xml*/ `
|
||||
@@ -112,7 +112,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
<group>
|
||||
<form>
|
||||
<div>
|
||||
<field name="test"/>
|
||||
<field field_id="test" name="test"/>
|
||||
</div>
|
||||
</form>
|
||||
</group>
|
||||
@@ -143,8 +143,8 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<notebook>
|
||||
<page name="p1" string="Page1"><field name="charfield"/></page>
|
||||
<page name="p2" string="Page2"><field name="display_name"/></page>
|
||||
<page name="p1" string="Page1"><field field_id="charfield" name="charfield"/></page>
|
||||
<page name="p2" string="Page2"><field field_id="display_name" name="display_name"/></page>
|
||||
</notebook>
|
||||
</form>`;
|
||||
|
||||
@@ -164,7 +164,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
QUnit.test("properly compile field without placeholder", async (assert) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name" placeholder="e.g. Contact's Name or //someinfo..."/>
|
||||
<field field_id="display_name" name="display_name" placeholder="e.g. Contact's Name or //someinfo..."/>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
@@ -232,7 +232,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
// </form>````
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name" modifiers="{"invisible": true}" />
|
||||
<field field_id="display_name" name="display_name" modifiers="{"invisible": true}" />
|
||||
<div class="visible3" modifiers="{"invisible": false}"/>
|
||||
<div modifiers="{"invisible": [["display_name", "=", "take"]]}"/>
|
||||
</form>`;
|
||||
@@ -299,7 +299,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
help="this is bar"
|
||||
documentation="/applications/technical/web/settings/this_is_a_test.html"
|
||||
company_dependent="1">
|
||||
<field name="bar"/>
|
||||
<field field_id="bar" name="bar"/>
|
||||
<label>label with content</label>
|
||||
</setting>
|
||||
</form>`;
|
||||
|
||||
@@ -863,9 +863,9 @@ QUnit.module("Views", (hooks) => {
|
||||
assert.hasClass(target.querySelector(".o_field_widget[name=foo]"), "o_field_char");
|
||||
assert.hasClass(target.querySelector(".o_field_widget[name=foo]"), "col-lg-6");
|
||||
|
||||
assert.hasClass(target.querySelector(".o_form_label[for=foo]"), "plop");
|
||||
assert.hasClass(target.querySelector(".o_form_label[for=foo]"), "plop2");
|
||||
assert.hasClass(target.querySelector(".o_form_label[for=foo]"), "col-lg-6");
|
||||
assert.hasClass(target.querySelector(".o_form_label[for=foo_0]"), "plop");
|
||||
assert.hasClass(target.querySelector(".o_form_label[for=foo_0]"), "plop2");
|
||||
assert.hasClass(target.querySelector(".o_form_label[for=foo_0]"), "col-lg-6");
|
||||
});
|
||||
|
||||
QUnit.test("group containing both a field and a group", async function (assert) {
|
||||
@@ -2350,14 +2350,14 @@ QUnit.module("Views", (hooks) => {
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=foo] sup"));
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=foo_0] sup"));
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o-tooltip .o-tooltip--help").textContent,
|
||||
"foo tooltip"
|
||||
);
|
||||
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=bar] sup"));
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=bar_0] sup"));
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o-tooltip .o-tooltip--help").textContent,
|
||||
@@ -4911,7 +4911,7 @@ QUnit.module("Views", (hooks) => {
|
||||
".o_statusbar_status button",
|
||||
"Must have only one statusbar button"
|
||||
);
|
||||
await editInput(target, "input#name.o_input", "xpad");
|
||||
await editInput(target, "input#name_0.o_input", "xpad");
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_statusbar_status button",
|
||||
@@ -8750,7 +8750,7 @@ QUnit.module("Views", (hooks) => {
|
||||
assert.containsOnce(rows[0], "> .o_cell", "Should only contain one cell");
|
||||
assert.containsOnce(rows[0], ".o_horizontal_separator");
|
||||
assert.containsN(rows[1], "> .o_cell", 2, "Should contain 2 cells");
|
||||
assert.containsOnce(rows[1], "label[for=display_name]");
|
||||
assert.containsOnce(rows[1], "label[for=display_name_0]");
|
||||
assert.containsOnce(rows[1], "div[name=display_name]");
|
||||
}
|
||||
);
|
||||
@@ -11220,14 +11220,14 @@ QUnit.module("Views", (hooks) => {
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=product_id] sup"));
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=product_id_0] sup"));
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o-tooltip .o-tooltip--help").textContent,
|
||||
"this is a tooltip\n\nValues set here are company-specific."
|
||||
);
|
||||
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=foo] sup"));
|
||||
await mouseEnter(target.querySelector(".o_form_label[for=foo_0] sup"));
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o-tooltip .o-tooltip--help").textContent,
|
||||
|
||||
@@ -867,7 +867,7 @@ QUnit.module("Views", (hooks) => {
|
||||
str: "some string",
|
||||
bool: "true",
|
||||
num: "4.5",
|
||||
field_id: "int_field",
|
||||
field_id: "int_field_0",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,7 +658,7 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
"checkbox should not be checked"
|
||||
);
|
||||
assert.containsNone(target, ".o_dirty_warning", "warning message should not be shown");
|
||||
await click(target.querySelector(".o_field_boolean input[id=bar]"));
|
||||
await click(target.querySelector(".o_field_boolean input[id=bar_0]"));
|
||||
assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked");
|
||||
|
||||
window.dispatchEvent(new Event("beforeunload"));
|
||||
@@ -1139,7 +1139,7 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
"checkbox should not be checked"
|
||||
);
|
||||
assert.containsNone(target, ".o_dirty_warning", "warning message should not be shown");
|
||||
await click(target.querySelector(".o_field_boolean input[id=bar]"));
|
||||
await click(target.querySelector(".o_field_boolean input[id=bar_0]"));
|
||||
assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked");
|
||||
assert.containsOnce(target, ".o_dirty_warning", "warning message should be shown");
|
||||
});
|
||||
@@ -1170,7 +1170,7 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await click(target.querySelector("input[id=bar]"));
|
||||
await click(target.querySelector("input[id=bar_0]"));
|
||||
assert.containsOnce(target, ".o_dirty_warning", "warning message should be shown");
|
||||
await click(target.querySelector(".o_form_button_save"));
|
||||
assert.containsOnce(target, ".o_dirty_warning", "warning message should be shown");
|
||||
@@ -1517,8 +1517,8 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
<SettingsPage slots="{NoContentHelper:__comp__.props.slots.NoContentHelper}" initialTab="__comp__.props.initialApp" t-slot-scope="settings" modules="[{"key":"crm","string":"CRM","imgurl":"/crm/static/description/icon.png"}]">
|
||||
<SettingsApp key="\`crm\`" string="\`CRM\`" imgurl="\`/crm/static/description/icon.png\`" selectedTab="settings.selectedTab">
|
||||
<SearchableSetting title="\`\`" help="\`\`" companyDependent="false" documentation="\`\`" record="__comp__.props.record" string="\`\`" addLabel="true" labels="["\`My\\" little ' Label\`"]">
|
||||
<FormLabel id="'display_name'" fieldName="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']" className=""highhopes"" string="\`My" little ' Label\`"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']"/>
|
||||
<FormLabel id="'display_name_0'" fieldName="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name_0']" className=""highhopes"" string="\`My" little ' Label\`"/>
|
||||
<Field id="'display_name_0'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name_0']"/>
|
||||
</SearchableSetting>
|
||||
</SettingsApp>
|
||||
</SettingsPage>`;
|
||||
@@ -1559,7 +1559,7 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
|
||||
const expectedCompiled = `
|
||||
<HighlightText originalText="\`this is Baz value: \`"/>
|
||||
<Field id="'baz'" name="'baz'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['baz']"/>
|
||||
<Field id="'baz_0'" name="'baz'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['baz_0']"/>
|
||||
<HighlightText originalText="\` and this is the after text\`"/>`;
|
||||
assert.areEquivalent(
|
||||
compiled.querySelector("SearchableSetting div.text-muted").innerHTML,
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
trigger: '.modal-dialog div[name=date_begin]',
|
||||
content: _t("Open date range picker. Pick a Start date for your event"),
|
||||
run: function () {
|
||||
$('input[id="date_begin"]').val('09/30/2020 08:00:00').change();
|
||||
$('input[id="date_end"]').val('10/02/2020 23:00:00').change();
|
||||
$('input[id="date_begin"]').click();
|
||||
$('input[id="date_begin_0"]').val('09/30/2020 08:00:00').change();
|
||||
$('input[id="date_end_0"]').val('10/02/2020 23:00:00').change();
|
||||
$('input[id="date_begin_0"]').click();
|
||||
}
|
||||
}, {
|
||||
content: _t("Apply change."),
|
||||
|
||||
Reference in New Issue
Block a user