diff --git a/packages/main/cypress/specs/ToolbarSelect.cy.tsx b/packages/main/cypress/specs/ToolbarSelect.cy.tsx index 1c07e3644ea1..8ecb2b592624 100644 --- a/packages/main/cypress/specs/ToolbarSelect.cy.tsx +++ b/packages/main/cypress/specs/ToolbarSelect.cy.tsx @@ -369,7 +369,7 @@ describe("Toolbar general interaction", () => { }); cy.get("@toolbarSelect").then($select => { - $select.get(0).addEventListener("ui5-change", (e) => { + $select.get(0).addEventListener("ui5-change", (_: Event) => { const input = document.querySelector("[data-testid='selectResult']") as HTMLInputElement; input.value = "1"; }); @@ -400,7 +400,6 @@ describe("Toolbar general interaction", () => { describe("value and label properties", () => { it("Should verify the initial value of the ToolbarSelect", () => { - // Mount the Toolbar with a ToolbarSelect component cy.mount( @@ -412,13 +411,16 @@ describe("Toolbar general interaction", () => { ); - // Verify the initial value of the ToolbarSelect - cy.get("ui5-select", { includeShadowDom: true }) - .should("have.attr", "value", "Option 2"); + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(1) + .should("have.attr", "selected"); }); it("Should verify the label slot content", () => { - // Mount the Toolbar with a ToolbarSelect component cy.mount( @@ -430,14 +432,12 @@ describe("Toolbar general interaction", () => { ); - // Verify the label slot content - cy.get("ui5-toolbar-select") + cy.get("[ui5-toolbar-select]") .find("span[slot='label']") .should("contain.text", "Select an Option:"); }); it("Should change the value and update the selection", () => { - // Mount the Toolbar with a ToolbarSelect component cy.mount( @@ -448,20 +448,25 @@ describe("Toolbar general interaction", () => { ); - // Change the value of the ToolbarSelect - cy.get("ui5-select", { includeShadowDom: true }) + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") .realClick() - .find("ui5-option") + .find("[ui5-option]") .contains("Option 3") .realClick(); - // Verify the updated value of the ToolbarSelect - cy.get("ui5-select", { includeShadowDom: true }) - .should("have.attr", "value", "Option 3"); + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(2) + .should("have.attr", "selected"); }); it("Should handle a value with no corresponding option", () => { - // Mount the Toolbar with a ToolbarSelect component cy.mount( @@ -472,13 +477,12 @@ describe("Toolbar general interaction", () => { ); - // Verify that no option is selected when the value does not match any option - cy.get("ui5-select", { includeShadowDom: true }) - .should("have.attr", "value", "NonExistentOption"); + cy.get("[ui5-toolbar-select-option]").eq(0).should("not.have.attr", "selected"); + cy.get("[ui5-toolbar-select-option]").eq(1).should("not.have.attr", "selected"); + cy.get("[ui5-toolbar-select-option]").eq(2).should("not.have.attr", "selected"); }); it("Should update the value programmatically and reflect the selection", () => { - // Mount the Toolbar with a ToolbarSelect component cy.mount( @@ -489,12 +493,15 @@ describe("Toolbar general interaction", () => { ); - // Update the value programmatically - cy.get("ui5-toolbar-select").invoke("attr", "value", "Option 3"); + cy.get("[ui5-toolbar-select]").invoke("attr", "value", "Option 3"); - // Verify the updated value and selection - cy.get("ui5-select", { includeShadowDom: true }) - .should("have.attr", "value", "Option 3"); + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(2) + .should("have.attr", "selected"); }); }); @@ -518,7 +525,7 @@ describe("Toolbar general interaction", () => { }); // Verify the toolbar-select is rendered inside the popover - cy.get("ui5-toolbar-select").should("be.visible"); + cy.get("[ui5-toolbar-select]").should("be.visible"); }); it("Should update selection when option's selected property is changed programmatically", () => { @@ -539,8 +546,8 @@ describe("Toolbar general interaction", () => { // Attach click handler once the button is in the DOM cy.get("#btn").then($btn => { $btn.get(0).addEventListener("click", () => { - const select = document.querySelector("ui5-toolbar-select"); - const options = select?.querySelectorAll("ui5-toolbar-select-option"); + const select = document.querySelector("[ui5-toolbar-select]"); + const options = select?.querySelectorAll("[ui5-toolbar-select-option]"); options?.forEach(opt => { (opt as ToolbarSelectOption).selected = false; }); @@ -565,11 +572,16 @@ describe("Toolbar general interaction", () => { }); // The rendered select reflects option 2 as the selected value - cy.get("ui5-select", { includeShadowDom: true }) - .should("have.attr", "value", "2"); + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(1) + .should("have.attr", "selected"); }); - it("Should ensure only one option is selected at any time", () => { + it("Should display the last selected option when multiple options are set selected", () => { cy.mount( <> @@ -583,7 +595,6 @@ describe("Toolbar general interaction", () => { ); - // Set up button to attempt selecting multiple options cy.document().then(doc => { const btn = doc.getElementById("selectMultiple"); btn?.addEventListener("click", () => { @@ -591,20 +602,112 @@ describe("Toolbar general interaction", () => { const opt2 = doc.getElementById("opt2") as ToolbarSelectOption; const opt3 = doc.getElementById("opt3") as ToolbarSelectOption; - // Try to select multiple options opt1.selected = true; opt2.selected = true; - opt3.selected = true; // This should be the final selection + opt3.selected = true; }); }); - // Click button to attempt multiple selections cy.get("#selectMultiple").realClick(); - // Verify only the last option (opt3) is selected - cy.get("[ui5-toolbar-select-option]").eq(2).should("have.attr", "selected"); + // Inner display shows the last selected option (opt3) + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(2) + .should("have.attr", "selected"); + }); + + it("Should clear outer selection when value is set to empty string after render", () => { + cy.mount( + + + Option 1 + Option 2 + Option 3 + + + ); + + cy.document().then(doc => { + const select = doc.querySelector("[ui5-toolbar-select]") as ToolbarSelect; + select.value = ""; + }); + cy.get("[ui5-toolbar-select-option]").eq(0).should("not.have.attr", "selected"); cy.get("[ui5-toolbar-select-option]").eq(1).should("not.have.attr", "selected"); - cy.get("ui5-select", { includeShadowDom: true }).should("have.attr", "value", "3"); + cy.get("[ui5-toolbar-select-option]").eq(2).should("not.have.attr", "selected"); + }); + + it("Should not let stale _pendingValue override a later programmatic selected change", () => { + cy.mount( + <> + + + Option 1 + Option 2 + Option 3 + + + + + ); + + cy.get("#prog-btn").then($btn => { + $btn.get(0).addEventListener("click", () => { + const options = document.querySelectorAll("[ui5-toolbar-select-option]"); + options.forEach(opt => { (opt as ToolbarSelectOption).selected = false; }); + (document.getElementById("prog-opt3") as ToolbarSelectOption).selected = true; + }); + }); + + cy.get("#prog-btn").realClick(); + + cy.get("[ui5-toolbar-select-option]").eq(2).should("have.attr", "selected"); + cy.get("[ui5-toolbar-select-option]").eq(0).should("not.have.attr", "selected"); + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(2) + .should("have.attr", "selected"); + }); + + it("Should not let _pendingValue set by value='X' override a later option.selected change before pending is cleared", () => { + cy.mount( + <> + + + Option 1 + Option 2 + Option 3 + + + + + ); + + cy.get("#pending-btn").then($btn => { + $btn.get(0).addEventListener("click", () => { + const options = document.querySelectorAll("[ui5-toolbar-select-option]"); + options.forEach(opt => { (opt as ToolbarSelectOption).selected = false; }); + (document.getElementById("pending-opt2") as ToolbarSelectOption).selected = true; + }); + }); + + cy.get("#pending-btn").realClick(); + + cy.get("[ui5-toolbar-select-option]").eq(1).should("have.attr", "selected"); + cy.get("[ui5-toolbar-select-option]").eq(0).should("not.have.attr", "selected"); + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(1) + .should("have.attr", "selected"); }); }); \ No newline at end of file diff --git a/packages/main/src/ToolbarSelect.ts b/packages/main/src/ToolbarSelect.ts index 74fde918b1ad..324ad71fdcf9 100644 --- a/packages/main/src/ToolbarSelect.ts +++ b/packages/main/src/ToolbarSelect.ts @@ -154,22 +154,36 @@ class ToolbarSelect extends ToolbarItemBase { */ @property() set value(newValue: string) { - if (this.select && this.select.value !== newValue) { - this.select.value = newValue; + if (this.options.length) { + // Options are available: resolve immediately by setting selected on the matching outer option. + // Empty string clears all selections. + this.options.forEach(option => { + option.selected = newValue !== "" && (option.value === newValue || option.textContent?.trim() === newValue); + }); + this._pendingValue = ""; + this._hasPendingValue = false; + } else { + // Options not yet available (pre-render): stage for onBeforeRendering to resolve. + this._pendingValue = newValue; + this._hasPendingValue = true; } - this._value = newValue; } get value(): string | undefined { - return this.select ? this.select.value : this._value; + const selectedOption = this.options.find(o => o.selected); + return selectedOption?.value || selectedOption?.textContent?.trim() || ""; } get select(): Select | null { return this.shadowRoot!.querySelector (