+ * Use Breadcrumb when you want to show the user's current location within a + * hierarchy and provide navigation to parent levels. + *
+ * {@link BreadcrumbItem} components can be added to this component with the
+ * {@link #add(BreadcrumbItem...)} method or the
+ * {@link #Breadcrumb(BreadcrumbItem...)} constructor.
+ *
+ * @author Vaadin Ltd.
+ */
+@Tag("vaadin-breadcrumb")
+@JsModule("@vaadin/breadcrumb/src/vaadin-breadcrumb.js")
+@NpmPackage(value = "@vaadin/breadcrumb", version = "25.0.0-alpha16")
+public class Breadcrumb extends Component
+ implements HasSize, HasStyle, HasThemeVariant
+ * BreadcrumbItem can contain text and/or components. It supports navigation
+ * through href property or by using Router navigation targets.
+ *
+ * @author Vaadin Ltd.
+ */
+@Tag("vaadin-breadcrumb-item")
+@JsModule("@vaadin/breadcrumb/src/vaadin-breadcrumb-item.js")
+@NpmPackage(value = "@vaadin/breadcrumb", version = "25.0.0-alpha16")
+public class BreadcrumbItem extends Component
+ implements HasComponents, HasText, HasStyle, HasEnabled, HasTooltip {
+
+ private static final PropertyDescriptor
+ * The href is the URL that the item links to. Set to an empty string to
+ * remove the href.
+ *
+ * @param href
+ * the href to set, or an empty string to remove
+ */
+ public void setHref(String href) {
+ set(hrefDescriptor, href == null ? "" : href);
+ }
+
+ /**
+ * Gets the target of this item's link.
+ *
+ * @return the target, or an empty string if no target is set
+ */
+ public String getTarget() {
+ return get(targetDescriptor);
+ }
+
+ /**
+ * Sets the target of this item's link.
+ *
+ * The target attribute specifies where to display the linked URL. Common
+ * values are "_blank" to open in a new tab, "_self" to open in the same
+ * frame, "_parent" to open in the parent frame, or "_top" to open in the
+ * full window.
+ *
+ * @param target
+ * the target to set, or an empty string to remove
+ */
+ public void setTarget(String target) {
+ set(targetDescriptor, target == null ? "" : target);
+ }
+
+ /**
+ * Gets whether this item should be ignored by client-side routers.
+ *
+ * @return {@code true} if router should ignore this item, {@code false}
+ * otherwise
+ */
+ public boolean isRouterIgnore() {
+ return get(routerIgnoreDescriptor);
+ }
+
+ /**
+ * Sets whether this item should be ignored by client-side routers.
+ *
+ * When set to {@code true}, clicking this item will cause a full page
+ * reload instead of client-side navigation.
+ *
+ * @param routerIgnore
+ * {@code true} to ignore client-side routing, {@code false}
+ * otherwise
+ */
+ public void setRouterIgnore(boolean routerIgnore) {
+ set(routerIgnoreDescriptor, routerIgnore);
+ }
+
+ /**
+ * Gets whether this item represents the current page.
+ *
+ * This property is automatically updated based on the current URL.
+ *
+ * @return {@code true} if this item represents the current page,
+ * {@code false} otherwise
+ */
+ @Synchronize(property = "current", value = "current-changed")
+ public boolean isCurrent() {
+ return getElement().getProperty("current", false);
+ }
+
+ /**
+ * Sets the navigation target for this item using a router class.
+ *
+ * @param navigationTarget
+ * the navigation target class
+ */
+ public void setRoute(Class extends Component> navigationTarget) {
+ setRoute(navigationTarget, RouteParameters.empty());
+ }
+
+ /**
+ * Sets the navigation target for this item using a router class and route
+ * parameters.
+ *
+ * @param navigationTarget
+ * the navigation target class
+ * @param routeParameters
+ * the route parameters
+ */
+ public void setRoute(Class extends Component> navigationTarget,
+ RouteParameters routeParameters) {
+ setRoute(getRouter(), navigationTarget, routeParameters,
+ QueryParameters.empty());
+ }
+
+ /**
+ * Sets the navigation target for this item using a router class, route
+ * parameters, and query parameters.
+ *
+ * @param navigationTarget
+ * the navigation target class
+ * @param routeParameters
+ * the route parameters
+ * @param queryParameters
+ * the query parameters
+ */
+ public void setRoute(Class extends Component> navigationTarget,
+ RouteParameters routeParameters, QueryParameters queryParameters) {
+ setRoute(getRouter(), navigationTarget, routeParameters,
+ queryParameters);
+ }
+
+ /**
+ * Sets the navigation target for this item using a specific router.
+ *
+ * @param router
+ * the router to use, or {@code null} to use the default
+ * @param navigationTarget
+ * the navigation target class
+ * @param routeParameters
+ * the route parameters
+ * @param queryParameters
+ * the query parameters
+ */
+ public void setRoute(Router router,
+ Class extends Component> navigationTarget,
+ RouteParameters routeParameters, QueryParameters queryParameters) {
+ if (router == null) {
+ router = getRouter();
+ }
+
+ if (navigationTarget != null) {
+ String url = RouteConfiguration.forRegistry(router.getRegistry())
+ .getUrl(navigationTarget, routeParameters);
+
+ if (!queryParameters.getParameters().isEmpty()) {
+ url = url + "?" + queryParameters.getQueryString();
+ }
+
+ setHref(url);
+ } else {
+ setHref("");
+ }
+ }
+
+ private Router getRouter() {
+ Router router = null;
+ if (getElement().getNode().isAttached()) {
+ router = getUI().map(ui -> ui.getInternals().getRouter())
+ .orElse(null);
+ }
+ if (router == null) {
+ router = VaadinService.getCurrent().getRouter();
+ }
+ if (router == null) {
+ throw new IllegalStateException(
+ "Cannot find a router to use for navigation");
+ }
+ return router;
+ }
+}
diff --git a/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/main/java/com/vaadin/flow/component/breadcrumb/BreadcrumbVariant.java b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/main/java/com/vaadin/flow/component/breadcrumb/BreadcrumbVariant.java
new file mode 100644
index 00000000000..f480edb72f1
--- /dev/null
+++ b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/main/java/com/vaadin/flow/component/breadcrumb/BreadcrumbVariant.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2025 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.component.breadcrumb;
+
+import com.vaadin.flow.component.shared.ThemeVariant;
+
+/**
+ * Theme variants for the {@link Breadcrumb} component.
+ *
+ * @author Vaadin Ltd.
+ */
+public enum BreadcrumbVariant implements ThemeVariant {
+
+ /**
+ * Small size variant.
+ */
+ LUMO_SMALL("small"),
+
+ /**
+ * Large size variant.
+ */
+ LUMO_LARGE("large");
+
+ private final String variant;
+
+ BreadcrumbVariant(String variant) {
+ this.variant = variant;
+ }
+
+ /**
+ * Gets the variant name.
+ *
+ * @return variant name
+ */
+ @Override
+ public String getVariantName() {
+ return variant;
+ }
+}
diff --git a/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/test/java/com/vaadin/flow/component/breadcrumb/BreadcrumbItemTest.java b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/test/java/com/vaadin/flow/component/breadcrumb/BreadcrumbItemTest.java
new file mode 100644
index 00000000000..74c89e0e015
--- /dev/null
+++ b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/test/java/com/vaadin/flow/component/breadcrumb/BreadcrumbItemTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2000-2025 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.component.breadcrumb;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.router.RouteParameters;
+
+/**
+ * Tests for {@link BreadcrumbItem}.
+ */
+public class BreadcrumbItemTest {
+
+ @Test
+ public void createEmptyItem() {
+ BreadcrumbItem item = new BreadcrumbItem();
+
+ Assert.assertEquals("Tag name is invalid", "vaadin-breadcrumb-item",
+ item.getElement().getTag());
+ Assert.assertEquals("Text should be empty", "", item.getText());
+ Assert.assertEquals("Href should be empty", "", item.getHref());
+ }
+
+ @Test
+ public void createItemWithText() {
+ BreadcrumbItem item = new BreadcrumbItem("Home");
+
+ Assert.assertEquals("Text is invalid", "Home", item.getText());
+ Assert.assertEquals("Href should be empty", "", item.getHref());
+ }
+
+ @Test
+ public void createItemWithTextAndHref() {
+ BreadcrumbItem item = new BreadcrumbItem("Home", "/home");
+
+ Assert.assertEquals("Text is invalid", "Home", item.getText());
+ Assert.assertEquals("Href is invalid", "/home", item.getHref());
+ }
+
+ @Test
+ public void createItemWithComponent() {
+ Div div = new Div("Content");
+ BreadcrumbItem item = new BreadcrumbItem(div);
+
+ Assert.assertEquals("Child count is invalid", 1,
+ item.getElement().getChildCount());
+ Assert.assertEquals("Child component is invalid", div.getElement(),
+ item.getElement().getChild(0));
+ }
+
+ @Test
+ public void createItemWithComponentAndHref() {
+ Div div = new Div("Content");
+ BreadcrumbItem item = new BreadcrumbItem(div, "/home");
+
+ Assert.assertEquals("Child count is invalid", 1,
+ item.getElement().getChildCount());
+ Assert.assertEquals("Href is invalid", "/home", item.getHref());
+ }
+
+ @Test
+ public void setHref() {
+ BreadcrumbItem item = new BreadcrumbItem();
+
+ item.setHref("/products");
+ Assert.assertEquals("Href is invalid", "/products", item.getHref());
+
+ item.setHref(null);
+ Assert.assertEquals("Href should be empty", "", item.getHref());
+
+ item.setHref("");
+ Assert.assertEquals("Href should be empty", "", item.getHref());
+ }
+
+ @Test
+ public void setTarget() {
+ BreadcrumbItem item = new BreadcrumbItem();
+
+ item.setTarget("_blank");
+ Assert.assertEquals("Target is invalid", "_blank", item.getTarget());
+
+ item.setTarget(null);
+ Assert.assertEquals("Target should be empty", "", item.getTarget());
+
+ item.setTarget("");
+ Assert.assertEquals("Target should be empty", "", item.getTarget());
+ }
+
+ @Test
+ public void setRouterIgnore() {
+ BreadcrumbItem item = new BreadcrumbItem();
+
+ Assert.assertFalse("Default routerIgnore should be false",
+ item.isRouterIgnore());
+
+ item.setRouterIgnore(true);
+ Assert.assertTrue("RouterIgnore should be true", item.isRouterIgnore());
+
+ item.setRouterIgnore(false);
+ Assert.assertFalse("RouterIgnore should be false",
+ item.isRouterIgnore());
+ }
+
+ @Test
+ public void setText() {
+ BreadcrumbItem item = new BreadcrumbItem();
+
+ item.setText("Products");
+ Assert.assertEquals("Text is invalid", "Products", item.getText());
+
+ item.setText(null);
+ Assert.assertEquals("Text should be empty", "", item.getText());
+ }
+
+ @Test
+ public void addComponent() {
+ BreadcrumbItem item = new BreadcrumbItem();
+ Div div1 = new Div("First");
+ Div div2 = new Div("Second");
+
+ item.add(div1, div2);
+
+ Assert.assertEquals("Child count is invalid", 2,
+ item.getElement().getChildCount());
+ Assert.assertEquals("First child is invalid", div1.getElement(),
+ item.getElement().getChild(0));
+ Assert.assertEquals("Second child is invalid", div2.getElement(),
+ item.getElement().getChild(1));
+ }
+
+ @Test
+ public void removeComponent() {
+ BreadcrumbItem item = new BreadcrumbItem();
+ Div div1 = new Div("First");
+ Div div2 = new Div("Second");
+
+ item.add(div1, div2);
+ item.remove(div1);
+
+ Assert.assertEquals("Child count is invalid", 1,
+ item.getElement().getChildCount());
+ Assert.assertEquals("Remaining child is invalid", div2.getElement(),
+ item.getElement().getChild(0));
+ }
+
+ @Test
+ public void removeAllComponents() {
+ BreadcrumbItem item = new BreadcrumbItem();
+ Div div1 = new Div("First");
+ Div div2 = new Div("Second");
+
+ item.add(div1, div2);
+ item.removeAll();
+
+ Assert.assertEquals("Child count should be 0", 0,
+ item.getElement().getChildCount());
+ }
+
+ @Test
+ public void setEnabled() {
+ BreadcrumbItem item = new BreadcrumbItem();
+
+ Assert.assertTrue("Default enabled should be true", item.isEnabled());
+
+ item.setEnabled(false);
+ Assert.assertFalse("Enabled should be false", item.isEnabled());
+
+ item.setEnabled(true);
+ Assert.assertTrue("Enabled should be true", item.isEnabled());
+ }
+
+ @Test
+ public void setTooltip() {
+ BreadcrumbItem item = new BreadcrumbItem();
+
+ // HasTooltip creates a tooltip immediately, so we check if text is
+ // null/empty
+ Assert.assertTrue("Default tooltip text should be null or empty",
+ item.getTooltip() == null || item.getTooltip().getText() == null
+ || item.getTooltip().getText().isEmpty());
+
+ item.setTooltipText("This is a tooltip");
+ Assert.assertNotNull("Tooltip should not be null", item.getTooltip());
+ Assert.assertEquals("Tooltip text is invalid", "This is a tooltip",
+ item.getTooltip().getText());
+ }
+
+ @Test
+ public void constructorsWithNavigationTarget() {
+ // Test constructor with navigation target - will throw without router
+ try {
+ BreadcrumbItem item1 = new BreadcrumbItem("Test", TestView.class);
+ Assert.fail("Should throw exception without router");
+ } catch (IllegalStateException | NullPointerException e) {
+ // Expected if router configuration is not available
+ // Can be either IllegalStateException or NullPointerException
+ // depending on the state
+ Assert.assertTrue("Expected router exception",
+ e.getMessage() != null
+ && (e.getMessage().contains("Cannot find a router")
+ || e.getMessage().contains(
+ "VaadinService.getCurrent()")));
+ }
+
+ // Test constructor with navigation target and route parameters - will
+ // throw without router
+ try {
+ RouteParameters params = RouteParameters.empty();
+ BreadcrumbItem item2 = new BreadcrumbItem("Test", TestView.class,
+ params);
+ Assert.fail("Should throw exception without router");
+ } catch (IllegalStateException | NullPointerException e) {
+ // Expected if router configuration is not available
+ // Can be either IllegalStateException or NullPointerException
+ // depending on the state
+ Assert.assertTrue("Expected router exception",
+ e.getMessage() != null
+ && (e.getMessage().contains("Cannot find a router")
+ || e.getMessage().contains(
+ "VaadinService.getCurrent()")));
+ }
+ }
+
+ // Test view class for navigation tests
+ private static class TestView extends Div {
+ }
+}
diff --git a/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/test/java/com/vaadin/flow/component/breadcrumb/BreadcrumbTest.java b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/test/java/com/vaadin/flow/component/breadcrumb/BreadcrumbTest.java
new file mode 100644
index 00000000000..09b913f92b1
--- /dev/null
+++ b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-flow/src/test/java/com/vaadin/flow/component/breadcrumb/BreadcrumbTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2000-2025 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.component.breadcrumb;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Tests for {@link Breadcrumb}.
+ */
+public class BreadcrumbTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void createBreadcrumbInDefaultState() {
+ Breadcrumb breadcrumb = new Breadcrumb();
+
+ Assert.assertEquals("Initial item count is invalid", 0,
+ breadcrumb.getItemCount());
+ Assert.assertEquals("Tag name is invalid", "vaadin-breadcrumb",
+ breadcrumb.getElement().getTag());
+ }
+
+ @Test
+ public void createBreadcrumbWithItems() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ BreadcrumbItem item3 = new BreadcrumbItem("Details");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2, item3);
+
+ Assert.assertEquals("Initial item count is invalid", 3,
+ breadcrumb.getItemCount());
+ Assert.assertEquals("First item is invalid", item1,
+ breadcrumb.getItemAt(0));
+ Assert.assertEquals("Second item is invalid", item2,
+ breadcrumb.getItemAt(1));
+ Assert.assertEquals("Third item is invalid", item3,
+ breadcrumb.getItemAt(2));
+ }
+
+ @Test
+ public void addItems() {
+ Breadcrumb breadcrumb = new Breadcrumb();
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+
+ breadcrumb.add(item1, item2);
+
+ Assert.assertEquals("Item count after add is invalid", 2,
+ breadcrumb.getItemCount());
+ Assert.assertEquals("First item is invalid", item1,
+ breadcrumb.getItemAt(0));
+ Assert.assertEquals("Second item is invalid", item2,
+ breadcrumb.getItemAt(1));
+ }
+
+ @Test
+ public void addNullItems_throwsException() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("Items to add cannot be null");
+
+ Breadcrumb breadcrumb = new Breadcrumb();
+ breadcrumb.add((BreadcrumbItem[]) null);
+ }
+
+ @Test
+ public void addItemWithNull_throwsException() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("Individual item to add cannot be null");
+
+ Breadcrumb breadcrumb = new Breadcrumb();
+ breadcrumb.add(new BreadcrumbItem("Home"), null);
+ }
+
+ @Test
+ public void removeItems() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ BreadcrumbItem item3 = new BreadcrumbItem("Details");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2, item3);
+
+ breadcrumb.remove(item2);
+
+ Assert.assertEquals("Item count after remove is invalid", 2,
+ breadcrumb.getItemCount());
+ Assert.assertEquals("First item is invalid", item1,
+ breadcrumb.getItemAt(0));
+ Assert.assertEquals("Second item is invalid", item3,
+ breadcrumb.getItemAt(1));
+ }
+
+ @Test
+ public void removeAll() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2);
+
+ breadcrumb.removeAll();
+
+ Assert.assertEquals("Item count after removeAll is invalid", 0,
+ breadcrumb.getItemCount());
+ }
+
+ @Test
+ public void getItemAt_invalidIndex_throwsException() {
+ thrown.expect(IndexOutOfBoundsException.class);
+ thrown.expectMessage("Index: 0, Size: 0");
+
+ Breadcrumb breadcrumb = new Breadcrumb();
+ breadcrumb.getItemAt(0);
+ }
+
+ @Test
+ public void getItemAt_negativeIndex_throwsException() {
+ thrown.expect(IndexOutOfBoundsException.class);
+ thrown.expectMessage("Index: -1, Size: 0");
+
+ Breadcrumb breadcrumb = new Breadcrumb();
+ breadcrumb.getItemAt(-1);
+ }
+
+ @Test
+ public void indexOf_returnsCorrectIndex() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ BreadcrumbItem item3 = new BreadcrumbItem("Details");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2, item3);
+
+ Assert.assertEquals("Index of item1 is invalid", 0,
+ breadcrumb.indexOf(item1));
+ Assert.assertEquals("Index of item2 is invalid", 1,
+ breadcrumb.indexOf(item2));
+ Assert.assertEquals("Index of item3 is invalid", 2,
+ breadcrumb.indexOf(item3));
+ }
+
+ @Test
+ public void indexOf_itemNotFound_returnsNegativeOne() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ BreadcrumbItem itemNotAdded = new BreadcrumbItem("Details");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2);
+
+ Assert.assertEquals("Index of not added item should be -1", -1,
+ breadcrumb.indexOf(itemNotAdded));
+ }
+
+ @Test
+ public void indexOf_nullItem_returnsNegativeOne() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ Breadcrumb breadcrumb = new Breadcrumb(item1);
+
+ Assert.assertEquals("Index of null should be -1", -1,
+ breadcrumb.indexOf(null));
+ }
+
+ @Test
+ public void replace_validIndex() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ BreadcrumbItem item3 = new BreadcrumbItem("Details");
+ BreadcrumbItem newItem = new BreadcrumbItem("Categories");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2, item3);
+
+ breadcrumb.replace(1, newItem);
+
+ Assert.assertEquals("Item count after replace is invalid", 3,
+ breadcrumb.getItemCount());
+ Assert.assertEquals("First item is invalid", item1,
+ breadcrumb.getItemAt(0));
+ Assert.assertEquals("Replaced item is invalid", newItem,
+ breadcrumb.getItemAt(1));
+ Assert.assertEquals("Third item is invalid", item3,
+ breadcrumb.getItemAt(2));
+ }
+
+ @Test
+ public void replace_invalidIndex_throwsException() {
+ thrown.expect(IndexOutOfBoundsException.class);
+ thrown.expectMessage("Index: 3, Size: 2");
+
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ BreadcrumbItem newItem = new BreadcrumbItem("Categories");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2);
+
+ breadcrumb.replace(3, newItem);
+ }
+
+ @Test
+ public void replace_nullItem_throwsException() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("New item cannot be null");
+
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ Breadcrumb breadcrumb = new Breadcrumb(item1);
+
+ breadcrumb.replace(0, null);
+ }
+
+ @Test
+ public void getItems_returnsAllItems() {
+ BreadcrumbItem item1 = new BreadcrumbItem("Home");
+ BreadcrumbItem item2 = new BreadcrumbItem("Products");
+ BreadcrumbItem item3 = new BreadcrumbItem("Details");
+ Breadcrumb breadcrumb = new Breadcrumb(item1, item2, item3);
+
+ BreadcrumbItem[] items = breadcrumb.getItems()
+ .toArray(BreadcrumbItem[]::new);
+
+ Assert.assertEquals("Item count is invalid", 3, items.length);
+ Assert.assertEquals("First item is invalid", item1, items[0]);
+ Assert.assertEquals("Second item is invalid", item2, items[1]);
+ Assert.assertEquals("Third item is invalid", item3, items[2]);
+ }
+
+ @Test
+ public void themeVariants() {
+ Breadcrumb breadcrumb = new Breadcrumb();
+
+ breadcrumb.addThemeVariants(BreadcrumbVariant.LUMO_SMALL);
+ Assert.assertTrue("Should have small variant",
+ breadcrumb.getThemeNames().contains("small"));
+
+ breadcrumb.addThemeVariants(BreadcrumbVariant.LUMO_LARGE);
+ Assert.assertTrue("Should have large variant",
+ breadcrumb.getThemeNames().contains("large"));
+
+ breadcrumb.removeThemeVariants(BreadcrumbVariant.LUMO_SMALL);
+ Assert.assertFalse("Should not have small variant",
+ breadcrumb.getThemeNames().contains("small"));
+ }
+}
diff --git a/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-testbench/pom.xml b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-testbench/pom.xml
new file mode 100644
index 00000000000..dc35a31cd09
--- /dev/null
+++ b/vaadin-breadcrumb-flow-parent/vaadin-breadcrumb-testbench/pom.xml
@@ -0,0 +1,45 @@
+
+