From 77aadf471ce95de10b240276d45bb5737589dc52 Mon Sep 17 00:00:00 2001 From: "Julien Carion (juca)" Date: Wed, 10 Apr 2024 08:26:37 +0000 Subject: [PATCH] [FIX] web, *: autocomplete cancels search on click out This commit fixes a bug where the value of an autocomplete with autoselect could go into an undefined state where the model value doesn't match the input value but no change is detected. This would happen on escape press, scroll and more recently due to https://github.com/odoo/odoo/pull/159333 on click out. To fix this issue, the commit introduces some change in the autocomplete behavior so that it will no longer select the active option on click out (blur) when autoselect is active and instead it will revert the input value to the one stored into the model. Also applies this behavior on escape key press and scroll for the before mentioned reasons. Steps to reproduce: - Go to CRM form view - Edit the salesperson name to some non existant one and click out (or scroll or press escape) Before the fix, the name was stuck to the invalid one but no change was detected so we couldnt save it. Before https://github.com/odoo/odoo/pull/159333, click out would select the active option of the dropdown instead (and launch a dialog for quick create). This is removed because the behavior is neither intuitive nor practical. After the fix, the name is reset to its previous valid value instead. This applies to all autocomplete components with autoselect prop set to true. closes odoo/odoo#162040 X-original-commit: 9d9026ed0c7bb0af6cf910cc206cf8b55d1b7a36 Signed-off-by: Aaron Bohy (aab) Signed-off-by: Julien Carion (juca) --- .../src/js/partner_autocomplete_fieldchar.js | 3 + .../tests/partner_autocomplete_tests.js | 12 +-- .../sale/static/src/js/sale_product_field.js | 2 +- .../static/tests/sale_product_field_tests.js | 21 +++-- .../src/core/autocomplete/autocomplete.js | 35 ++++---- .../views/fields/many2one/many2one_field.js | 7 +- .../views/fields/properties/property_value.js | 4 - .../src/views/fields/relational_utils.js | 3 + .../src/views/fields/relational_utils.xml | 1 + .../static/tests/core/autocomplete_tests.js | 47 +++++++++-- .../fields/many2many_tags_field_tests.js | 6 +- .../views/fields/many2one_field_tests.js | 80 +++++++++---------- .../tests/views/kanban/kanban_view_tests.js | 3 +- .../web/static/tests/views/list_view_tests.js | 13 +-- 14 files changed, 137 insertions(+), 100 deletions(-) diff --git a/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js b/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js index c57e85fb726..d52fd91af4f 100644 --- a/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js +++ b/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js @@ -73,6 +73,9 @@ export class PartnerAutoCompleteCharField extends CharField { } }); this.props.record.update(data.company); + if (this.props.setDirty) { + this.props.setDirty(false); + } } } diff --git a/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js index c80c30b8584..727b8b404df 100644 --- a/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js +++ b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js @@ -4,11 +4,11 @@ import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; import { click, + editInput, editSelect, getFixture, patchWithCleanup, triggerEvent, - editInput, } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { loadJS } from "@web/core/assets"; @@ -348,18 +348,20 @@ QUnit.module('partner_autocomplete', { } }); - QUnit.test("Show confirmation dialog on input blur", async function (assert) { - assert.expect(1); + QUnit.test("Click out after edition", async function (assert) { + assert.expect(2); await makeView(makeViewParams); const input = target.querySelector("[name=parent_id] input.o-autocomplete--input.o_input"); await triggerEvent(input, null, "focus"); await click(input); await editInput(input, null, "go"); + assert.strictEqual(input.value, "go"); + await triggerEvent(target, null, "pointerdown"); await triggerEvent(input, null, "blur"); - assert.containsOnce(target, ".o_dialog"); + assert.strictEqual(input.value, ""); }); - QUnit.test("Hide auto complate suggestion for no create", async function (assert) { + QUnit.test("Hide auto complete suggestion for no create", async function (assert) { const partnerMakeViewParams = { ...makeViewParams, arch: diff --git a/addons/sale/static/src/js/sale_product_field.js b/addons/sale/static/src/js/sale_product_field.js index cd0856ce332..eec807b165d 100644 --- a/addons/sale/static/src/js/sale_product_field.js +++ b/addons/sale/static/src/js/sale_product_field.js @@ -20,7 +20,7 @@ export class SaleOrderLineProductField extends Many2OneField { isInternalUpdate = true; return updateRecord.call(this, value); }; - useEffect(value => { + useEffect(value => { if (!isMounted) { isMounted = true; } else if (value && isInternalUpdate) { diff --git a/addons/sale/static/tests/sale_product_field_tests.js b/addons/sale/static/tests/sale_product_field_tests.js index f899c70468f..6bc027df492 100644 --- a/addons/sale/static/tests/sale_product_field_tests.js +++ b/addons/sale/static/tests/sale_product_field_tests.js @@ -1,7 +1,13 @@ /** @odoo-module **/ import { - getFixture, patchWithCleanup, addRow, editInput, triggerEvent, click } from "@web/../tests/helpers/utils"; + getFixture, + patchWithCleanup, + addRow, + editInput, + triggerHotkey, + nextTick +} from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { browser } from "@web/core/browser/browser"; @@ -88,7 +94,7 @@ QUnit.module("Fields", (hooks) => { QUnit.module("Sale product field"); - QUnit.test("blurring input with incomplete text will propose to create product", async function (assert) { + QUnit.test("pressing tab with incomplete text will create a product", async function (assert) { await makeView({ type: "form", @@ -112,23 +118,16 @@ QUnit.module("Fields", (hooks) => { // add a line and enter new product name await addRow(target, ".o_field_x2many_list"); await editInput(target, "[name='product_template_id'] input", "new product"); - - // blur input => should ask for confirmation if we want to create product - await triggerEvent(target, "[name='product_template_id'] input", "blur"); - assert.containsOnce(target, ".modal:contains(Create new product as a new Product)") + await triggerHotkey("tab"); + await nextTick(); assert.verifySteps([ "get_views", "onchange", "onchange", "name_search", - ]); - - await click(target, ".modal button.btn-primary"); - assert.verifySteps([ "name_create", "get_single_product_variant", ]); - }); }); diff --git a/addons/web/static/src/core/autocomplete/autocomplete.js b/addons/web/static/src/core/autocomplete/autocomplete.js index b5dcb0604d1..9e23db6d0fb 100644 --- a/addons/web/static/src/core/autocomplete/autocomplete.js +++ b/addons/web/static/src/core/autocomplete/autocomplete.js @@ -104,6 +104,16 @@ export class AutoComplete extends Component { this.state.activeSourceOption = null; } + cancel() { + if (this.inputRef.el.value.length) { + if (this.props.autoSelect) { + this.inputRef.el.value = this.props.value; + this.props.onCancel(); + } + } + this.close(); + } + async loadSources(useInput) { this.sources = []; this.state.activeSourceOption = null; @@ -237,21 +247,10 @@ export class AutoComplete extends Component { this.ignoreBlur = false; return; } - const value = this.inputRef.el.value; - if ( - this.props.autoSelect && - this.state.activeSourceOption && - value.length > 0 && - value !== this.props.value - ) { - this.selectOption(this.state.activeSourceOption, { triggeredOnBlur: true }); - } else { - this.props.onBlur({ - inputValue: value, - }); - this.inEdition = false; - this.close(); - } + this.props.onBlur({ + inputValue: this.inputRef.el.value, + }); + this.inEdition = false; } onInputClick() { if (!this.isOpened) { @@ -325,7 +324,7 @@ export class AutoComplete extends Component { if (!this.isOpened) { return; } - this.close(); + this.cancel(); break; case "tab": if (!this.isOpened) { @@ -373,7 +372,7 @@ export class AutoComplete extends Component { externalClose(ev) { if (this.isOpened && !this.root.el.contains(ev.target)) { - this.close(); + this.cancel(); } } } @@ -397,6 +396,7 @@ Object.assign(AutoComplete, { placeholder: { type: String, optional: true }, autoSelect: { type: Boolean, optional: true }, resetOnSelect: { type: Boolean, optional: true }, + onCancel: { type: Function, optional: true }, onInput: { type: Function, optional: true }, onChange: { type: Function, optional: true }, onBlur: { type: Function, optional: true }, @@ -411,6 +411,7 @@ Object.assign(AutoComplete, { placeholder: "", autoSelect: false, dropdown: true, + onCancel: () => {}, onInput: () => {}, onChange: () => {}, onBlur: () => {}, diff --git a/addons/web/static/src/views/fields/many2one/many2one_field.js b/addons/web/static/src/views/fields/many2one/many2one_field.js index 97f4609d252..296245967d0 100644 --- a/addons/web/static/src/views/fields/many2one/many2one_field.js +++ b/addons/web/static/src/views/fields/many2one/many2one_field.js @@ -134,10 +134,7 @@ export class Many2OneField extends Component { }; if (this.props.canQuickCreate) { - this.quickCreate = (name, params = {}) => { - if (params.triggeredOnBlur) { - return this.openConfirmationDialog(name); - } + this.quickCreate = (name) => { this.state.isFloating = false; return this.updateRecord([false, name]); }; @@ -368,7 +365,7 @@ export const many2OneField = { extractProps({ attrs, context, decorations, options, string }, dynamicInfo) { const hasCreatePermission = attrs.can_create ? evaluateBooleanExpr(attrs.can_create) : true; const hasWritePermission = attrs.can_write ? evaluateBooleanExpr(attrs.can_write) : true; - const canCreate = options.no_create ? false : hasCreatePermission + const canCreate = options.no_create ? false : hasCreatePermission; return { placeholder: attrs.placeholder, canOpen: !options.no_open, diff --git a/addons/web/static/src/views/fields/properties/property_value.js b/addons/web/static/src/views/fields/properties/property_value.js index 87e7e41007b..67bbb6cfaf4 100644 --- a/addons/web/static/src/views/fields/properties/property_value.js +++ b/addons/web/static/src/views/fields/properties/property_value.js @@ -293,10 +293,6 @@ export class PropertyValue extends Component { * @param {object} params */ async onQuickCreate(name, params = {}) { - if (params.triggeredOnBlur) { - this.onValueChange(false); - return; - } const result = await this.orm.call(this.props.comodel, "name_create", [name], { context: this.props.context, }); diff --git a/addons/web/static/src/views/fields/relational_utils.js b/addons/web/static/src/views/fields/relational_utils.js index 09f20f90acc..d0994f3f68d 100644 --- a/addons/web/static/src/views/fields/relational_utils.js +++ b/addons/web/static/src/views/fields/relational_utils.js @@ -253,6 +253,9 @@ export class Many2XAutocomplete extends Component { this.props.setInputFloats(true); } } + onCancel() { + this.props.setInputFloats(false); + } onSelect(option, params = {}) { if (option.action) { diff --git a/addons/web/static/src/views/fields/relational_utils.xml b/addons/web/static/src/views/fields/relational_utils.xml index 8a20593f518..224868015dc 100644 --- a/addons/web/static/src/views/fields/relational_utils.xml +++ b/addons/web/static/src/views/fields/relational_utils.xml @@ -59,6 +59,7 @@ dropdown="props.dropdown" autofocus="props.autofocus" resetOnSelect="props.value === ''" + onCancel.bind="onCancel" /> diff --git a/addons/web/static/tests/core/autocomplete_tests.js b/addons/web/static/tests/core/autocomplete_tests.js index 896130bc0c0..6802c900341 100644 --- a/addons/web/static/tests/core/autocomplete_tests.js +++ b/addons/web/static/tests/core/autocomplete_tests.js @@ -164,7 +164,7 @@ QUnit.module("Components", (hooks) => { assert.containsOnce(target, ".o-autocomplete--dropdown-menu"); }); - QUnit.test("close dropdown on escape keydown", async (assert) => { + QUnit.test("cancel result on escape keydown", async (assert) => { class Parent extends Component {} Parent.components = { AutoComplete }; Parent.template = xml` @@ -172,17 +172,21 @@ QUnit.module("Components", (hooks) => { value="'Hello'" sources="[{ options: [{ label: 'World' }, { label: 'Hello' }] }]" onSelect="() => {}" + autoSelect="true" /> `; await mount(Parent, target, { env }); assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, "Hello"); await triggerEvents(target, ".o-autocomplete--input", ["focus", "click"]); assert.containsOnce(target, ".o-autocomplete--dropdown-menu"); + await editInput(target, ".o-autocomplete--input", "H"); await triggerEvent(target, ".o-autocomplete--input", "keydown", { key: "Escape" }); assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, "Hello"); }); QUnit.test("select input text on first focus", async (assert) => { @@ -198,7 +202,7 @@ QUnit.module("Components", (hooks) => { assert.strictEqual(el.value.substring(el.selectionStart, el.selectionEnd), "Bar"); }); - QUnit.test("scroll outside should close dropdown", async (assert) => { + QUnit.test("scroll outside should cancel result", async (assert) => { class Parent extends Component {} Parent.components = { AutoComplete }; Parent.template = xml` @@ -206,17 +210,21 @@ QUnit.module("Components", (hooks) => { value="'Hello'" sources="[{ options: [{ label: 'World' }, { label: 'Hello' }] }]" onSelect="() => {}" + autoSelect="true" /> `; await mount(Parent, target, { env }); assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, "Hello"); await click(target, ".o-autocomplete--input"); assert.containsOnce(target, ".o-autocomplete--dropdown-menu"); + await editInput(target, ".o-autocomplete--input", "H"); await triggerEvent(target, null, "scroll"); assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, "Hello"); }); QUnit.test("scroll inside should keep dropdown open", async (assert) => { @@ -240,7 +248,33 @@ QUnit.module("Components", (hooks) => { assert.containsOnce(target, ".o-autocomplete--dropdown-menu"); }); - QUnit.test("losing focus should close dropdown", async (assert) => { + QUnit.test("losing focus should cancel result", async (assert) => { + class Parent extends Component {} + Parent.components = { AutoComplete }; + Parent.template = xml` + + `; + + await mount(Parent, target, { env }); + assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, "Hello"); + + await triggerEvents(target, ".o-autocomplete--input", ["focus", "click"]); + assert.containsOnce(target, ".o-autocomplete--dropdown-menu"); + await editInput(target, ".o-autocomplete--input", "H"); + + await triggerEvent(target, "", "pointerdown"); + await triggerEvent(target, ".o-autocomplete--input", "blur"); + assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, "Hello"); + }); + + QUnit.test("click out after clearing input", async (assert) => { class Parent extends Component {} Parent.components = { AutoComplete }; Parent.template = xml` @@ -253,12 +287,16 @@ QUnit.module("Components", (hooks) => { await mount(Parent, target, { env }); assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, "Hello"); await triggerEvents(target, ".o-autocomplete--input", ["focus", "click"]); assert.containsOnce(target, ".o-autocomplete--dropdown-menu"); + await editInput(target, ".o-autocomplete--input", ""); + await triggerEvent(target, "", "pointerdown"); await triggerEvent(target, ".o-autocomplete--input", "blur"); assert.containsNone(target, ".o-autocomplete--dropdown-menu"); + assert.strictEqual(target.querySelector(".o-autocomplete--input").value, ""); }); QUnit.test("open twice should not display previous results", async (assert) => { @@ -495,10 +533,9 @@ QUnit.module("Components", (hooks) => { onChange() { assert.step("change"); } - onSelect(option, params) { + onSelect(option) { target.querySelector(".o-autocomplete--input").value = option.label; assert.step("select " + option.label); - assert.notOk(params.triggeredOnBlur); } onBlur() { assert.step("blur"); diff --git a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js index b98618aedeb..cfd0d900f23 100644 --- a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js @@ -1014,7 +1014,8 @@ QUnit.module("Fields", (hooks) => { await triggerEvent(input, null, "focus"); await click(input); await editInput(input, null, "go"); - await triggerEvent(input, null, "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsNone(document.body, ".modal"); assert.containsOnce(target, ".o_field_many2many_tags .badge"); @@ -1028,7 +1029,8 @@ QUnit.module("Fields", (hooks) => { await click(input); await editInput(input, null, "r"); await triggerEvent(input, null, "keydown", { key: "ArrowDown" }); - await triggerEvent(input, null, "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.strictEqual( target.querySelectorAll(".o_field_many2many_tags .badge")[1].textContent, "red", diff --git a/addons/web/static/tests/views/fields/many2one_field_tests.js b/addons/web/static/tests/views/fields/many2one_field_tests.js index 74e3c98cfef..2271a69564c 100644 --- a/addons/web/static/tests/views/fields/many2one_field_tests.js +++ b/addons/web/static/tests/views/fields/many2one_field_tests.js @@ -2950,7 +2950,7 @@ QUnit.module("Fields", (hooks) => { ); QUnit.test("quick create on a many2one", async function (assert) { - assert.expect(2); + assert.expect(1); await makeView({ type: "form", @@ -2971,13 +2971,7 @@ QUnit.module("Fields", (hooks) => { await triggerEvent(target, ".o_field_many2one input", "focus"); await editInput(target, ".o_field_many2one input", "new partner"); - await triggerEvent(target, ".o_field_many2one input", "blur"); - - assert.strictEqual( - target.querySelector(".modal .modal-body").textContent.trim(), - "Create new partner as a new Product?" - ); - await click(target, ".modal .modal-footer .btn-primary"); + await triggerHotkey("tab"); }); QUnit.test( @@ -3117,7 +3111,8 @@ QUnit.module("Fields", (hooks) => { // cancel the many2one creation with Discard button await editInput(target, ".o_field_many2one input", "new product"); - await triggerEvent(target, ".o_field_many2one input", "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsOnce(target, ".modal", "there should be one opened modal"); @@ -3131,7 +3126,8 @@ QUnit.module("Fields", (hooks) => { // cancel the many2one creation with Close button await editInput(target, ".o_field_many2one input", "new product"); - await triggerEvent(target, ".o_field_many2one input", "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsOnce(target, ".modal", "there should be one opened modal"); await click(target, ".modal .modal-header button"); @@ -3153,7 +3149,8 @@ QUnit.module("Fields", (hooks) => { ); await editInput(target, ".o_field_many2one input", "new product"); - await triggerEvent(target, ".o_field_many2one input", "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsOnce(target, ".modal", "there should be one opened modal"); await click(target, ".modal .modal-footer .btn:not(.btn-primary)"); @@ -3165,7 +3162,8 @@ QUnit.module("Fields", (hooks) => { // confirm the many2one creation await editInput(target, ".o_field_many2one input", "new product"); - await triggerEvent(target, ".o_field_many2one input", "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsOnce( target, @@ -3185,7 +3183,8 @@ QUnit.module("Fields", (hooks) => { }); await editInput(target, ".o_field_many2one input", "xph"); - await triggerEvent(target, ".o_field_many2one input", "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsNone(target, ".modal"); assert.strictEqual(target.querySelector(".o_field_many2one input").value, "xphone"); @@ -3206,7 +3205,8 @@ QUnit.module("Fields", (hooks) => { }); await editInput(target, ".o_field_many2one input", "new partner"); - await triggerEvent(target, ".o_field_many2one input", "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsNone(target, ".modal", "should not display the create modal"); assert.strictEqual( @@ -3230,7 +3230,8 @@ QUnit.module("Fields", (hooks) => { `, }); await editInput(target, ".o_field_many2one input", "new partner"); - await triggerEvent(target, ".o_field_many2one input", "blur"); + await triggerHotkey("tab"); + await nextTick(); assert.containsNone(target, ".modal", "should not display the create modal"); assert.strictEqual( @@ -3240,30 +3241,33 @@ QUnit.module("Fields", (hooks) => { ); }); - QUnit.test("no_quick_create option on a many2one when can_create is absent", async function (assert) { - serverData.models.partner.fields.product_id.readonly = true; - await makeView({ - type: "form", - resModel: "partner", - serverData, - arch: ` + QUnit.test( + "no_quick_create option on a many2one when can_create is absent", + async function (assert) { + serverData.models.partner.fields.product_id.readonly = true; + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: `
`, - }); - await editInput(target, ".o_field_many2one input", "new partner"); - assert.containsOnce( - target, - ".ui-autocomplete .o_m2o_dropdown_option", - "Dropdown should be opened and have only one item" - ); - assert.hasClass( - target.querySelector(".ui-autocomplete .o_m2o_dropdown_option"), - "o_m2o_dropdown_option_create_edit" - ); - }); + }); + await editInput(target, ".o_field_many2one input", "new partner"); + assert.containsOnce( + target, + ".ui-autocomplete .o_m2o_dropdown_option", + "Dropdown should be opened and have only one item" + ); + assert.hasClass( + target.querySelector(".ui-autocomplete .o_m2o_dropdown_option"), + "o_m2o_dropdown_option_create_edit" + ); + } + ); QUnit.test("can_create and can_write option on a many2one", async function (assert) { serverData.models.product.options = { @@ -3451,12 +3455,8 @@ QUnit.module("Fields", (hooks) => { "there should be option for 'No records'" ); - await triggerEvent(target, ".o_field_many2one[name=product_id] input", "blur"); - assert.containsNone( - target, - ".o_field_many2one[name=product_id] .o_m2o_no_result", - "there should be option for 'No records'" - ); + await triggerEvent(target, "", "pointerdown"); + assert.containsNone(target, ".o_field_many2one[name=product_id] .o_m2o_no_result"); } ); diff --git a/addons/web/static/tests/views/kanban/kanban_view_tests.js b/addons/web/static/tests/views/kanban/kanban_view_tests.js index 61c903b0a58..e7fcce6e4f8 100644 --- a/addons/web/static/tests/views/kanban/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban/kanban_view_tests.js @@ -3571,6 +3571,7 @@ QUnit.module("Views", (hooks) => { QUnit.test("quick create record: cancel when modal is opened", async (assert) => { serverData.views["partner,some_view_ref,form"] = '
'; + serverData.views["product,false,form"] = '
'; // patch setTimeout s.t. the autocomplete dropdown opens directly patchWithCleanup(browser, { @@ -3596,7 +3597,7 @@ QUnit.module("Views", (hooks) => { await editInput(target, ".o_kanban_quick_create input", "test"); await triggerEvent(target, ".o_kanban_quick_create input", "input"); - await triggerEvent(target, ".o_kanban_quick_create input", "blur"); + await click(target, ".o_m2o_dropdown_option_create_edit"); // When focusing out of the many2one, a modal to add a 'product' will appear. // The following assertions ensures that a click on the body element that has 'modal-open' diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js index 9aa5e190811..314215cfdd8 100644 --- a/addons/web/static/tests/views/list_view_tests.js +++ b/addons/web/static/tests/views/list_view_tests.js @@ -13115,10 +13115,9 @@ QUnit.module("Views", (hooks) => { await editInput(target, ".o_field_many2one input", "abcdef"); await nextTick(); - // simulate focus out - await triggerEvent(target, ".o_field_many2one input", "blur"); + await click(target, ".o_m2o_dropdown_option_create_edit"); - assert.containsOnce(target, ".modal", "should ask confirmation to create a record"); + assert.containsOnce(target, ".modal", "should show dialog to create the record"); assert.containsOnce(target, ".o_data_row", "the row should still be there"); } ); @@ -16809,13 +16808,9 @@ QUnit.module("Views", (hooks) => { const input = target.querySelector(".o_data_row .o_data_cell input"); await editInput(input, null, "aaa"); - await triggerEvents(input, null, ["keyup", "blur"]); - document.body.click(); + await triggerEvents(input, null, ["keyup"]); + await triggerHotkey("tab"); await nextTick(); - assert.containsOnce(target, ".modal", "the quick_create modal should appear"); - - await click(target.querySelector(".modal .btn-primary")); - await click(target.querySelector(".o_list_view")); assert.strictEqual( target.getElementsByClassName("o_data_cell")[0].innerHTML, "aaa",