From c7b07f4ec5b33b8fb60496cdcf83a767d44f150d Mon Sep 17 00:00:00 2001 From: Jorge Pinna Puissant Date: Wed, 29 Nov 2023 08:38:21 +0000 Subject: [PATCH] [FIX] web: avoid closing dropdown outside the active element When a dropdown open a dialog and on the dialog there is a new dropdown, before this commit, when clicking on the second dropdown, the first one will close. This is an issue with closing the first dropdown, is if the dialog is opened using useOwnedDialogs, the dialog will also close. Also, if the dialog is open to throw a "find more" of a x2many field, even if the dialog is not close, as the dropdown will close, the original field will also be destroyed and it will raise an error. Now, we avoid closing dropdown's, when opening new dropdowns that there are not on the same active element. opw-3464528 closes odoo/odoo#144238 X-original-commit: 6fec387d258dfd945dbbd48a9aab91ce6113ccaa Signed-off-by: Aaron Bohy (aab) Signed-off-by: Jorge Pinna Puissant (jpp) --- .../web/static/src/core/dropdown/dropdown.js | 6 +- .../web/static/tests/core/dropdown_tests.js | 65 ++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/core/dropdown/dropdown.js b/addons/web/static/src/core/dropdown/dropdown.js index d2ef3a86d4c..753412d31e1 100644 --- a/addons/web/static/src/core/dropdown/dropdown.js +++ b/addons/web/static/src/core/dropdown/dropdown.js @@ -261,7 +261,11 @@ export class Dropdown extends Component { * @param {DropdownStateChangedPayload} args */ onDropdownStateChanged(args) { - if (!this.rootRef.el || this.rootRef.el.contains(args.emitter.rootRef.el)) { + if ( + !this.rootRef.el || + this.rootRef.el.contains(args.emitter.rootRef.el) || + args.emitter.myActiveEl !== this.myActiveEl + ) { // Do not listen to events emitted by self or children return; } diff --git a/addons/web/static/tests/core/dropdown_tests.js b/addons/web/static/tests/core/dropdown_tests.js index 64c7f0cd5b5..3769aec5f36 100644 --- a/addons/web/static/tests/core/dropdown_tests.js +++ b/addons/web/static/tests/core/dropdown_tests.js @@ -11,7 +11,7 @@ import { hotkeyService } from "@web/core/hotkeys/hotkey_service"; import { registry } from "@web/core/registry"; import { uiService } from "@web/core/ui/ui_service"; import { registerCleanup } from "../helpers/cleanup"; -import { makeTestEnv } from "../helpers/mock_env"; +import { clearRegistryWithCleanup, makeTestEnv } from "../helpers/mock_env"; import { makeFakeLocalizationService } from "../helpers/mock_services"; import { click, @@ -28,6 +28,8 @@ import { import { makeParent } from "./tooltip/tooltip_service_tests"; import { getPickerCell } from "./datetime/datetime_test_helpers"; import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; +import { Dialog } from "@web/core/dialog/dialog"; +import { dialogService } from "@web/core/dialog/dialog_service"; const serviceRegistry = registry.category("services"); @@ -1385,4 +1387,65 @@ QUnit.module("Components", ({ beforeEach }) => { ` My checkbox item ` ); }); + + QUnit.test("don't close dropdown outside the active element", async (assert) => { + // This test checks that if a dropdown element opens a dialog with a dropdown inside, + // opening this dropdown will not close the first dropdown. + class CustomDialog extends Component {} + CustomDialog.template = xml` + + + Item + +
Outside Dialog
+
`; + CustomDialog.components = { Dialog, Dropdown, DropdownItem }; + + const mainComponentRegistry = registry.category("main_components"); + clearRegistryWithCleanup(mainComponentRegistry); + serviceRegistry.add("dialog", dialogService); + serviceRegistry.add("l10n", makeFakeLocalizationService()); + + class PseudoWebClient extends Component { + setup() { + this.Components = mainComponentRegistry.getEntries(); + } + clicked() { + env.services.dialog.add(CustomDialog); + } + } + PseudoWebClient.template = xml` +
+
+ + + +
+
+ + + +
Outside Parent
+
+
+ `; + PseudoWebClient.components = { Dropdown }; + + env = await makeTestEnv(); + await mount(PseudoWebClient, target, { env }); + await click(target, "button.dropdown-toggle"); + assert.containsOnce(target, ".dropdown-menu"); + await click(target, "button.click-me"); + assert.containsOnce(target, ".modal-dialog"); + await click(target, ".modal-dialog button.dropdown-toggle"); + assert.containsN(target, ".dropdown-menu", 2); + await click(target, ".outside_dialog"); + assert.containsOnce(target, ".modal-dialog"); + assert.containsN(target, ".dropdown-menu", 1); + await click(target, ".modal-dialog .btn-primary"); + assert.containsNone(target, ".modal-dialog"); + assert.containsN(target, ".dropdown-menu", 1); + await click(target, ".outside_parent"); + assert.containsNone(target, ".dropdown-menu"); + }); });