Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Copyright (C) 2000-2025 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See <https://vaadin.com/commercial-license-and-service-terms> 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<String> 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<String> 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");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright (C) 2000-2025 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See <https://vaadin.com/commercial-license-and-service-terms> 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<Person> combo;
List<Person> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public void selectItem(String selection) {
setValueAsUser(null);
return;
}
// Apply the filter to respect native filtering behavior
setFilter(selection);

final List<Y> suggestionItems = getSuggestionItems();
final ItemLabelGenerator<Y> itemLabelGenerator = getComponent()
.getItemLabelGenerator();
Expand All @@ -84,6 +87,9 @@ public void selectItem(String selection) {
"No item found for '" + selection + "'");
}
setValueAsUser(filtered.get(0));

// Clear the filter after selection
setFilter("");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,28 @@ public void selectItem(String... selection) {
return;
}
List<String> toBeSelected = Arrays.asList(selection);
final List<Y> suggestionItems = getSuggestionItems();
final ItemLabelGenerator<Y> itemLabelGenerator = getComponent()
.getItemLabelGenerator();
final List<Y> 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<Y> allMatchingItems = toBeSelected.stream().flatMap(sel -> {
setFilter(sel);
final List<Y> suggestionItems = getSuggestionItems();
final ItemLabelGenerator<Y> 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);
}

/**
Expand Down