From 7ef2e53dd3965de782d41a62be97ee5cf3f6ad0e Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 5 Mar 2025 08:56:25 -0700 Subject: [PATCH] Et2TreeDropdown: Fix closing dropdown via expand icon opened it again --- api/js/etemplate/Et2Tree/Et2TreeDropdown.ts | 20 ++-- .../Et2Tree/test/Et2TreeDropdown.test.ts | 93 +++++++++++++++++++ 2 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 api/js/etemplate/Et2Tree/test/Et2TreeDropdown.test.ts diff --git a/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts b/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts index ff38a56d2b..b8a9bff5b7 100644 --- a/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts +++ b/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts @@ -620,7 +620,7 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg this.setCurrentTag(null); // Don't show if only tabbed into - if(!event.relatedTarget) + if(!event.relatedTarget && !this.open) { this.show(); } @@ -629,6 +629,10 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg private handleInternalBlur(event) { // Focus lost to some other internal component - ignore it + if(this.shadowRoot.contains(event.target)) + { + return; + } let o = event.relatedTarget; while(o) { @@ -755,6 +759,7 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg event.stopPropagation(); this.hasFocus = true; + const oldValue = this.open; if(this.open) { this._popup.active = false; @@ -766,12 +771,15 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg } this.open = this._popup.active; this.treeOrSearch = "tree"; - this.requestUpdate("open"); - this.updateComplete.then(() => + this.requestUpdate("open", oldValue); + if(this.open) { - this._tree.style.minWidth = getComputedStyle(this).width; - this.focus(); - }) + this.updateComplete.then(() => + { + this._tree.style.minWidth = getComputedStyle(this).width; + this.focus(); + }); + } } /** diff --git a/api/js/etemplate/Et2Tree/test/Et2TreeDropdown.test.ts b/api/js/etemplate/Et2Tree/test/Et2TreeDropdown.test.ts new file mode 100644 index 0000000000..f44f93296d --- /dev/null +++ b/api/js/etemplate/Et2Tree/test/Et2TreeDropdown.test.ts @@ -0,0 +1,93 @@ +import {Et2TreeDropdown} from "../Et2TreeDropdown"; +import {assert, fixture} from "@open-wc/testing"; +import {html} from "lit"; +import {Et2Tree} from "../Et2Tree"; + +window.egw = { + ajaxUrl: (url) => url, + decodePath: (_path : string) => _path, + image: () => "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDMyIDMyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjNjk2OTY5IiBkPSJNNi45NDMsMjguNDUzDQoJYzAuOTA2LDAuNzY1LDIuMDk3LDEuMTI3LDMuMjg2LDEuMTA5YzAuNDMsMC4wMTQsMC44NTItMC4wNjgsMS4yNjUtMC4yMDdjMC42NzktMC4xOCwxLjMyOC0wLjQ1LDEuODY2LTAuOTAyTDI5LjQwMywxNC45DQoJYzEuNzcyLTEuNDk4LDEuNzcyLTMuOTI1LDAtNS40MjJjLTEuNzcyLTEuNDk3LTQuNjQ2LTEuNDk3LTYuNDE4LDBMMTAuMTE5LDIwLjM0OWwtMi4zODktMi40MjRjLTEuNDQtMS40NTctMy43NzItMS40NTctNS4yMTIsMA0KCWMtMS40MzgsMS40Ni0xLjQzOCwzLjgyNSwwLDUuMjgxQzIuNTE4LDIzLjIwNiw1LjQ3NCwyNi45NDcsNi45NDMsMjguNDUzeiIvPg0KPC9zdmc+DQo=", + preference: i => "", + tooltipUnbind: () => {}, + webserverUrl: "" +}; +describe("Et2TreeDropdown", () => +{ + let element : Et2TreeDropdown; + + beforeEach(async() => + { + element = await fixture(html` + `); + }); + + // Make sure it works + it("is defined", async() => + { + assert.instanceOf(element, Et2TreeDropdown); + + // Tree is also used, so it needs to work too + const tree = await fixture(html` + `); + assert.instanceOf(tree, Et2Tree); + }); + + it("renders correctly", () => + { + assert(element, "Component should be rendered"); + }); + + it("has correct default properties", () => + { + assert.strictEqual(element.open, false, "Default open property should be false"); + assert.strictEqual(element.disabled, false, "Default disabled property should be false"); + }); + + it("closes and stays closed when expand icon is clicked", async() => + { + const expandIcon = element.shadowRoot.querySelector(".tree-dropdown__expand-icon"); + assert(expandIcon, "Expand icon should be present"); + const popup = element.shadowRoot.querySelector("sl-popup"); + assert(popup, "Popup should be present"); + + // Click to expand + expandIcon.click(); + await element.updateComplete; + await new Promise(resolve => setTimeout(resolve, 0)); + assert(element.hasAttribute("open"), "Dropdown should be open after clicking expand"); + assert(popup.hasAttribute("active"), "Popup should be active after clicking expand"); + assert(element.open, "Open property should be true after clicking expand"); + + // Click again to collapse + expandIcon.click(); + await element.updateComplete; + await new Promise(resolve => setTimeout(resolve, 0)); + assert(!element.hasAttribute("open"), "Dropdown should be closed after clicking expand again"); + assert(!element.hasAttribute("active"), "Popup should not be active after clicking expand again"); + assert(!element.open, "Open property should be false after clicking expand again"); + + // Wait a tick to make sure it stays closed + await new Promise(resolve => setTimeout(resolve, 10)); + await element.updateComplete; + assert(!element.hasAttribute("open"), "Dropdown should stay closed after closed"); + assert(!element.hasAttribute("active"), "Popup should not be active after closed"); + + }); + + + it("reflects disabled changes correctly", async() => + { + element.setAttribute("disabled", ""); + await element.updateComplete; + assert(element.hasAttribute("disabled"), "Component should reflect disabled attribute"); + }); + + it("closes when clicking outside", async() => + { + element.setAttribute("open", ""); + await element.updateComplete; + document.body.click(); + await element.updateComplete; + assert(!element.hasAttribute("open"), "Dropdown should close when clicking outside"); + }); +}); \ No newline at end of file