Skip to content

feat: add GeolocationClient port for external test drivers#24211

Merged
heruan merged 7 commits intomainfrom
feat/geolocation-test-support
May 4, 2026
Merged

feat: add GeolocationClient port for external test drivers#24211
heruan merged 7 commits intomainfrom
feat/geolocation-test-support

Conversation

@heruan
Copy link
Copy Markdown
Member

@heruan heruan commented Apr 28, 2026

Summary

  • Extract a GeolocationClient port behind the Geolocation facade and GeolocationTracker. Production wire behavior moves to a new BrowserGeolocationClient; executeJs expressions, DOM event names and target elements are unchanged.
  • Add Geolocation.setClient(GeolocationClient) and GeolocationTracker.handle() as public framework-internal entry points so external browserless test drivers (e.g. vaadin/browserless-test) can swap the production client and reach the active WatchHandle without reflection.
  • Use SerializableConsumer<T> for the port's listener types and mark Geolocation.availabilitySubscription transient so dev-mode UI/session serialization keeps working.

Adds the API surface needed by the Geolocation browserless testing PRD requirement. The actual test driver lives in vaadin/browserless-test (separate PR) — keeping it out of flow avoids dragging Selenium / TestBench into the dependency tree of consumers that only want browserless unit tests.

Test plan

  • mvn test -pl :flow-server -Dtest=GeolocationTest — 33 wire-protocol assertions still pass (production behavior preserved byte-for-byte)
  • mvn test -pl :flow-server -Dtest=GeolocationClientSeamTest — 4 new tests pin the setClient + handle() contract
  • mvn test -pl :flow-server -Dtest=SerializationTest,FlowClassesSerializableTest — dev-mode UI/session serialization still passes
  • End-to-end smoke: companion test driver branch in vaadin/browserless-test (feat/geolocation-test-support) consumes this branch; ran against use-cases/geolocation, all 7 PRD use cases testable browserlessly (12 tests, 0 failures)

heruan added 2 commits April 27, 2026 14:29
Carve a small GeolocationClient port between the Geolocation facade and
the underlying delivery mechanism. The production implementation,
BrowserGeolocationClient, carries the existing wire behavior unchanged:
same executeJs expressions, same DOM event names, same target elements.
The facade and GeolocationTracker now delegate to the port instead of
calling executeJs directly.

Two methods are added as public framework-internal entry points so
external browserless test drivers can replace the production client and
inspect tracker handles without reflection:

  - Geolocation.setClient(GeolocationClient) swaps the active client,
    closing the previous one and re-wiring the availability subscription.
  - GeolocationTracker.handle() exposes the active WatchHandle (or null
    when stopped) so test drivers can push synthetic position/error
    updates to a specific tracker.

The port uses SerializableConsumer rather than java.util.function.Consumer
to keep listener lambdas serializable. Geolocation.availabilitySubscription
is transient because the Registration lambda's nested SerializedLambda
does not deserialize cleanly; the underlying listener stays in the
client's listener list, and setClient() unconditionally closes the prior
client to drop any orphaned wiring.

Wire protocol unchanged; the existing GeolocationTest cases verify this.
Verify the four invariants of the Geolocation.setClient and
GeolocationTracker.handle() seam:

  - setClient routes Geolocation.get(...) through the installed client.
  - setClient closes the previous client when replacing it.
  - track(...) returns a tracker whose handle() comes from the client
    that was active at track() time.
  - handle() returns null after stop().

Uses an in-test FakeClient implementing GeolocationClient directly, with
no external test infrastructure.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

Test Results

 1 395 files  +1   1 395 suites  +1   1h 16m 42s ⏱️ + 2m 13s
10 069 tests +4   9 999 ✅ +4  70 💤 ±0  0 ❌ ±0 
10 544 runs  +4  10 465 ✅ +4  79 💤 ±0  0 ❌ ±0 

Results for commit cf29268. ± Comparison against base commit 29044c1.

♻️ This comment has been updated with latest results.

@heruan heruan self-assigned this Apr 28, 2026
@heruan heruan marked this pull request as ready for review April 28, 2026 08:20
GeolocationClient, Geolocation#setClient, and GeolocationTracker#handle
are now package-private. The split-package test driver in browserless-test
lives in the same package and can access them directly without the SPI
being exposed as public API.
Comment thread flow-server/src/main/java/com/vaadin/flow/component/geolocation/Geolocation.java Outdated
Comment thread flow-server/src/main/java/com/vaadin/flow/component/geolocation/Geolocation.java Outdated
} else if (result.error() != null) {
future.complete(result.error());
} else {
LOGGER.debug(
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.

I guess here we should complete the future exceptionally.

LOGGER.debug(
"Geolocation get() returned neither position nor error");
}
}, err -> LOGGER.debug("Client-side geolocation.get failed: {}",
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.

I guess here we should complete the future exceptionally.

heruan added 2 commits April 29, 2026 18:07
Drop redundant availabilitySubscription field —
BrowserGeolocationClient.close() clears its listeners on its own.
Eagerly install the production client via setClient(...) instead of a
lazy accessor; pass the bootstrap-seeded availability into the
BrowserGeolocationClient constructor so the SPI doesn't read framework
internals.
When the browser response has neither a position nor an error, or when
executeJs fails, the CompletableFuture from BrowserGeolocationClient.get(...)
was previously orphaned. Callers chained via .exceptionally(...) got nothing
and .thenAccept(...) silently never fired. Complete the future exceptionally
so failures surface.
}
}, err -> LOGGER.debug("Client-side geolocation.get failed: {}",
err));
client.get(options).thenAccept(callback);
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.

This code now silently ignores the potential exception instead of logging it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — switched to whenComplete so an exceptionally completed future logs a warning instead of being swallowed silently.

Replace thenAccept with whenComplete so an exceptionally completed
future from the client logs a warning instead of being silently
swallowed.
@github-actions github-actions Bot added +1.0.0 and removed +0.1.0 labels May 4, 2026
client.get(options).thenAccept(callback);
client.get(options).whenComplete((outcome, error) -> {
if (error != null) {
LOGGER.warn("Geolocation get() failed", error);
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.

IIRC we usually use DEBUG level in such cases to avoid flooding the log

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 4, 2026

@heruan heruan added this pull request to the merge queue May 4, 2026
Merged via the queue into main with commit 9ac549e May 4, 2026
31 checks passed
@mshabarov mshabarov moved this from 🔎Iteration reviews to Done in Vaadin Flow | Hilla | Kits ongoing work May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Development

Successfully merging this pull request may close these issues.

3 participants