diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkQueryParameterTargetView.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkQueryParameterTargetView.java new file mode 100644 index 000000000..4de67c72e --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkQueryParameterTargetView.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2000-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.routerlink; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasComponents; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.BeforeEnterObserver; +import com.vaadin.flow.router.Route; + +import java.util.stream.Collectors; + +@Tag(Tag.DIV) +@Route(value = RouterLinkQueryParameterTargetView.ROUTE, registerAtStartup = false) +public class RouterLinkQueryParameterTargetView extends Component + implements HasComponents, BeforeEnterObserver { + + public static final String ROUTE = "router-link-query-parameter-target"; + + @Override + public void beforeEnter(BeforeEnterEvent event) { + var queryParameters = event.getLocation().getQueryParameters(); + + add(new Span("Query Parameter Target View: { " + + queryParameters.getParameters().entrySet().stream() + .map(entry -> entry.getKey() + " = [" + + entry.getValue().stream() + .sorted() + .collect(Collectors.joining(", ")) + + "]") + .sorted() + .collect(Collectors.joining("; ")) + + " }")); + } +} diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkRouteParameterTargetView.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkRouteParameterTargetView.java new file mode 100644 index 000000000..67188208a --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkRouteParameterTargetView.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2000-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.routerlink; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasComponents; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.BeforeEnterObserver; +import com.vaadin.flow.router.Route; + +import java.util.stream.Collectors; + +@Tag(Tag.DIV) +@Route(value = RouterLinkRouteParameterTargetView.ROUTE + RouterLinkRouteParameterTargetView.ROUTE_PARAMETERS, + registerAtStartup = false) +public class RouterLinkRouteParameterTargetView extends Component + implements HasComponents, BeforeEnterObserver { + + public static final String ROUTE = "router-link-route-parameter-target"; + public static final String ROUTE_PARAMETERS = "/:segment1?/static/:segment2/:segment3*"; + + @Override + public void beforeEnter(BeforeEnterEvent event) { + var routeParameters = event.getRouteParameters(); + + add(new Span("Route Parameter Target View: { " + + routeParameters.getParameterNames().stream() + .map(name -> name + " = " + routeParameters.get(name).orElse("")) + .sorted() + .collect(Collectors.joining("; ")) + + " }")); + } +} diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkStaticTargetView.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkStaticTargetView.java new file mode 100644 index 000000000..7f825cfd3 --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkStaticTargetView.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2000-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.routerlink; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasComponents; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.Route; + +@Tag(Tag.DIV) +@Route(value = RouterLinkStaticTargetView.ROUTE, registerAtStartup = false) +public class RouterLinkStaticTargetView extends Component + implements HasComponents { + + public static final String ROUTE = "router-link-static-target"; + + public RouterLinkStaticTargetView() { + add(new Span("Static Target View")); + } +} diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkTesterTest.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkTesterTest.java new file mode 100644 index 000000000..8bebc4b97 --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkTesterTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2000-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.routerlink; + +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.RouteConfiguration; +import com.vaadin.flow.router.RouterLink; +import com.vaadin.testbench.unit.ComponentTester; +import com.vaadin.testbench.unit.UIUnitTest; +import com.vaadin.testbench.unit.ViewPackages; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@ViewPackages +class RouterLinkTesterTest extends UIUnitTest { + + private ComponentTester $routerLinkView; + + @BeforeEach + void init() { + RouteConfiguration.forApplicationScope() + .setAnnotatedRoute(RouterLinkView.class); + RouteConfiguration.forApplicationScope() + .setAnnotatedRoute(RouterLinkStaticTargetView.class); + RouteConfiguration.forApplicationScope() + .setAnnotatedRoute(RouterLinkUrlParameterTargetView.class); + RouteConfiguration.forApplicationScope() + .setAnnotatedRoute(RouterLinkQueryParameterTargetView.class); + RouteConfiguration.forApplicationScope() + .setAnnotatedRoute(RouterLinkRouteParameterTargetView.class); + + var routerLinkView = navigate(RouterLinkView.class); + $routerLinkView = test(routerLinkView); + } + + @Test + void routerLink_targetless() { + // get router link + var targetlessRouterLink = $routerLinkView.find(RouterLink.class) + .withText("No Target") + .single(); + var $targetlessRouterLink = test(targetlessRouterLink); + Assertions.assertNotNull($targetlessRouterLink, + "Tester for targetless RouterLink not initialized."); + + // verify its href + Assertions.assertEquals("", + $targetlessRouterLink.getHref()); + + // verify its click action fails due to no navigation target + Assertions.assertThrows(IllegalStateException.class, $targetlessRouterLink::click); + } + + @Test + void routerLink_static() { + // get router link + var staticRouterLink = $routerLinkView.find(RouterLink.class) + .withText("Static Target") + .single(); + var $staticRouterLink = test(staticRouterLink); + Assertions.assertNotNull($staticRouterLink, + "Tester for static RouterLink not initialized."); + + // verify its href + Assertions.assertEquals(RouterLinkStaticTargetView.ROUTE, + $staticRouterLink.getHref()); + + // verify its click action returns correct target + var targetView = $staticRouterLink.click(); + Assertions.assertInstanceOf(RouterLinkStaticTargetView.class, targetView); + + // verify navigation target is correct + var $targetView = test(targetView); + Assertions.assertDoesNotThrow(() -> $targetView.find(Span.class) + .withText("Static Target View") + .single()); + } + + @Test + void routerLink_emptyUrlParameter() { + var emptyUrlParameterRouterLink = $routerLinkView.find(RouterLink.class) + .withText("Empty URL Parameter Target") + .single(); + var $emptyUrlParameterRouterLink = test(emptyUrlParameterRouterLink); + Assertions.assertNotNull($emptyUrlParameterRouterLink, + "Tester for empty URL parameter RouterLink not initialized."); + + // verify its href + Assertions.assertEquals(RouterLinkUrlParameterTargetView.ROUTE, + $emptyUrlParameterRouterLink.getHref()); + + // verify its click action returns correct target + var targetView = $emptyUrlParameterRouterLink.click(); + Assertions.assertInstanceOf(RouterLinkUrlParameterTargetView.class, targetView); + + // verify navigation target is correct + var $targetView = test(targetView); + Assertions.assertDoesNotThrow(() -> $targetView.find(Span.class) + .withText("URL Parameter Target View: { }") + .single()); + } + + @Test + void routerLink_urlParameter() { + var urlParameterRouterLink = $routerLinkView.find(RouterLink.class) + .withText("URL Parameter Target") + .single(); + var $urlParameterRouterLink = test(urlParameterRouterLink); + Assertions.assertNotNull($urlParameterRouterLink, + "Tester for URL parameter RouterLink not initialized."); + + // verify its href + Assertions.assertEquals(RouterLinkUrlParameterTargetView.ROUTE + "/parameter-value", + $urlParameterRouterLink.getHref()); + + // verify its click action returns correct target + var targetView = $urlParameterRouterLink.click(); + Assertions.assertInstanceOf(RouterLinkUrlParameterTargetView.class, targetView); + + // verify navigation target is correct + var $targetView = test(targetView); + Assertions.assertDoesNotThrow(() -> $targetView.find(Span.class) + .withText("URL Parameter Target View: { parameter-value }") + .single()); + } + + @Test + void routerLink_queryParameter() { + var queryParameterRouterLink = $routerLinkView.find(RouterLink.class) + .withText("Query Parameter Target") + .single(); + var $queryParameterRouterLink = test(queryParameterRouterLink); + Assertions.assertNotNull($queryParameterRouterLink, + "Tester for QueryParameter RouterLink not initialized."); + + // verify its href + Assertions.assertEquals(RouterLinkQueryParameterTargetView.ROUTE + + "?parameter2=parameter2-value1¶meter2=parameter2-value2¶meter1=parameter1-value", + $queryParameterRouterLink.getHref()); + + // verify its click action returns correct target + var targetView = $queryParameterRouterLink.click(); + Assertions.assertInstanceOf(RouterLinkQueryParameterTargetView.class, targetView); + + // verify navigation target is correct + var $targetView = test(targetView); + Assertions.assertDoesNotThrow(() -> $targetView.find(Span.class) + .withText("Query Parameter Target View: { parameter1 = [parameter1-value]; parameter2 = [parameter2-value1, parameter2-value2] }") + .single()); + } + + @Test + void routerLink_routeParameter() { + var routeParameterRouterLink = $routerLinkView.find(RouterLink.class) + .withText("Route Parameter Target") + .single(); + var $routeParameterRouterLink = test(routeParameterRouterLink); + Assertions.assertNotNull($routeParameterRouterLink, + "Tester for RouteParameter RouterLink not initialized."); + + // verify its href + Assertions.assertEquals(RouterLinkRouteParameterTargetView.ROUTE + + "/static/segment2-value/segment3-value1/segment3-value2", + $routeParameterRouterLink.getHref()); + + // verify its click action returns correct target + var targetView = $routeParameterRouterLink.click(); + Assertions.assertInstanceOf(RouterLinkRouteParameterTargetView.class, targetView); + + // verify navigation target is correct + var $targetView = test(targetView); + Assertions.assertDoesNotThrow(() -> $targetView.find(Span.class) + .withText("Route Parameter Target View: { segment2 = segment2-value; segment3 = segment3-value1/segment3-value2 }") + .single()); + } +} diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkUrlParameterTargetView.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkUrlParameterTargetView.java new file mode 100644 index 000000000..ad6f41855 --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkUrlParameterTargetView.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2000-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.routerlink; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasComponents; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.BeforeEvent; +import com.vaadin.flow.router.HasUrlParameter; +import com.vaadin.flow.router.OptionalParameter; +import com.vaadin.flow.router.Route; + +@Tag(Tag.DIV) +@Route(value = RouterLinkUrlParameterTargetView.ROUTE, registerAtStartup = false) +public class RouterLinkUrlParameterTargetView extends Component + implements HasComponents, HasUrlParameter { + + public static final String ROUTE = "router-link-url-parameter-target"; + + @Override + public void setParameter(BeforeEvent event, @OptionalParameter String parameter) { + add(new Span("URL Parameter Target View: { " + (parameter != null ? parameter : "") + " }")); + } +} diff --git a/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkView.java b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkView.java new file mode 100644 index 000000000..547e29c17 --- /dev/null +++ b/vaadin-testbench-unit-junit5/src/test/java/com/vaadin/flow/component/routerlink/RouterLinkView.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2000-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.routerlink; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasComponents; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.router.QueryParameters; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouteParameters; +import com.vaadin.flow.router.RouterLink; + +import java.util.Map; + +@Tag(Tag.DIV) +@Route(value = RouterLinkView.ROUTE, registerAtStartup = false) +public class RouterLinkView extends Component implements HasComponents { + + public static final String ROUTE = "router-link-test"; + + public RouterLinkView() { + // targetless router link + var targetlessRouterLink = new RouterLink(); + targetlessRouterLink.setText("No Target"); + + // static router link + var staticRouterLink = new RouterLink("Static Target", + RouterLinkStaticTargetView.class); + + // url parameter router link - empty + var emptyUrlParameterRouterLink = new RouterLink("Empty URL Parameter Target", + RouterLinkUrlParameterTargetView.class); + + // url parameter router link - non-empty + var urlParameterRouterLink = new RouterLink("URL Parameter Target", + RouterLinkUrlParameterTargetView.class, + "parameter-value"); + + // query parameter router link + var queryParameterRouterLink = new RouterLink("Query Parameter Target", + RouterLinkQueryParameterTargetView.class); + queryParameterRouterLink.setQueryParameters( + QueryParameters.empty() + .merging("parameter1", "parameter1-value") + .merging("parameter2", "parameter2-value1", "parameter2-value2")); + + // route parameter router link + var routeParameterRouterLink = new RouterLink("Route Parameter Target", + RouterLinkRouteParameterTargetView.class, + new RouteParameters(Map.ofEntries( + Map.entry("segment2", "segment2-value"), + Map.entry("segment3", "segment3-value1/segment3-value2")))); + + add(targetlessRouterLink); + add(staticRouterLink); + add(emptyUrlParameterRouterLink); + add(urlParameterRouterLink); + add(queryParameterRouterLink); + add(routeParameterRouterLink); + } +} diff --git a/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/routerlink/RouterLinkTester.java b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/routerlink/RouterLinkTester.java new file mode 100644 index 000000000..52109825f --- /dev/null +++ b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/flow/component/routerlink/RouterLinkTester.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2000-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.flow.component.routerlink; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.router.QueryParameters; +import com.vaadin.flow.router.RouteConfiguration; +import com.vaadin.flow.router.RouterLink; +import com.vaadin.testbench.unit.ComponentTester; +import com.vaadin.testbench.unit.Tests; + +import java.net.URI; +import java.util.Optional; + +/** + * + * Tester for RouterLink components. + * + * @param + * component type + */ +@Tests(RouterLink.class) +public class RouterLinkTester extends ComponentTester { + + /** + * Wrap given component for testing. + * + * @param component target component + */ + public RouterLinkTester(T component) { + super(component); + } + + /** + * Gets the URL that the router-link links to. + * + * @return the href value, or empty string if no href has been set + */ + public String getHref() { + ensureComponentIsUsable(); + + return getComponent().getHref(); + } + + /** + * Gets the path for the router-link. + * Returns an empty {@link String} if there is no corresponding navigation target. + * + * @return a {@link String} containing the navigation target path or empty if not present + */ + public String getPath() { + return URI.create(getHref()).getPath(); + } + + /** + * Gets the query parameters for the router-link. + * + * @return a {@link QueryParameters} containing the navigation target's query parameters + */ + public QueryParameters getQueryParameters() { + return QueryParameters.fromString(URI.create(getHref()).getQuery()); + } + + /** + * Gets the registered route class for the router-link. + * Returns an empty optional if there is no corresponding navigation target. + * + * @return an {@link Optional} containing the navigation target class or empty if not found + */ + public Optional> getRoute() { + return RouteConfiguration.forSessionScope().getRoute(getPath()); + } + + /** + * Click the router-link for navigation. + * + * @return navigated view + */ + public Component click() { + if (getRoute().isEmpty()) { + throw new IllegalStateException(); + } + + UI.getCurrent().navigate(getPath(), getQueryParameters()); + return (Component) UI.getCurrent().getInternals() + .getActiveRouterTargetsChain().get(0); + } +} diff --git a/vaadin-testbench-unit-shared/src/main/java/com/vaadin/testbench/unit/TesterWrappers.java b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/testbench/unit/TesterWrappers.java index 6566b7444..331a9118d 100644 --- a/vaadin-testbench-unit-shared/src/main/java/com/vaadin/testbench/unit/TesterWrappers.java +++ b/vaadin-testbench-unit-shared/src/main/java/com/vaadin/testbench/unit/TesterWrappers.java @@ -100,6 +100,7 @@ import com.vaadin.flow.component.notification.NotificationTester; import com.vaadin.flow.component.radiobutton.RadioButtonGroup; import com.vaadin.flow.component.radiobutton.RadioButtonGroupTester; +import com.vaadin.flow.component.routerlink.RouterLinkTester; import com.vaadin.flow.component.select.Select; import com.vaadin.flow.component.select.SelectTester; import com.vaadin.flow.component.sidenav.SideNav; @@ -124,6 +125,7 @@ import com.vaadin.flow.component.upload.UploadTester; import com.vaadin.flow.component.virtuallist.VirtualList; import com.vaadin.flow.component.virtuallist.VirtualListTester; +import com.vaadin.flow.router.RouterLink; import java.math.BigDecimal; @@ -138,6 +140,10 @@ default ButtonTester