diff --git a/flow-server/src/main/java/com/vaadin/flow/component/geolocation/Geolocation.java b/flow-server/src/main/java/com/vaadin/flow/component/geolocation/Geolocation.java index f9f9de71cb4..bd852174c9c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/geolocation/Geolocation.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/geolocation/Geolocation.java @@ -16,6 +16,7 @@ package com.vaadin.flow.component.geolocation; import java.io.Serializable; +import java.util.Objects; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -41,11 +42,15 @@ *

* Two usage modes: *

- * For the one-shot {@link Geolocation#get} callback use the narrower - * {@link GeolocationOutcome}, which excludes {@link GeolocationPending} - * (one-shot requests never produce that value). + * One-shot {@link Geolocation#get} requests never produce + * {@link GeolocationPending}; they deliver the position and the error through + * separate callbacks instead. *

* The sealed hierarchy is designed for exhaustive pattern matching. A * {@code switch} covering the three permitted variants is guaranteed complete diff --git a/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationClientSeamTest.java b/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationClientSeamTest.java index 61b5a0359ac..6318914ca65 100644 --- a/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationClientSeamTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationClientSeamTest.java @@ -36,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -71,7 +70,8 @@ void lookupFactory_resolvedAtConstruction_clientReceivesGetCalls() { .thenReturn(unused -> fake); UI freshUi = new MockUI(); - freshUi.getGeolocation().get(outcome -> { + freshUi.getGeolocation().get(pos -> { + }, err -> { }); assertEquals(1, fake.getCalls.size(), @@ -83,7 +83,8 @@ void setClient_routesGetThroughInstalledClient() { FakeClient fake = new FakeClient(); ui.getGeolocation().setClient(fake); - ui.getGeolocation().get(outcome -> { + ui.getGeolocation().get(pos -> { + }, err -> { }); assertEquals(1, fake.getCalls.size(), @@ -131,21 +132,21 @@ void track_handleIsNullAfterStop() { } @Test - void get_callbackReceivesUnknownErrorWhenClientFutureFailsExceptionally() { + void get_onErrorReceivesUnknownErrorWhenClientFutureFailsExceptionally() { FakeClient fake = new FakeClient(); fake.nextGetResult = CompletableFuture .failedFuture(new RuntimeException( "Client-side geolocation.get failed: boom")); ui.getGeolocation().setClient(fake); - AtomicReference<@Nullable GeolocationOutcome> received = new AtomicReference<>(); - ui.getGeolocation().get(received::set); + AtomicReference<@Nullable GeolocationPosition> position = new AtomicReference<>(); + AtomicReference<@Nullable GeolocationError> error = new AtomicReference<>(); + ui.getGeolocation().get(position::set, error::set); - GeolocationOutcome outcome = received.get(); - assertNotNull(outcome, - "callback must fire even when the JS bridge fails"); - GeolocationError err = assertInstanceOf(GeolocationError.class, outcome, - "infra failure should surface as a GeolocationError"); + GeolocationError err = error.get(); + assertNotNull(err, "onError must fire even when the JS bridge fails"); + assertNull(position.get(), + "onSuccess must stay silent when the bridge fails"); assertEquals(GeolocationErrorCode.UNKNOWN, err.errorCode(), "error code should be UNKNOWN for client-bridge failures"); assertFalse(err.message().contains("boom"), diff --git a/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationTest.java b/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationTest.java index 49765ca7ba3..8de2b73d68b 100644 --- a/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationTest.java @@ -156,7 +156,8 @@ void get_executesPromiseJs() { TestComponent component = new TestComponent(); ui.add(component); - ui.getGeolocation().get(result -> { + ui.getGeolocation().get(pos -> { + }, err -> { }); List invocations = ui @@ -166,35 +167,38 @@ void get_executesPromiseJs() { } @Test - void get_callbackReceivesPosition() { + void get_onSuccessReceivesPositionAndOnErrorIsSilent() { TestComponent component = new TestComponent(); ui.add(component); - List received = new ArrayList<>(); - ui.getGeolocation().get(received::add); + List positions = new ArrayList<>(); + List errors = new ArrayList<>(); + ui.getGeolocation().get(positions::add, errors::add); resolvePromise(ui, resultJson(position(60.1699, 24.9384, 10.0), null, "GRANTED")); - assertEquals(1, received.size()); - assertInstanceOf(GeolocationPosition.class, received.get(0)); - assertEquals(60.1699, - ((GeolocationPosition) received.get(0)).coords().latitude()); + assertEquals(1, positions.size()); + assertEquals(60.1699, positions.get(0).coords().latitude()); + assertTrue(errors.isEmpty(), + "onError must not fire for a successful reading"); } @Test - void get_callbackReceivesError() { + void get_onErrorReceivesErrorAndOnSuccessIsSilent() { TestComponent component = new TestComponent(); ui.add(component); - List received = new ArrayList<>(); - ui.getGeolocation().get(received::add); + List positions = new ArrayList<>(); + List errors = new ArrayList<>(); + ui.getGeolocation().get(positions::add, errors::add); resolvePromise(ui, resultJson(null, error(1, "denied"), "DENIED")); - assertEquals(1, received.size()); - assertInstanceOf(GeolocationError.class, received.get(0)); - assertEquals(1, ((GeolocationError) received.get(0)).code()); + assertEquals(1, errors.size()); + assertEquals(1, errors.get(0).code()); + assertTrue(positions.isEmpty(), + "onSuccess must not fire when the browser reports an error"); } @Test @@ -202,7 +206,8 @@ void get_updatesAvailabilityFromResponse() { TestComponent component = new TestComponent(); ui.add(component); - ui.getGeolocation().get(result -> { + ui.getGeolocation().get(pos -> { + }, err -> { }); resolvePromise(ui, resultJson(position(60.0, 25.0, 10.0), null, "GRANTED")); diff --git a/flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/GeolocationView.java b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/GeolocationView.java index c16b2645339..abcd8381986 100644 --- a/flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/GeolocationView.java +++ b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/GeolocationView.java @@ -16,7 +16,6 @@ package com.vaadin.flow.uitest.ui; import com.vaadin.flow.component.UI; -import com.vaadin.flow.component.geolocation.GeolocationError; import com.vaadin.flow.component.geolocation.GeolocationOptions; import com.vaadin.flow.component.geolocation.GeolocationPosition; import com.vaadin.flow.component.geolocation.GeolocationTracker; @@ -106,35 +105,35 @@ protected void onShow() { """); NativeButton getButton = createButton("Get Position", "getButton", - e -> UI.getCurrent().getGeolocation().get(outcome -> { + e -> e.getUI().getGeolocation().get(pos -> { Div out = new Div(); out.setId("getResult"); - switch (outcome) { - case GeolocationPosition pos -> - out.setText("lat=" + pos.coords().latitude() + ", lon=" - + pos.coords().longitude()); - case GeolocationError error -> out.setText( + out.setText("lat=" + pos.coords().latitude() + ", lon=" + + pos.coords().longitude()); + add(out); + }, error -> { + Div out = new Div(); + out.setId("getResult"); + out.setText( "error=" + error.code() + ":" + error.message()); - } add(out); })); - // Uses the mock's "maximumAge == -1 → error" trigger to exercise + // Uses the mock's "maximumAge == 9999 → error" trigger to exercise // the error branch. NativeButton getErrorButton = createButton("Get Position (error)", - "getErrorButton", e -> UI.getCurrent().getGeolocation().get( - new GeolocationOptions(null, null, 9999), outcome -> { - Div out = new Div(); - out.setId("getErrorResult"); - switch (outcome) { - case GeolocationPosition pos -> out.setText( - "unexpected position: " + pos.coords()); - case GeolocationError error -> - out.setText("error=" + error.errorCode() + ":" - + error.message()); - } - add(out); - })); + "getErrorButton", e -> e.getUI().getGeolocation().get(pos -> { + Div out = new Div(); + out.setId("getErrorResult"); + out.setText("unexpected position: " + pos.coords()); + add(out); + }, error -> { + Div out = new Div(); + out.setId("getErrorResult"); + out.setText("error=" + error.errorCode() + ":" + + error.message()); + add(out); + }, new GeolocationOptions(null, null, 9999))); NativeButton trackButton = createButton("Track Position", "trackButton", e -> {