From baebb6a5b05ac8d59501a6071a5513e31a4ca047 Mon Sep 17 00:00:00 2001 From: Jorge Pinna Puissant Date: Tue, 4 Apr 2023 09:37:22 +0000 Subject: [PATCH] [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) --- .../static/tests/tours/calendar_tour.js | 4 +- .../tests/tours/create_crm_team_tour.js | 2 +- ...im_livechat_chatbot_steps_sequence_tour.js | 32 ++++++++------- .../views/activity/activity_arch_parser.js | 26 +++++++++--- .../web/fields/many2many_tags_email_tests.js | 6 +-- .../web/fields/onchange_on_keydown_tests.js | 2 +- .../src/js/tours/mass_mailing_editor_tour.js | 2 +- .../tests/tours/mrp_manual_consumption.js | 4 +- .../tests/tours/project_sharing_tour.js | 2 +- addons/sale/static/src/js/tours/sale.js | 2 +- .../product_configurator_pricelist_ui.js | 4 +- .../static/src/views/form/form_arch_parser.js | 8 ++-- .../static/src/views/form/form_compiler.js | 4 +- .../src/views/kanban/kanban_arch_parser.js | 32 ++++++++++++--- .../src/views/kanban/kanban_compiler.js | 2 +- .../static/src/views/kanban/kanban_record.js | 6 ++- .../static/src/views/list/list_arch_parser.js | 41 +++++++++++++++---- addons/web/static/src/views/view_compiler.js | 2 +- .../fields/many2many_tags_field_tests.js | 4 +- .../tests/views/form/form_compiler_tests.js | 20 ++++----- .../tests/views/form/form_view_tests.js | 18 ++++---- .../static/tests/views/kanban_view_tests.js | 2 +- .../settings_form_view_tests.js | 12 +++--- .../static/tests/tours/website_event.js | 6 +-- 24 files changed, 153 insertions(+), 90 deletions(-) diff --git a/addons/calendar/static/tests/tours/calendar_tour.js b/addons/calendar/static/tests/tours/calendar_tour.js index 0fded337c8c..08f0f1855f3 100644 --- a/addons/calendar/static/tests/tours/calendar_tour.js +++ b/addons/calendar/static/tests/tours/calendar_tour.js @@ -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()}`, }, diff --git a/addons/crm/static/tests/tours/create_crm_team_tour.js b/addons/crm/static/tests/tours/create_crm_team_tour.js index d4531ef6b62..dd9f4826ed9 100644 --- a/addons/crm/static/tests/tours/create_crm_team_tour.js +++ b/addons/crm/static/tests/tours/create_crm_team_tour.js @@ -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', diff --git a/addons/im_livechat/static/tests/tours/im_livechat_chatbot_steps_sequence_tour.js b/addons/im_livechat/static/tests/tours/im_livechat_chatbot_steps_sequence_tour.js index 3c22c651bcf..0d3131d4340 100644 --- a/addons/im_livechat/static/tests/tours/im_livechat_chatbot_steps_sequence_tour.js +++ b/addons/im_livechat/static/tests/tours/im_livechat_chatbot_steps_sequence_tour.js @@ -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", }, { diff --git a/addons/mail/static/src/views/activity/activity_arch_parser.js b/addons/mail/static/src/views/activity/activity_arch_parser.js index f5d0b77ed7a..e9a3e0f9205 100644 --- a/addons/mail/static/src/views/activity/activity_arch_parser.js +++ b/addons/mail/static/src/views/activity/activity_arch_parser.js @@ -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 { diff --git a/addons/mail/static/tests/web/fields/many2many_tags_email_tests.js b/addons/mail/static/tests/web/fields/many2many_tags_email_tests.js index 277212c0108..d7df7fa553b 100644 --- a/addons/mail/static/tests/web/fields/many2many_tags_email_tests.js +++ b/addons/mail/static/tests/web/fields/many2many_tags_email_tests.js @@ -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")); diff --git a/addons/mail/static/tests/web/fields/onchange_on_keydown_tests.js b/addons/mail/static/tests/web/fields/onchange_on_keydown_tests.js index 1b42e7db605..f25dbdda7dd 100644 --- a/addons/mail/static/tests/web/fields/onchange_on_keydown_tests.js +++ b/addons/mail/static/tests/web/fields/onchange_on_keydown_tests.js @@ -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 diff --git a/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js b/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js index 8438309458d..cae88ec8f13 100644 --- a/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js +++ b/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js @@ -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 diff --git a/addons/mrp/static/tests/tours/mrp_manual_consumption.js b/addons/mrp/static/tests/tours/mrp_manual_consumption.js index 155af8bb1e5..17e32d56b43 100644 --- a/addons/mrp/static/tests/tours/mrp_manual_consumption.js +++ b/addons/mrp/static/tests/tours/mrp_manual_consumption.js @@ -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', }, { diff --git a/addons/project/static/tests/tours/project_sharing_tour.js b/addons/project/static/tests/tours/project_sharing_tour.js index 0ce0b83cb11..fbe50508cf4 100644 --- a/addons/project/static/tests/tours/project_sharing_tour.js +++ b/addons/project/static/tests/tours/project_sharing_tour.js @@ -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', }, { diff --git a/addons/sale/static/src/js/tours/sale.js b/addons/sale/static/src/js/tours/sale.js index e375b3b6abd..b3401d500b1 100644 --- a/addons/sale/static/src/js/tours/sale.js +++ b/addons/sale/static/src/js/tours/sale.js @@ -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', diff --git a/addons/test_sale_product_configurators/static/tests/tours/product_configurator_pricelist_ui.js b/addons/test_sale_product_configurators/static/tests/tours/product_configurator_pricelist_ui.js index 1e002a289c6..9f03a113988 100644 --- a/addons/test_sale_product_configurators/static/tests/tours/product_configurator_pricelist_ui.js +++ b/addons/test_sale_product_configurators/static/tests/tours/product_configurator_pricelist_ui.js @@ -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", diff --git a/addons/web/static/src/views/form/form_arch_parser.js b/addons/web/static/src/views/form/form_arch_parser.js index c2130146420..d50975f9316 100644 --- a/addons/web/static/src/views/form/form_arch_parser.js +++ b/addons/web/static/src/views/form/form_arch_parser.js @@ -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") || "")) { diff --git a/addons/web/static/src/views/form/form_compiler.js b/addons/web/static/src/views/form/form_compiler.js index 3e87df25729..9b3232360bb 100644 --- a/addons/web/static/src/views/form/form_compiler.js +++ b/addons/web/static/src/views/form/form_compiler.js @@ -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); diff --git a/addons/web/static/src/views/kanban/kanban_arch_parser.js b/addons/web/static/src/views/kanban/kanban_arch_parser.js index e6dbae7d0f3..c0c2dab5886 100644 --- a/addons/web/static/src/views/kanban/kanban_arch_parser.js +++ b/addons/web/static/src/views/kanban/kanban_arch_parser.js @@ -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 { diff --git a/addons/web/static/src/views/kanban/kanban_compiler.js b/addons/web/static/src/views/kanban/kanban_compiler.js index 634e0892d20..b202caf2f99 100644 --- a/addons/web/static/src/views/kanban/kanban_compiler.js +++ b/addons/web/static/src/views/kanban/kanban_compiler.js @@ -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`); } diff --git a/addons/web/static/src/views/kanban/kanban_record.js b/addons/web/static/src/views/kanban/kanban_record.js index 384074d2b60..5584e411acb 100644 --- a/addons/web/static/src/views/kanban/kanban_record.js +++ b/addons/web/static/src/views/kanban/kanban_record.js @@ -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 { diff --git a/addons/web/static/src/views/list/list_arch_parser.js b/addons/web/static/src/views/list/list_arch_parser.js index b5180f3ae32..1f9b7157225 100644 --- a/addons/web/static/src/views/list/list_arch_parser.js +++ b/addons/web/static/src/views/list/list_arch_parser.js @@ -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 { diff --git a/addons/web/static/src/views/view_compiler.js b/addons/web/static/src/views/view_compiler.js index 78878e20511..956891dc524 100644 --- a/addons/web/static/src/views/view_compiler.js +++ b/addons/web/static/src/views/view_compiler.js @@ -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}'`); diff --git a/addons/web/static/tests/mobile/views/fields/many2many_tags_field_tests.js b/addons/web/static/tests/mobile/views/fields/many2many_tags_field_tests.js index b708762fd3b..4a553525600 100644 --- a/addons/web/static/tests/mobile/views/fields/many2many_tags_field_tests.js +++ b/addons/web/static/tests/mobile/views/fields/many2many_tags_field_tests.js @@ -44,7 +44,7 @@ QUnit.module("Fields", (hooks) => { `, }); - 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) => { `, }); - assert.strictEqual(target.querySelector("#timmy").placeholder, ""); + assert.strictEqual(target.querySelector("#timmy_0").placeholder, ""); }); }); diff --git a/addons/web/static/tests/views/form/form_compiler_tests.js b/addons/web/static/tests/views/form/form_compiler_tests.js index bfd60034259..ce2fe80fd56 100644 --- a/addons/web/static/tests/views/form/form_compiler_tests.js +++ b/addons/web/static/tests/views/form/form_compiler_tests.js @@ -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*/ `