Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions packages/main/cypress/specs/ComboBox.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,57 @@ describe("General Interaction", () => {
.should("have.text", "Canada");
});

it("should highlight entire value when navigating to item that doesn't start with typed text", () => {
cy.mount(
<ComboBox filter="Contains">
<ComboBoxItem text="Algeria"></ComboBoxItem>
<ComboBoxItem text="Bulgaria"></ComboBoxItem>
<ComboBoxItem text="Canada"></ComboBoxItem>
</ComboBox>
);

cy.get("[ui5-combobox]")
.as("combo")
.shadow()
.find("input")
.as("input");

cy.get("@input").realClick();
cy.get("@input").realType("a");

cy.get("@combo")
.shadow()
.find("ui5-responsive-popover")
.should("have.attr", "open");

cy.get("@input").should("have.value", "Algeria");
cy.get("@input").then(($input) => {
const input = $input[0] as HTMLInputElement;

expect(input.selectionStart).to.equal(1);
expect(input.selectionEnd).to.equal("Algeria".length);
});

cy.get("@input").realPress("ArrowDown");

cy.get("@input").should("have.value", "Bulgaria");
cy.get("@input").then(($input) => {
const input = $input[0] as HTMLInputElement;

expect(input.selectionStart).to.equal(0);
expect(input.selectionEnd).to.equal("Bulgaria".length);
});

cy.get("@input").realPress("ArrowDown");

cy.get("@input").should("have.value", "Canada");
cy.get("@input").then(($input) => {
const input = $input[0] as HTMLInputElement;
expect(input.selectionStart).to.equal(0);
expect(input.selectionEnd).to.equal("Canada".length);
});
});

it("tests Combo with startswith filter", () => {
cy.mount(
<ComboBox filter="StartsWith">
Expand Down Expand Up @@ -3336,3 +3387,158 @@ describe("SelectedValue API", () => {
cy.get("[ui5-cb-item]").eq(2).should("have.prop", "selected", false);
});
});

describe("Case-Insensitive Selection", () => {
it("should select item with case-insensitive matching", () => {
cy.mount(
<ComboBox noTypeahead>
<ComboBoxItem text="Argentina"></ComboBoxItem>
<ComboBoxItem text="Bulgaria"></ComboBoxItem>
<ComboBoxItem text="Canada"></ComboBoxItem>
</ComboBox>
);

cy.get("[ui5-combobox]")
.as("combobox")
.shadow()
.find("input")
.as("input");

cy.get("@input").realClick();
cy.get("@input").realType("b");
cy.get("@input").realType("u");
cy.get("@input").realType("l");
cy.get("@input").realType("g");
cy.get("@input").realType("a");
cy.get("@input").realType("r");
cy.get("@input").realType("i");
cy.get("@input").realType("a");

cy.get("@combobox").should("have.prop", "value", "bulgaria");
cy.get("[ui5-cb-item]").eq(1).should("have.prop", "selected", true);
});

it("should select item case-insensitively with Contains filter", () => {
cy.mount(
<ComboBox filter="Contains">
<ComboBoxItem text="United Kingdom"></ComboBoxItem>
<ComboBoxItem text="United States"></ComboBoxItem>
<ComboBoxItem text="United Arab Emirates"></ComboBoxItem>
</ComboBox>
);

cy.get("[ui5-combobox]")
.as("combobox")
.shadow()
.find("input")
.as("input");

cy.get("@input").realClick();
cy.get("@input").realType("u");
cy.get("@input").realType("n");
cy.get("@input").realType("i");
cy.get("@input").realType("t");
cy.get("@input").realType("e");
cy.get("@input").realType("d");
cy.get("@input").realType(" ");
cy.get("@input").realType("s");
cy.get("@input").realType("t");
cy.get("@input").realType("a");
cy.get("@input").realType("t");
cy.get("@input").realType("e");
cy.get("@input").realType("s");

cy.get("@combobox").should("have.prop", "value", "United States");
cy.get("[ui5-cb-item]").eq(1).should("have.prop", "selected", true);
});

it("should select matching item of a group case-insensitively", () => {
cy.mount(
<ComboBox>
<ComboBoxItemGroup headerText="Americas">
<ComboBoxItem text="Argentina"></ComboBoxItem>
<ComboBoxItem text="Brazil"></ComboBoxItem>
</ComboBoxItemGroup>
<ComboBoxItemGroup headerText="Europe">
<ComboBoxItem text="France"></ComboBoxItem>
<ComboBoxItem text="Germany"></ComboBoxItem>
</ComboBoxItemGroup>
</ComboBox>
);

cy.get("[ui5-combobox]")
.as("combobox")
.shadow()
.find("input")
.as("input");

cy.get("@input").realClick();
cy.get("@input").realType("a");
cy.get("@input").realType("r");
cy.get("@input").realType("g");
cy.get("@input").realType("e");
cy.get("@input").realType("n");
cy.get("@input").realType("t");
cy.get("@input").realType("i");
cy.get("@input").realType("n");
cy.get("@input").realType("a");

cy.get("@combobox").should("have.prop", "value", "Argentina");
cy.get("[ui5-cb-item]").eq(0).should("have.prop", "selected", true);
});

it("should select item case-insensitively when using selectedValue", () => {
cy.mount(
<ComboBox>
<ComboBoxItem text="Item 1" value="item-1"></ComboBoxItem>
<ComboBoxItem text="Item 2" value="item-2"></ComboBoxItem>
<ComboBoxItem text="Item 3" value="item-3"></ComboBoxItem>
</ComboBox>
);

cy.get("[ui5-combobox]")
.as("combobox")
.shadow()
.find("input")
.as("input");

cy.get("@input").realClick();
cy.get("@input").realType("i");
cy.get("@input").realType("t");
cy.get("@input").realType("e");
cy.get("@input").realType("m");
cy.get("@input").realType(" ");
cy.get("@input").realType("2");

cy.get("@combobox").should("have.prop", "value", "Item 2");
cy.get("[ui5-cb-item]").eq(1).should("have.prop", "selected", true);
cy.get("@combobox").should("have.prop", "selectedValue", "item-2");
});

it("should handle progressive typing with mixed case", () => {
cy.mount(
<ComboBox>
<ComboBoxItem text="Belgium"></ComboBoxItem>
<ComboBoxItem text="Bulgaria"></ComboBoxItem>
<ComboBoxItem text="Brazil"></ComboBoxItem>
</ComboBox>
);

cy.get("[ui5-combobox]")
.as("combobox")
.shadow()
.find("input")
.as("input");

cy.get("@input").realClick();
cy.get("@input").realType("B");
cy.get("@combobox").should("have.prop", "value", "Belgium");

cy.get("@input").realType("u");
cy.get("@combobox").should("have.prop", "value", "Bulgaria");

cy.get("@input").realType("l");
cy.get("@combobox").should("have.prop", "value", "Bulgaria");
cy.get("[ui5-cb-item]").eq(1).should("have.prop", "selected", true);
});
});
20 changes: 12 additions & 8 deletions packages/main/src/ComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,9 +1229,9 @@ class ComboBox extends UI5Element implements IFormInputElement {
if (matchingItems.length) {
let exactMatch;
if (this._useSelectedValue) {
exactMatch = matchingItems.find(item => item.value === (currentlyFocusedItem?.value || this.selectedValue) && item.text === current);
exactMatch = matchingItems.find(item => item.value === (currentlyFocusedItem?.value || this.selectedValue) && item.text?.toLowerCase() === current.toLowerCase());
} else {
exactMatch = matchingItems.find(item => item.text === current);
exactMatch = matchingItems.find(item => item.text?.toLowerCase() === current.toLowerCase());
}

return exactMatch ?? matchingItems[0];
Expand All @@ -1242,7 +1242,11 @@ class ComboBox extends UI5Element implements IFormInputElement {
const value = (item && item.text) || "";

this.inner.value = value;
this.inner.setSelectionRange(filterValue.length, value.length);

// select the whole value if it doesn't start with the filterValue, otherwise select only the autocompleted part
const startsWithFilter = value.toLowerCase().startsWith(filterValue.toLowerCase());
const selectionStart = startsWithFilter ? filterValue.length : 0;
this.inner.setSelectionRange(selectionStart, value.length);
this.value = value;

if (this._useSelectedValue) {
Expand Down Expand Up @@ -1275,16 +1279,16 @@ class ComboBox extends UI5Element implements IFormInputElement {
if (!shouldSelectionBeCleared && !itemToBeSelected) {
if (isInstanceOfComboBoxItemGroup(item)) {
if (this._useSelectedValue) {
itemToBeSelected = item.items.find(i => i.value === valueToMatch && (this.value === "" || this.value === i.text));
itemToBeSelected = item.items.find(i => i.value === valueToMatch && (this.value === "" || i.text?.toLowerCase() === this.value.toLowerCase()));
} else {
itemToBeSelected = item.items?.find(i => i.text === this.value);
itemToBeSelected = item.items?.find(i => i.text?.toLowerCase() === this.value.toLowerCase());
}
} else {
if (this._useSelectedValue) {
itemToBeSelected = this.items.find(i => i.value === valueToMatch && (this.value === "" || this.value === i.text));
itemToBeSelected = this.items.find(i => i.value === valueToMatch && (this.value === "" || i.text?.toLowerCase() === this.value.toLowerCase()));
return;
}
itemToBeSelected = item.text === this.value ? item : undefined;
itemToBeSelected = item.text?.toLowerCase() === this.value.toLowerCase() ? item : undefined;
}
}
});
Expand Down Expand Up @@ -1405,7 +1409,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
_clear() {
const selectedItem = this.items.find(item => item.selected);

if (selectedItem?.text === this.value) {
if (selectedItem?.text?.toLowerCase() === this.value.toLowerCase()) {
this.fireDecoratorEvent("change");
}

Expand Down
9 changes: 9 additions & 0 deletions packages/main/test/pages/ComboBox.html
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,15 @@ <h3>ComboBox Composition</h3>
});
</script>
</div>
<div class="demo-section">
<span>ComboBox - items with lower case and upper case</span>
<ui5-combobox id="combo-lower-upper-case" no-typeahead>
<ui5-cb-item text="Item 1"></ui5-cb-item>
<ui5-cb-item text="item 11"></ui5-cb-item>
<ui5-cb-item text="Item 2"></ui5-cb-item>
<ui5-cb-item text="item 22"></ui5-cb-item>
</ui5-combobox>
</div>

<script type="module">
document.getElementById("lazy").addEventListener("ui5-input", async event => {
Expand Down
Loading