From e3263ce3791928553780cc78eeb08b2c50f99a1f Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Sat, 20 Sep 2025 15:03:44 +0300 Subject: [PATCH] fix: Make ComboBoxTester.selectItem respect native filtering (#2003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The selectItem method now applies the filter text before searching for items, ensuring it respects the ComboBox's custom filtering logic set via setItems(ItemFilter, Collection). This matches the behavior a user would experience when typing in the ComboBox. - Applied filter before selection in ComboBoxTester.selectItem - Applied same fix to MultiSelectComboBoxTester.selectItem - Clear filter after selection to restore original state - Added tests to verify the fix works with custom filtering Fixes #2003 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../ComboBoxCustomFilteringTesterTest.java | 91 +++++++++++++++++++ .../combobox/ComboBoxCustomFilteringView.java | 71 +++++++++++++++ .../component/combobox/ComboBoxTester.java | 6 ++ .../combobox/MultiSelectComboBoxTester.java | 29 ++++-- 4 files changed, 188 insertions(+), 9 deletions(-) create mode 100644 vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringTesterTest.java create mode 100644 vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringView.java diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringTesterTest.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringTesterTest.java new file mode 100644 index 000000000..9f71a1c21 --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringTesterTest.java @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2000-2025 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.combobox; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.vaadin.flow.router.RouteConfiguration; +import com.vaadin.testbench.unit.UIUnitTest; +import com.vaadin.testbench.unit.ViewPackages; + +@ViewPackages +public class ComboBoxCustomFilteringTesterTest extends UIUnitTest { + + ComboBoxCustomFilteringView view; + + @BeforeEach + void init() { + RouteConfiguration.forApplicationScope() + .setAnnotatedRoute(ComboBoxCustomFilteringView.class); + view = navigate(ComboBoxCustomFilteringView.class); + } + + @Test + void selectItem_withCustomFiltering_respectsNativeFilter() { + // Test that selectItem respects the custom filtering + // "John" should be selectable as it matches the first name + test(view.combo).selectItem("John"); + Assertions.assertEquals(view.items.get(0), + test(view.combo).getSelected()); + + // Clear selection + test(view.combo).selectItem(null); + Assertions.assertNull(test(view.combo).getSelected()); + + // "Jane" should also be selectable + test(view.combo).selectItem("Jane"); + Assertions.assertEquals(view.items.get(1), + test(view.combo).getSelected()); + } + + @Test + void setFilter_withCustomFiltering_filtersCorrectly() { + // Test filtering by last name (which is part of custom filter logic) + test(view.combo).setFilter("Smith"); + List suggestions = test(view.combo).getSuggestions(); + + // Should find John Smith even though we're filtering by last name + Assertions.assertEquals(1, suggestions.size()); + Assertions.assertEquals("John", suggestions.get(0)); + + // Test filtering by "son" which should match "Johnson" + test(view.combo).setFilter("son"); + suggestions = test(view.combo).getSuggestions(); + Assertions.assertEquals(1, suggestions.size()); + Assertions.assertEquals("Bob", suggestions.get(0)); + + // Test filtering by "o" which should match John, Doe (Jane), Johnson + // (Bob) + test(view.combo).setFilter("o"); + suggestions = test(view.combo).getSuggestions(); + Assertions.assertEquals(3, suggestions.size()); + } + + @Test + void selectItem_afterManualFilter_selectsCorrectly() { + // Manually set a filter + test(view.combo).setFilter("Jane"); + List suggestions = test(view.combo).getSuggestions(); + Assertions.assertEquals(1, suggestions.size()); + + // Clear the filter + test(view.combo).setFilter(""); + + // Select an item using selectItem + test(view.combo).selectItem("Jane"); + + // Verify the item was selected correctly + Assertions.assertEquals(view.items.get(1), + test(view.combo).getSelected(), "Jane should be selected"); + } +} \ No newline at end of file diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringView.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringView.java new file mode 100644 index 000000000..cf0a2c76c --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/combobox/ComboBoxCustomFilteringView.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2000-2025 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.combobox; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasComponents; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.router.Route; + +@Tag("div") +@Route(value = "combo-custom-filter", registerAtStartup = false) +public class ComboBoxCustomFilteringView extends Component + implements HasComponents { + + ComboBox combo; + List items = Arrays.asList(new Person("John", "Smith"), + new Person("Jane", "Doe"), new Person("Bob", "Johnson")); + + public ComboBoxCustomFilteringView() { + combo = new ComboBox<>("Person Selector"); + + // Custom filter that matches both first and last name + combo.setItems((person, filterText) -> { + if (filterText == null || filterText.isEmpty()) { + return true; + } + String lowerFilter = filterText.toLowerCase(); + // Custom logic: match if filter text is found in either first or + // last name + return person.getFirstName().toLowerCase().contains(lowerFilter) + || person.getLastName().toLowerCase().contains(lowerFilter); + }, items); + + // Display only the first name in the dropdown + combo.setItemLabelGenerator(person -> person.getFirstName()); + + add(combo); + } + + public static class Person { + private String firstName; + private String lastName; + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + @Override + public String toString() { + return firstName + " " + lastName; + } + } +} \ No newline at end of file diff --git a/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/ComboBoxTester.java b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/ComboBoxTester.java index 2f61cf7f0..56c7c342f 100644 --- a/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/ComboBoxTester.java +++ b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/ComboBoxTester.java @@ -73,6 +73,9 @@ public void selectItem(String selection) { setValueAsUser(null); return; } + // Apply the filter to respect native filtering behavior + setFilter(selection); + final List suggestionItems = getSuggestionItems(); final ItemLabelGenerator itemLabelGenerator = getComponent() .getItemLabelGenerator(); @@ -84,6 +87,9 @@ public void selectItem(String selection) { "No item found for '" + selection + "'"); } setValueAsUser(filtered.get(0)); + + // Clear the filter after selection + setFilter(""); } /** diff --git a/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/MultiSelectComboBoxTester.java b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/MultiSelectComboBoxTester.java index 59e979141..79780d506 100644 --- a/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/MultiSelectComboBoxTester.java +++ b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/combobox/MultiSelectComboBoxTester.java @@ -76,17 +76,28 @@ public void selectItem(String... selection) { return; } List toBeSelected = Arrays.asList(selection); - final List suggestionItems = getSuggestionItems(); - final ItemLabelGenerator itemLabelGenerator = getComponent() - .getItemLabelGenerator(); - final List filtered = suggestionItems.stream().filter( - item -> toBeSelected.contains(itemLabelGenerator.apply(item))) - .collect(Collectors.toList()); - if (filtered.size() < 1) { + + // Apply filter for each selection to respect native filtering behavior + // We need to collect all matching items from potentially different + // filter results + List allMatchingItems = toBeSelected.stream().flatMap(sel -> { + setFilter(sel); + final List suggestionItems = getSuggestionItems(); + final ItemLabelGenerator itemLabelGenerator = getComponent() + .getItemLabelGenerator(); + return suggestionItems.stream() + .filter(item -> sel.equals(itemLabelGenerator.apply(item))); + }).distinct().collect(Collectors.toList()); + + if (allMatchingItems.size() < 1) { throw new IllegalArgumentException( - "No item found for '" + selection + "'"); + "No item found for '" + Arrays.toString(selection) + "'"); } - getComponent().setValue(filtered); + + // Clear the filter after selection + setFilter(""); + + getComponent().setValue(allMatchingItems); } /**