diff --git a/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuRegistry.java b/flow-server/src/main/java/com/vaadin/flow/internal/menu/MenuRegistry.java
similarity index 98%
rename from flow-server/src/main/java/com/vaadin/flow/server/menu/MenuRegistry.java
rename to flow-server/src/main/java/com/vaadin/flow/internal/menu/MenuRegistry.java
index f703d0ff6bc..0186c50c417 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuRegistry.java
+++ b/flow-server/src/main/java/com/vaadin/flow/internal/menu/MenuRegistry.java
@@ -14,7 +14,7 @@
* the License.
*/
-package com.vaadin.flow.server.menu;
+package com.vaadin.flow.internal.menu;
import java.io.IOException;
import java.io.InputStream;
@@ -54,6 +54,8 @@
import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
+import com.vaadin.flow.server.menu.AvailableViewInfo;
+import com.vaadin.flow.server.menu.RouteParamType;
import static com.vaadin.flow.server.frontend.FrontendUtils.GENERATED;
@@ -63,6 +65,8 @@
*
* Only returns views that are accessible at the moment and leaves out routes
* that require path parameters.
+ *
+ * For internal use only. May be renamed or removed in a future release.
*/
public class MenuRegistry {
@@ -346,7 +350,7 @@ private static void collectClientViews(String basePath,
if (viewConfig.menu() == null) {
// create MenuData anyway to avoid need for null checking
viewConfig = copyAvailableViewInfo(viewConfig,
- new MenuData(viewConfig.title(), null, false, null));
+ new MenuData(viewConfig.title(), null, false, null, null));
}
configurations.put(path, viewConfig);
if (viewConfig.children() != null) {
diff --git a/flow-server/src/main/java/com/vaadin/flow/router/AbstractRouteNotFoundError.java b/flow-server/src/main/java/com/vaadin/flow/router/AbstractRouteNotFoundError.java
index abd36ab313c..76e772f98d0 100644
--- a/flow-server/src/main/java/com/vaadin/flow/router/AbstractRouteNotFoundError.java
+++ b/flow-server/src/main/java/com/vaadin/flow/router/AbstractRouteNotFoundError.java
@@ -22,8 +22,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -38,12 +36,8 @@
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.Tag;
-import com.vaadin.flow.di.Lookup;
-import com.vaadin.flow.router.internal.ClientRoutesProvider;
import com.vaadin.flow.server.HttpStatusCode;
-import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.frontend.FrontendUtils;
-import com.vaadin.flow.server.menu.MenuRegistry;
/**
* This is abstract error view for routing exceptions.
diff --git a/flow-server/src/main/java/com/vaadin/flow/router/MenuData.java b/flow-server/src/main/java/com/vaadin/flow/router/MenuData.java
index b59a659ed43..becfcdfac4c 100644
--- a/flow-server/src/main/java/com/vaadin/flow/router/MenuData.java
+++ b/flow-server/src/main/java/com/vaadin/flow/router/MenuData.java
@@ -19,12 +19,34 @@
import java.io.Serializable;
import java.util.Objects;
+import com.vaadin.flow.component.Component;
+
/**
* Data class for menu item information.
*
* Only for read as data is immutable.
*/
-public record MenuData(String title, Double order, boolean exclude, String icon) implements Serializable {
+public record MenuData(String title, Double order, boolean exclude, String icon, Class extends Component> menuClass) implements Serializable {
+
+ /**
+ * MenuData constructor.
+ *
+ * @param title
+ * title of the menu item
+ * @param order
+ * order of the menu item
+ * @param exclude
+ * whether the menu item should be excluded
+ * @param icon
+ * the icon of the menu item
+ *
+ * @deprecated Use {@link #MenuData(String, Double, boolean, String, Class)}
+ * instead.
+ */
+ @Deprecated(forRemoval = true)
+ public MenuData(String title, Double order, boolean exclude, String icon) {
+ this(title, order, exclude, icon, null);
+ }
/**
* Gets the title of the menu item.
@@ -65,20 +87,7 @@ public String getIcon() {
@Override
public String toString() {
return "MenuData{" + "title='" + title + '\'' + ", order=" + order
- + ", exclude=" + exclude + ", icon='" + icon + '\'' + '}';
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof MenuData other
- && Objects.equals(title, other.title)
- && Objects.equals(order, other.order)
- && Objects.equals(exclude, other.exclude)
- && Objects.equals(icon, other.icon);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(title, order, exclude, icon);
+ + ", exclude=" + exclude + ", icon='" + icon + "', menuClass='"
+ + menuClass + "'" + '}';
}
}
diff --git a/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java b/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java
index 2fd1dae7eb1..6c853dddd31 100644
--- a/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java
+++ b/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java
@@ -68,7 +68,7 @@
import com.vaadin.flow.server.Constants;
import com.vaadin.flow.server.HttpStatusCode;
import com.vaadin.flow.server.VaadinSession;
-import com.vaadin.flow.server.menu.MenuRegistry;
+import com.vaadin.flow.internal.menu.MenuRegistry;
/**
* Base class for navigation handlers that target a navigation state.
diff --git a/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractRouteRegistry.java b/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractRouteRegistry.java
index ea650fd8dfe..56d23d5ddfe 100644
--- a/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractRouteRegistry.java
+++ b/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractRouteRegistry.java
@@ -56,7 +56,7 @@
import com.vaadin.flow.server.auth.NavigationContext;
import com.vaadin.flow.server.auth.ViewAccessChecker;
import com.vaadin.flow.router.Layout;
-import com.vaadin.flow.server.menu.MenuRegistry;
+import com.vaadin.flow.internal.menu.MenuRegistry;
import com.vaadin.flow.shared.Registration;
import static java.util.stream.Collectors.toList;
@@ -324,7 +324,7 @@ private void populateRegisteredRoutes(ConfiguredRoutes configuration,
(Objects.equals(menu.order(), Double.MIN_VALUE)) ? null
: menu.order(),
excludeFromMenu,
- (menu.icon().isBlank() ? null : menu.icon())))
+ (menu.icon().isBlank() ? null : menu.icon()), target))
.orElse(null);
RouteData route = new RouteData(parentLayouts, template, parameters,
diff --git a/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java b/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java
index 6dbfe0b4069..ce232e44348 100644
--- a/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java
+++ b/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java
@@ -16,7 +16,6 @@
package com.vaadin.flow.router.internal;
import java.util.Collections;
-import java.util.List;
import org.slf4j.LoggerFactory;
@@ -25,11 +24,8 @@
import com.vaadin.flow.router.NavigationStateBuilder;
import com.vaadin.flow.router.NotFoundException;
import com.vaadin.flow.router.RouteResolver;
-import com.vaadin.flow.router.RouterLayout;
import com.vaadin.flow.server.RouteRegistry;
-import com.vaadin.flow.server.VaadinSession;
-import com.vaadin.flow.server.menu.AvailableViewInfo;
-import com.vaadin.flow.server.menu.MenuRegistry;
+import com.vaadin.flow.internal.menu.MenuRegistry;
/**
* Default implementation of the {@link RouteResolver} interface.
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java
index f8935dde512..036e001d16b 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java
@@ -53,15 +53,12 @@
import com.vaadin.flow.internal.Pair;
import com.vaadin.flow.internal.StringUtil;
import com.vaadin.flow.internal.hilla.EndpointRequestUtil;
-import com.vaadin.flow.router.internal.ClientRoutesProvider;
import com.vaadin.flow.server.AbstractConfiguration;
import com.vaadin.flow.server.Constants;
-import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinServlet;
-import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
-import com.vaadin.flow.server.menu.MenuRegistry;
+import com.vaadin.flow.internal.menu.MenuRegistry;
import elemental.json.JsonObject;
import static com.vaadin.flow.server.Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT;
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuConfiguration.java
new file mode 100644
index 00000000000..b51b1f336b8
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuConfiguration.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2024 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.menu;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Locale;
+
+import com.vaadin.flow.internal.menu.MenuRegistry;
+
+/**
+ * Menu configuration helper class to retrieve available menu entries for
+ * application main menu.
+ *
+ * @since 24.5
+ */
+public final class MenuConfiguration {
+
+ /**
+ * Collect ordered list of menu entries for menu population. All client
+ * views are collected and any accessible server views.
+ *
+ * @return ordered list of {@link MenuEntry} instances
+ */
+ public static List getMenuEntries() {
+ return MenuRegistry.collectMenuItemsList().stream()
+ .map(MenuConfiguration::createMenuEntry).toList();
+ }
+
+ /**
+ * Collect ordered list of menu entries for menu population. All client
+ * views are collected and any accessible server views.
+ *
+ * @param locale
+ * locale to use for ordering. null for default locale.
+ *
+ * @return ordered list of {@link MenuEntry} instances
+ */
+ public static List getMenuEntries(Locale locale) {
+ return MenuRegistry.collectMenuItemsList(locale).stream()
+ .map(MenuConfiguration::createMenuEntry).toList();
+ }
+
+ private static MenuEntry createMenuEntry(AvailableViewInfo viewInfo) {
+ if (viewInfo.menu() == null) {
+ return new MenuEntry(viewInfo.route(), viewInfo.title(), null,
+ false, null, null);
+ }
+ return new MenuEntry(viewInfo.route(),
+ (viewInfo.menu().title() != null
+ && !viewInfo.menu().title().isBlank()
+ ? viewInfo.menu().title()
+ : viewInfo.title()),
+ viewInfo.menu().order(), viewInfo.menu().exclude(),
+ viewInfo.menu().icon(), viewInfo.menu().menuClass());
+ }
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuEntry.java b/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuEntry.java
new file mode 100644
index 00000000000..769e1876efa
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/menu/MenuEntry.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2024 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.menu;
+
+import java.io.Serializable;
+
+import com.vaadin.flow.component.Component;
+
+/**
+ * Menu entry for the main menu.
+ *
+ * @param path
+ * the path to navigate to
+ * @param title
+ * the title to display
+ * @param order
+ * the order in the menu or null for default order
+ * @param exclude
+ * whether to exclude the menu entry
+ * @param icon
+ * Icon to use in the menu or null for no icon. Value can go inside a
+ * {@code } element's {@code icon} attribute which
+ * accepts icon group and name like 'vaadin:file'. Or it can go to a
+ * {@code } element's {@code src} attribute which takes
+ * path to the icon. E.g. 'line-awesome/svg/lock-open-solid.svg'.
+ * @param menuClass
+ * the source class with {@link com.vaadin.flow.router.Menu}
+ * annotation or null if not available. Always null for
+ * Hilla/TypeScript client views.
+ */
+public record MenuEntry(String path, String title, Double order,
+ boolean exclude, String icon, Class extends Component> menuClass) implements Serializable {
+}
diff --git a/flow-server/src/test/java/com/vaadin/flow/component/internal/JavaScriptBootstrapUITest.java b/flow-server/src/test/java/com/vaadin/flow/component/internal/JavaScriptBootstrapUITest.java
index e12db65a0fa..731fc5eb23e 100644
--- a/flow-server/src/test/java/com/vaadin/flow/component/internal/JavaScriptBootstrapUITest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/component/internal/JavaScriptBootstrapUITest.java
@@ -46,8 +46,7 @@
import com.vaadin.flow.server.MockServletServiceSessionSetup;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
-import com.vaadin.flow.server.VaadinSessionState;
-import com.vaadin.flow.server.menu.MenuRegistry;
+import com.vaadin.flow.internal.menu.MenuRegistry;
import com.vaadin.tests.util.MockDeploymentConfiguration;
public class JavaScriptBootstrapUITest {
diff --git a/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java b/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java
index 7e4313cb690..c1280a11280 100644
--- a/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java
@@ -34,7 +34,7 @@
import com.vaadin.flow.router.internal.ResolveRequest;
import com.vaadin.flow.server.InvalidRouteConfigurationException;
import com.vaadin.flow.server.RouteRegistry;
-import com.vaadin.flow.server.menu.MenuRegistry;
+import com.vaadin.flow.internal.menu.MenuRegistry;
public class DefaultRouteResolverTest extends RoutingTestBase {
diff --git a/flow-server/src/test/java/com/vaadin/flow/router/internal/NavigationStateRendererTest.java b/flow-server/src/test/java/com/vaadin/flow/router/internal/NavigationStateRendererTest.java
index 96a615920f1..2949215f047 100644
--- a/flow-server/src/test/java/com/vaadin/flow/router/internal/NavigationStateRendererTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/router/internal/NavigationStateRendererTest.java
@@ -27,7 +27,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
-import com.fasterxml.jackson.annotation.JsonProperty;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.SyntheticState;
import net.bytebuddy.description.modifier.Visibility;
@@ -81,8 +80,7 @@
import com.vaadin.flow.server.ServiceException;
import com.vaadin.flow.server.WrappedSession;
import com.vaadin.flow.server.menu.AvailableViewInfo;
-import com.vaadin.flow.server.menu.MenuRegistry;
-import com.vaadin.flow.server.menu.RouteParamType;
+import com.vaadin.flow.internal.menu.MenuRegistry;
import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
import com.vaadin.tests.util.AlwaysLockedVaadinSession;
import com.vaadin.tests.util.MockDeploymentConfiguration;
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/menu/MenuConfigurationTest.java b/flow-server/src/test/java/com/vaadin/flow/server/menu/MenuConfigurationTest.java
new file mode 100644
index 00000000000..e1f2d5fe3e8
--- /dev/null
+++ b/flow-server/src/test/java/com/vaadin/flow/server/menu/MenuConfigurationTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2000-2024 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server.menu;
+
+import jakarta.servlet.ServletContext;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import net.jcip.annotations.NotThreadSafe;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import com.vaadin.flow.di.DefaultInstantiator;
+import com.vaadin.flow.function.DeploymentConfiguration;
+import com.vaadin.flow.internal.CurrentInstance;
+import com.vaadin.flow.router.RouteConfiguration;
+import com.vaadin.flow.server.MockServletContext;
+import com.vaadin.flow.server.MockVaadinContext;
+import com.vaadin.flow.server.MockVaadinSession;
+import com.vaadin.flow.server.VaadinRequest;
+import com.vaadin.flow.server.VaadinService;
+import com.vaadin.flow.server.VaadinServletContext;
+import com.vaadin.flow.server.VaadinSession;
+import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
+
+import static com.vaadin.flow.server.frontend.FrontendUtils.GENERATED;
+import static com.vaadin.flow.internal.menu.MenuRegistry.FILE_ROUTES_JSON_NAME;
+
+@NotThreadSafe
+public class MenuConfigurationTest {
+
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
+
+ private ApplicationRouteRegistry registry;
+ @Mock
+ private MenuRegistryTest.MockService vaadinService;
+ private VaadinSession session;
+ private ServletContext servletContext;
+ private VaadinServletContext vaadinContext;
+ @Mock
+ private DeploymentConfiguration deploymentConfiguration;
+ @Mock
+ private VaadinRequest request;
+
+ private AutoCloseable closeable;
+
+ @Before
+ public void init() {
+ closeable = MockitoAnnotations.openMocks(this);
+ servletContext = new MockServletContext();
+ vaadinContext = new MockVaadinContext(servletContext);
+
+ registry = ApplicationRouteRegistry.getInstance(vaadinContext);
+
+ Mockito.when(vaadinService.getRouteRegistry()).thenReturn(registry);
+ Mockito.when(vaadinService.getContext()).thenReturn(vaadinContext);
+ Mockito.when(vaadinService.getInstantiator())
+ .thenReturn(new DefaultInstantiator(vaadinService));
+
+ Mockito.when(vaadinService.getDeploymentConfiguration())
+ .thenReturn(deploymentConfiguration);
+
+ Mockito.when(deploymentConfiguration.getFrontendFolder())
+ .thenReturn(tmpDir.getRoot());
+
+ VaadinService.setCurrent(vaadinService);
+
+ session = new MockVaadinSession(vaadinService) {
+ @Override
+ public VaadinService getService() {
+ return vaadinService;
+ }
+ };
+
+ VaadinSession.setCurrent(session);
+
+ Mockito.when(request.getService()).thenReturn(vaadinService);
+ CurrentInstance.set(VaadinRequest.class, request);
+ }
+
+ @After
+ public void cleanup() throws Exception {
+ closeable.close();
+ CurrentInstance.clearAll();
+ }
+
+ @Test
+ public void testWithLoggedInUser_userHasRoles() throws IOException {
+ Mockito.when(request.getUserPrincipal())
+ .thenReturn(Mockito.mock(Principal.class));
+ Mockito.when(request.isUserInRole(Mockito.anyString()))
+ .thenReturn(true);
+
+ File generated = tmpDir.newFolder(GENERATED);
+ File clientFiles = new File(generated, FILE_ROUTES_JSON_NAME);
+ Files.writeString(clientFiles.toPath(),
+ MenuRegistryTest.testClientRouteFile);
+
+ List menuEntries = MenuConfiguration.getMenuEntries();
+ Assert.assertEquals(
+ "List of menu items has incorrect size. Excluded menu item like /login is not expected.",
+ 4, menuEntries.size());
+ assertOrder(menuEntries,
+ new String[] { "", "/about", "/hilla", "/hilla/sub" });
+ }
+
+ @Test
+ public void getMenuItemsList_returnsCorrectPaths() throws IOException {
+ File generated = tmpDir.newFolder(GENERATED);
+ File clientFiles = new File(generated, FILE_ROUTES_JSON_NAME);
+ Files.writeString(clientFiles.toPath(),
+ MenuRegistryTest.testClientRouteFile);
+
+ RouteConfiguration routeConfiguration = RouteConfiguration
+ .forRegistry(registry);
+ Arrays.asList(MenuRegistryTest.MyRoute.class,
+ MenuRegistryTest.MyInfo.class,
+ MenuRegistryTest.MyRequiredParamRoute.class,
+ MenuRegistryTest.MyRequiredAndOptionalParamRoute.class,
+ MenuRegistryTest.MyOptionalParamRoute.class,
+ MenuRegistryTest.MyVarargsParamRoute.class)
+ .forEach(routeConfiguration::setAnnotatedRoute);
+
+ List menuEntries = MenuConfiguration.getMenuEntries();
+ Assert.assertEquals(5, menuEntries.size());
+ assertOrder(menuEntries, new String[] { "", "/home", "/info", "/param",
+ "/param/varargs" });
+
+ Map mapMenuItems = menuEntries.stream()
+ .collect(Collectors.toMap(MenuEntry::path, item -> item));
+ assertClientRoutes(mapMenuItems, false, false, true);
+ assertServerRoutes(mapMenuItems);
+ assertServerRoutesWithParameters(mapMenuItems, true);
+ }
+
+ @Test
+ public void getMenuItemsList_assertOrder() {
+ RouteConfiguration routeConfiguration = RouteConfiguration
+ .forRegistry(registry);
+ Arrays.asList(MenuRegistryTest.TestRouteA.class,
+ MenuRegistryTest.TestRouteB.class,
+ MenuRegistryTest.TestRouteC.class,
+ MenuRegistryTest.TestRouteD.class,
+ MenuRegistryTest.TestRouteDA.class,
+ MenuRegistryTest.TestRouteDB.class)
+ .forEach(routeConfiguration::setAnnotatedRoute);
+
+ List menuEntries = MenuConfiguration.getMenuEntries();
+ ;
+ Assert.assertEquals(4, menuEntries.size());
+ assertOrder(menuEntries,
+ new String[] { "/d", "/c", "/a", "/b", "/d/a", "/d/b" });
+ }
+
+ private void assertOrder(List menuEntries,
+ String[] expectedOrder) {
+ for (int i = 0; i < menuEntries.size(); i++) {
+ Assert.assertEquals(expectedOrder[i], menuEntries.get(i).path());
+ }
+ }
+
+ private void assertClientRoutes(Map menuOptions,
+ boolean authenticated, boolean hasRole, boolean excludeExpected) {
+ Assert.assertTrue("Client route '' missing",
+ menuOptions.containsKey(""));
+ Assert.assertEquals("Public", menuOptions.get("").title());
+
+ if (authenticated) {
+ Assert.assertTrue("Client route 'about' missing",
+ menuOptions.containsKey("/about"));
+ Assert.assertEquals("About", menuOptions.get("/about").title());
+
+ if (hasRole) {
+ Assert.assertTrue("Client route 'hilla' missing",
+ menuOptions.containsKey("/hilla"));
+ Assert.assertEquals("Hilla", menuOptions.get("/hilla").title());
+
+ Assert.assertTrue("Client child route 'hilla/sub' missing",
+ menuOptions.containsKey("/hilla/sub"));
+ Assert.assertEquals("Hilla Sub",
+ menuOptions.get("/hilla/sub").title());
+ } else {
+ Assert.assertFalse(
+ "Roles do not match no hilla should be available",
+ menuOptions.containsKey("/hilla"));
+ }
+ } else {
+ Assert.assertFalse(
+ "Not authenticated about view should not be available",
+ menuOptions.containsKey("/about"));
+ Assert.assertFalse(
+ "Not authenticated hilla view should not be available",
+ menuOptions.containsKey("/hilla"));
+ }
+
+ if (excludeExpected) {
+ Assert.assertFalse("Client route 'login' should be excluded",
+ menuOptions.containsKey("/login"));
+ } else {
+ Assert.assertTrue("Client route 'login' missing",
+ menuOptions.containsKey("/login"));
+ Assert.assertEquals("Login", menuOptions.get("/login").title());
+ Assert.assertNull(menuOptions.get("/login").title());
+ Assert.assertTrue("Login view should be excluded",
+ menuOptions.get("/login").exclude());
+ }
+ }
+
+ private void assertServerRoutes(Map menuItems) {
+ Assert.assertTrue("Server route 'home' missing",
+ menuItems.containsKey("/home"));
+ Assert.assertEquals("Home", menuItems.get("/home").title());
+ Assert.assertEquals(MenuRegistryTest.MyRoute.class,
+ menuItems.get("/home").menuClass());
+
+ Assert.assertTrue("Server route 'info' missing",
+ menuItems.containsKey("/info"));
+ Assert.assertEquals("MyInfo", menuItems.get("/info").title());
+ Assert.assertEquals(MenuRegistryTest.MyInfo.class,
+ menuItems.get("/info").menuClass());
+ }
+
+ private void assertServerRoutesWithParameters(
+ Map menuItems, boolean excludeExpected) {
+ if (excludeExpected) {
+ Assert.assertFalse(
+ "Server route '/param/:param' should be excluded",
+ menuItems.containsKey("/param/:param"));
+ Assert.assertFalse(
+ "Server route '/param/:param1' should be excluded",
+ menuItems.containsKey("/param/:param1"));
+ } else {
+ Assert.assertTrue("Server route '/param/:param' missing",
+ menuItems.containsKey("/param/:param"));
+ Assert.assertTrue(
+ "Server route '/param/:param' should be excluded from menu",
+ menuItems.get("/param/:param").exclude());
+
+ Assert.assertTrue("Server route '/param/:param1' missing",
+ menuItems.containsKey("/param/:param1"));
+ Assert.assertTrue(
+ "Server route '/param/:param1' should be excluded from menu",
+ menuItems.get("/param/:param1").exclude());
+ }
+
+ Assert.assertTrue(
+ "Server route with optional parameters '/param' missing",
+ menuItems.containsKey("/param"));
+ Assert.assertFalse(
+ "Server route '/param' should be included in the menu",
+ menuItems.get("/param").exclude());
+
+ Assert.assertTrue(
+ "Server route with optional parameters '/param/varargs' missing",
+ menuItems.containsKey("/param/varargs"));
+ Assert.assertFalse(
+ "Server route '/param/varargs' should be included in the menu",
+ menuItems.get("/param/varargs").exclude());
+ }
+}
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/menu/MenuRegistryTest.java b/flow-server/src/test/java/com/vaadin/flow/server/menu/MenuRegistryTest.java
index b6f080aedc5..36078d7b620 100644
--- a/flow-server/src/test/java/com/vaadin/flow/server/menu/MenuRegistryTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/server/menu/MenuRegistryTest.java
@@ -44,6 +44,7 @@
import com.vaadin.flow.di.Instantiator;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.CurrentInstance;
+import com.vaadin.flow.internal.menu.MenuRegistry;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteConfiguration;
@@ -59,8 +60,8 @@
import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
import static com.vaadin.flow.server.frontend.FrontendUtils.GENERATED;
-import static com.vaadin.flow.server.menu.MenuRegistry.FILE_ROUTES_JSON_NAME;
-import static com.vaadin.flow.server.menu.MenuRegistry.FILE_ROUTES_JSON_PROD_PATH;
+import static com.vaadin.flow.internal.menu.MenuRegistry.FILE_ROUTES_JSON_NAME;
+import static com.vaadin.flow.internal.menu.MenuRegistry.FILE_ROUTES_JSON_PROD_PATH;
@NotThreadSafe
public class MenuRegistryTest {
@@ -457,78 +458,78 @@ private void assertServerRoutesWithParameters(
@Tag("div")
@Route("home")
@Menu(title = "Home")
- private static class MyRoute extends Component {
+ public static class MyRoute extends Component {
}
@Tag("div")
@Route("info")
@Menu
- private static class MyInfo extends Component {
+ public static class MyInfo extends Component {
}
@Tag("div")
@Route("param/:param")
@Menu
- private static class MyRequiredParamRoute extends Component {
+ public static class MyRequiredParamRoute extends Component {
}
@Tag("div")
@Route("param/:param1/:param2?")
@Menu
- private static class MyRequiredAndOptionalParamRoute extends Component {
+ public static class MyRequiredAndOptionalParamRoute extends Component {
}
@Tag("div")
@Route("param/:param1?/:param2?(edit)")
@Menu
- private static class MyOptionalParamRoute extends Component {
+ public static class MyOptionalParamRoute extends Component {
}
@Tag("div")
@Route("param/varargs/:param*")
@Menu
- private static class MyVarargsParamRoute extends Component {
+ public static class MyVarargsParamRoute extends Component {
}
@Tag("div")
@Route("a")
@Menu(order = 1.1)
- private static class TestRouteA extends Component {
+ public static class TestRouteA extends Component {
}
@Tag("div")
@Route("b")
@Menu(order = 1.2)
- private static class TestRouteB extends Component {
+ public static class TestRouteB extends Component {
}
@Tag("div")
@Route("c")
@Menu(order = 0.1)
- private static class TestRouteC extends Component {
+ public static class TestRouteC extends Component {
}
@Tag("div")
@Route("d")
@Menu(order = 0)
- private static class TestRouteD extends Component {
+ public static class TestRouteD extends Component {
}
@Tag("div")
@Route("d/b")
- private static class TestRouteDB extends Component {
+ public static class TestRouteDB extends Component {
}
@Tag("div")
@Route("d/a")
- private static class TestRouteDA extends Component {
+ public static class TestRouteDA extends Component {
}
/**
* Extending class to let us mock the getRouteRegistry method for testing.
*/
- private static class MockService extends VaadinServletService {
+ public static class MockService extends VaadinServletService {
@Override
public RouteRegistry getRouteRegistry() {
@@ -541,7 +542,7 @@ public Instantiator getInstantiator() {
}
}
- String testClientRouteFile = """
+ public static String testClientRouteFile = """
[
{
"route": "",
diff --git a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
index c4455547c14..46d86951352 100644
--- a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
+++ b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java
@@ -168,7 +168,8 @@ protected Stream getExcludedPatterns() {
"com\\.vaadin\\.flow\\.server\\.communication\\.PushHandler(\\$.*)?",
"com\\.vaadin\\.flow\\.server\\.communication\\.PushRequestHandler(\\$.*)?",
"com\\.vaadin\\.flow\\.server\\.communication\\.JavaScriptBootstrapHandler(\\$.*)?",
- "com\\.vaadin\\.flow\\.server\\.menu\\.MenuRegistry(\\$.*)?",
+ "com\\.vaadin\\.flow\\.internal\\.menu\\.MenuRegistry(\\$.*)?",
+ "com\\.vaadin\\.flow\\.server\\.menu\\.MenuConfiguration(\\$.*)?",
"com\\.vaadin\\.flow\\.templatemodel\\.PathLookup",
"com\\.vaadin\\.flow\\.server\\.startup\\.ErrorNavigationTargetInitializer",
"com\\.vaadin\\.flow\\.server\\.startup\\.RouteRegistryInitializer",