From 34694c5689eeb3ed28ad39f906616ae5447c6b26 Mon Sep 17 00:00:00 2001 From: Nikolay Deshev Date: Wed, 15 Jul 2026 00:07:42 +0300 Subject: [PATCH 1/2] fix(ui5-combobox): fire 'change' event with normalized values fixes: #13784 --- packages/main/cypress/specs/ComboBox.cy.tsx | 114 ++++++++++++++++++++ packages/main/src/ComboBox.ts | 11 +- packages/main/test/pages/ComboBox.html | 31 ++++++ 3 files changed, 155 insertions(+), 1 deletion(-) diff --git a/packages/main/cypress/specs/ComboBox.cy.tsx b/packages/main/cypress/specs/ComboBox.cy.tsx index 3b241da6809b4..9f0157794ca0a 100644 --- a/packages/main/cypress/specs/ComboBox.cy.tsx +++ b/packages/main/cypress/specs/ComboBox.cy.tsx @@ -4274,3 +4274,117 @@ describe("ComboBoxItemCustom - Accessibility", () => { cy.get("[ui5-cb-item-custom]").shadow().find("li").should("not.have.attr", "tabindex", "0"); }); }); + +describe("Newline normalization in item text", () => { + it("should fire change event twice when selecting two different items with newlines via keyboard", () => { + const changeSpy = cy.stub().as("changeSpy"); + cy.document().then(doc => { + const combobox = doc.createElement("ui5-combobox") as any; + combobox.id = "test-cb-newlines"; + combobox.addEventListener("ui5-change", changeSpy); + + const item1 = doc.createElement("ui5-cb-item"); + item1.setAttribute("text", "Item\nA"); + item1.setAttribute("value", "item-a"); + combobox.appendChild(item1); + + const item2 = doc.createElement("ui5-cb-item"); + item2.setAttribute("text", "Item\nB"); + item2.setAttribute("value", "item-b"); + combobox.appendChild(item2); + + doc.body.appendChild(combobox); + }); + + cy.get("#test-cb-newlines") + .as("combobox"); + + cy.get("@combobox") + .shadow() + .find("[ui5-icon]") + .realClick(); + + cy.get("@combobox") + .shadow() + .find("[ui5-responsive-popover]") + .should("have.attr", "open"); + + cy.get("@combobox").realPress("ArrowDown"); + cy.get("@combobox").realPress("Enter"); + + cy.get("@changeSpy").should("have.been.calledOnce"); + + cy.get("@combobox") + .shadow() + .find("[ui5-icon]") + .realClick(); + + cy.get("@combobox") + .shadow() + .find("[ui5-responsive-popover]") + .should("have.attr", "open"); + + cy.get("@combobox").realPress("ArrowDown"); + cy.get("@combobox").realPress("ArrowDown"); + cy.get("@combobox").realPress("Enter"); + + cy.get("@changeSpy").should("have.been.calledTwice"); + }); + + it("should fire change event when selecting items with identical normalized display text but different values", () => { + const changeSpy = cy.stub().as("changeSpy"); + cy.document().then(doc => { + const combobox = doc.createElement("ui5-combobox") as any; + combobox.id = "test-cb-normalized"; + combobox.addEventListener("ui5-change", changeSpy); + + const item1 = doc.createElement("ui5-cb-item"); + item1.setAttribute("text", "Item\nX"); + item1.setAttribute("value", "first"); + combobox.appendChild(item1); + + const item2 = doc.createElement("ui5-cb-item"); + item2.setAttribute("text", "Item X"); + item2.setAttribute("value", "second"); + combobox.appendChild(item2); + + doc.body.appendChild(combobox); + }); + + cy.get("#test-cb-normalized") + .as("combobox"); + + cy.get("@combobox").find("[ui5-cb-item]").should("have.length", 2); + + cy.get("@combobox") + .shadow() + .find("[ui5-icon]") + .realClick(); + + cy.get("@combobox") + .shadow() + .find("[ui5-responsive-popover]") + .should("have.attr", "open"); + + cy.get("@combobox").realPress("ArrowDown"); + cy.get("@combobox").realPress("Enter"); + + cy.get("@changeSpy").should("have.been.calledOnce"); + + cy.get("@combobox") + .shadow() + .find("[ui5-icon]") + .realClick(); + + cy.get("@combobox") + .shadow() + .find("[ui5-responsive-popover]") + .should("have.attr", "open"); + + cy.get("@combobox").realPress("ArrowDown"); + cy.get("@combobox").realPress("ArrowDown"); + cy.get("@combobox").realPress("Enter"); + + cy.get("@changeSpy").should("have.been.calledTwice"); + }); +}); diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index 4924702d5ac99..af122d5e4feed 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -519,6 +519,7 @@ class ComboBox extends UI5Element implements IFormInputElement { _selectionPerformed = false; _selectionTrigger?: ComboBoxSelectionChangeTrigger; _lastValue: string; + _lastSelectedValue?: string; _selectedItemText = ""; _userTypedValue = ""; _useSelectedValue = false; @@ -650,6 +651,7 @@ class ComboBox extends UI5Element implements IFormInputElement { if (!e.relatedTarget || (e.relatedTarget !== this.shadowRoot!.querySelector(".ui5-input-clear-icon"))) { this._lastValue = this.value; + this._lastSelectedValue = this.selectedValue; } !isPhone() && (e.target as HTMLInputElement).setSelectionRange(0, this.value.length); @@ -713,6 +715,7 @@ class ComboBox extends UI5Element implements IFormInputElement { if (this._selectionPerformed) { this._lastValue = this.value; + this._lastSelectedValue = this.selectedValue; this._selectionPerformed = false; } @@ -782,6 +785,7 @@ class ComboBox extends UI5Element implements IFormInputElement { if (isPhone() && this.value && !this._lastValue) { this._lastValue = this.value; + this._lastSelectedValue = this.selectedValue; } this._toggleRespPopover(); @@ -1400,9 +1404,13 @@ class ComboBox extends UI5Element implements IFormInputElement { } _fireChangeEvent() { - if (this.value !== this._lastValue) { + const valueChanged = this.value !== this._lastValue; + const selectedValueChanged = this._useSelectedValue && this.selectedValue !== this._lastSelectedValue; + + if (valueChanged || selectedValueChanged) { this.fireDecoratorEvent("change"); this._lastValue = this.value; + this._lastSelectedValue = this.selectedValue; } } @@ -1500,6 +1508,7 @@ class ComboBox extends UI5Element implements IFormInputElement { if (this._isPhone) { this._lastValue = ""; + this._lastSelectedValue = undefined; this.fireDecoratorEvent("change"); } else { this.focus(); diff --git a/packages/main/test/pages/ComboBox.html b/packages/main/test/pages/ComboBox.html index 96b7eb59f53e5..dae7010c1efc3 100644 --- a/packages/main/test/pages/ComboBox.html +++ b/packages/main/test/pages/ComboBox.html @@ -890,6 +890,37 @@

ComboBox with Mixed Items

+
+

Newline Normalization Test

+

Items with newlines in text that normalize to same display. Use Arrow keys + Enter to test.

+ +

+
Change events: 0
+
+ +
+ From 981161b7b8b86fcb3b49eba763653c642bd9f6a8 Mon Sep 17 00:00:00 2001 From: Nikolay Deshev Date: Wed, 15 Jul 2026 00:14:47 +0300 Subject: [PATCH 2/2] fix(ui5-combobox): fire 'change' event with normalized values improve the cypress tests --- packages/main/cypress/specs/ComboBox.cy.tsx | 60 +++++++-------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/packages/main/cypress/specs/ComboBox.cy.tsx b/packages/main/cypress/specs/ComboBox.cy.tsx index 9f0157794ca0a..81ebd3acc83b4 100644 --- a/packages/main/cypress/specs/ComboBox.cy.tsx +++ b/packages/main/cypress/specs/ComboBox.cy.tsx @@ -4277,27 +4277,16 @@ describe("ComboBoxItemCustom - Accessibility", () => { describe("Newline normalization in item text", () => { it("should fire change event twice when selecting two different items with newlines via keyboard", () => { - const changeSpy = cy.stub().as("changeSpy"); - cy.document().then(doc => { - const combobox = doc.createElement("ui5-combobox") as any; - combobox.id = "test-cb-newlines"; - combobox.addEventListener("ui5-change", changeSpy); - - const item1 = doc.createElement("ui5-cb-item"); - item1.setAttribute("text", "Item\nA"); - item1.setAttribute("value", "item-a"); - combobox.appendChild(item1); - - const item2 = doc.createElement("ui5-cb-item"); - item2.setAttribute("text", "Item\nB"); - item2.setAttribute("value", "item-b"); - combobox.appendChild(item2); - - doc.body.appendChild(combobox); - }); + cy.mount( + + + + + ); - cy.get("#test-cb-newlines") - .as("combobox"); + cy.get("[ui5-combobox]") + .as("combobox") + .invoke("on", "ui5-change", cy.spy().as("changeSpy")); cy.get("@combobox") .shadow() @@ -4332,29 +4321,18 @@ describe("Newline normalization in item text", () => { }); it("should fire change event when selecting items with identical normalized display text but different values", () => { - const changeSpy = cy.stub().as("changeSpy"); - cy.document().then(doc => { - const combobox = doc.createElement("ui5-combobox") as any; - combobox.id = "test-cb-normalized"; - combobox.addEventListener("ui5-change", changeSpy); - - const item1 = doc.createElement("ui5-cb-item"); - item1.setAttribute("text", "Item\nX"); - item1.setAttribute("value", "first"); - combobox.appendChild(item1); - - const item2 = doc.createElement("ui5-cb-item"); - item2.setAttribute("text", "Item X"); - item2.setAttribute("value", "second"); - combobox.appendChild(item2); - - doc.body.appendChild(combobox); - }); + cy.mount( + + + + + ); - cy.get("#test-cb-normalized") - .as("combobox"); + cy.get("[ui5-combobox]") + .as("combobox") + .invoke("on", "ui5-change", cy.spy().as("changeSpy")); - cy.get("@combobox").find("[ui5-cb-item]").should("have.length", 2); + cy.get("[ui5-cb-item]").should("have.length", 2); cy.get("@combobox") .shadow()