Skip to content
Closed
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
Expand Up @@ -15,10 +15,14 @@
*/
package com.vaadin.flow.component.combobox.test;

import org.junit.Assert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.vaadin.flow.component.combobox.testbench.ComboBoxElement;
import com.vaadin.flow.testutil.TestPath;
Expand All @@ -35,7 +39,10 @@ public void lazyComboBoxFilterFirstQuery() {
comboBox.openPopup();

WebElement query = findElement(By.id("query"));
Assert.assertTrue(query.getText().contains("Filter: 1"));
Assert.assertTrue(query.getText().contains("Count: 10"));

WebDriverWait wait = new WebDriverWait(driver,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Test hand-builds a WebDriverWait instead of the inherited waitUntil helper used throughout this module.

AbstractComponentIT (via com.vaadin.flow.testutil.AbstractComponentIT) already provides waitUntil(ExpectedCondition), and it is the established pattern across this IT module (ComboBoxClearIT, DataProviderIT, ClientSideFilterIT, and others). The new code instead constructs new WebDriverWait(driver, Duration.of(2, ChronoUnit.SECONDS)) and pulls in three extra imports (Duration, ChronoUnit, WebDriverWait).

The cost is inconsistent timeout/polling behavior and boilerplate the shared helper exists to avoid. Replace with waitUntil(ExpectedConditions.textToBePresentInElement(query, "Filter: 1 Count: 10")).

LazyComboBoxFilterIT.java:43 · reuse · confirmed

Duration.of(2, ChronoUnit.SECONDS));
wait.until(ExpectedConditions.textToBePresentInElement(query,
"Filter: 1 Count: 10"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public abstract class ComboBoxBase<TComponent extends ComboBoxBase<TComponent, T
HasValidationProperties, HasValidator<TValue>, HasPlaceholder {
private static final int DEFAULT_FILTER_TIMEOUT = 500;

private String lastKnownFilter;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 lastKnownFilter is a hand-maintained shadow of the filter that must be poked at every client entry point, and already misses paths on day one.

The removed @Synchronize was a single source of truth kept current by Flow on every filter-changed event. The replacement is a field updated in only two sites (setFilter() and the setViewportRange RPC) and never cleared — so it already fails to track the client-side-filter path and the popup-close path (see the two correctness findings). ComboBoxDataController also already tracks the requested filter in its own lastFilter field.

The cost is fragility: any future client path that changes the filter, or any new server reader of getFilter(), must remember to update this private field, with nothing enforcing it. A deeper fix would adjust when the client sends the filter (dropping the redundant per-keystroke sync) rather than adding a parallel field that drifts out of sync.

ComboBoxBase.java:102 · altitude · plausible


/**
* Registration for custom value listeners that disallows entering custom
* values as soon as there are no more listeners for the custom value event
Expand Down Expand Up @@ -346,9 +348,8 @@ public void setAllowCustomValue(boolean allowCustomValue) {
*
* @return the filter string
*/
@Synchronize(property = "filter", value = "filter-changed")
protected String getFilter() {
return getElement().getProperty("filter");
return lastKnownFilter;
}

/**
Expand All @@ -359,7 +360,11 @@ protected String getFilter() {
* @param filter
* the String value to set
*/
@Deprecated
protected void setFilter(String filter) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 setFilter() is left half-finished: a now-dead filter property write, stale javadoc, and a dated speculative comment.

With @Synchronize removed and getFilter() reading lastKnownFilter, nothing reads the filter element property anymore (no getProperty("filter") remains), so getElement().setProperty("filter", ...) on line 368 is an inert DOM write. The javadoc still says Setter is only required to allow using @Synchronize — the very annotation this PR deletes — and the body carries // MT 20250-02-21 I assume client side never uses this, probably the whole method is obsolete (note the typo year 20250).

The cost is that a future maintainer cannot tell what the method is for or whether it is safe to delete: the javadoc, the inline comment, and the @Deprecated (which has no @deprecated tag or replacement) give three conflicting signals. Resolve it here — confirm the client ignores the property and remove the method or the dead write, and replace the scratch comment with a real deprecation note.

ComboBoxBase.java:364 · simplification · confirmed

lastKnownFilter = filter;
// MT 20250-02-21 I assume client side never uses this, probably the
// whole method is obsolete
getElement().setProperty("filter", filter == null ? "" : filter);
}

Expand Down Expand Up @@ -1367,6 +1372,7 @@ private void confirmUpdate(int id) {
*/
@ClientCallable
private void setViewportRange(int start, int length, String filter) {
this.lastKnownFilter = filter;
dataController.setViewportRange(start, length, filter);
}

Expand Down