Repository navigation
feat: Add blur simulation and implicit focus tracking (#164) (CP: 25.3) - #287
Merged
Merged
Conversation
This PR cherry-picks changes from the original PR #164 to branch 25.3. --- #### Original PR description > ## Summary > Browserless tests now track which component has keyboard focus, so focus and blur listeners fire on their own when a tester clicks a component or sets its value. `ComponentTester` also gets explicit `focus()`, `blur()` and `isFocused()` methods, and server-side `Focusable.focus()` / `blur()` calls are simulated too. > > Fixes #163 > > ## What changed > > **Behavior change:** interactions made through testers now fire focus and blur events. This affects all users, but only tests whose components have focus or blur listeners: > > - `click()` (the shared `Clickable` implementation) and `setValueAsUser(...)` first move focus to the component, which blurs the previously focused one. Blur runs before the click is handled, like in a browser. > - Existing tests that count events, or that expect no focus/blur, may see extra events now. > - If the application has a `Focusable.focus()` or `blur()` call pending, the simulation reads the pending JavaScript queue, which consumes it — any other JavaScript queued at the same time is dropped. When no focus or blur call is pending the queue is left untouched, so JavaScript scheduled by the application can still be asserted on. Everything consumed is listed in a debug log under `com.vaadin.browserless.FocusTracker`. Handling the queue centrally is tracked in [#221](#221). > > New behaviour: > > - New package-private `FocusTracker` keeps the focused component per UI. It fires `focus` / `blur` DOM events as if they came from the client, and mirrors Flow's markers so that a server-initiated focus or blur reports `isFromClient() == false`. > - Server-side `Focusable.focus()` / `blur()` calls are applied at the end of a focus-tracked interaction, on `roundTrip()`, and when `isFocused()` is called. > - `focus()` and `blur()` fail fast: `IllegalArgumentException` if the component is not `Focusable`, `IllegalStateException` if it is disabled or not attached. Read-only fields can still be focused. > - Only `Focusable` components take focus. Interacting with anything else blurs the previous component and leaves nothing focused, like focus falling back to the document body. > - Focus is per UI, so it does not survive a page reload, not even in a `@PreserveOnRefresh` view. > - Testers that fire their events directly (radio button, menu bar, context menu item clicks) do not take part in focus tracking. This is documented in the README together with the other limits. > > ## Use case > A form focuses the next field from a value change listener, and a "save" button validates the amount in a blur listener. The developer wants a test that proves both happen in the right order, without any browser. > > ```java > TextField amount = new TextField("Amount"); > TextField note = new TextField("Note"); > amount.addValueChangeListener(e -> note.focus()); // application logic > amount.addBlurListener(e -> validate(amount.getValue())); > > // test > test(amount).setValue("100"); // focuses the field, then fires the value change > assertTrue(test(note).isFocused()); // server-side focus() was applied > > test(save).click(); // blurs "note" first, then handles the click > assertFalse(test(note).isFocused()); > > // explicit control when nothing else is interacted with afterwards > test(amount).focus(); > test(amount).blur(); > ``` > > ## API Changes > > ### com.vaadin.browserless.ComponentTester > > ```java > // Added > public void focus() // moves simulated keyboard focus to the wrapped component > public void blur() // fires a blur event; no-op if the component is not focused > public boolean isFocused() // true if the component has focus, server-side focus() included > ``` > > ## Test summary > **Implicit focus on interactions:** > - Field focused when a value is set through the tester > - Previously focused field blurred by a click or by setting another field > - Blur listener runs before the click listener > - No blur or re-focus on consecutive edits of the same field > - Click on a non-focusable component blurs the field and focuses nothing > > **Explicit focus API:** > - Blur through the tester fires the blur event as from the client > - Blur on a non-focused component is a no-op > - `focus()` / `blur()` fail with a readable message when the component is not `Focusable` > - Read-only field focusable, disabled or detached field fails > > **Server-side Focusable calls:** > - `focus()` from a value change listener moves focus and blurs the previous field, not from client > - `focus(FocusOption...)` detected as well > - `blur()` fires the blur event and clears focus, not from client > - Focus scheduled outside an interaction applied on `roundTrip()` > - Field focused in a dialog opened by a click listener > > **Page reload:** > - Focus cleared after a reload, also in a `@PreserveOnRefresh` view (cherry picked from commit 33e025f)
mcollovati
enabled auto-merge (squash)
October 13, 2026 13:01
caalador
approved these changes
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR cherry-picks changes from the original PR #164 to branch 25.3.
Original PR description