diff --git a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/main/java/com/vaadin/flow/component/dashboard/tests/DashboardPage.java b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/main/java/com/vaadin/flow/component/dashboard/tests/DashboardPage.java index c18f2554312..12a98e075e3 100644 --- a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/main/java/com/vaadin/flow/component/dashboard/tests/DashboardPage.java +++ b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/main/java/com/vaadin/flow/component/dashboard/tests/DashboardPage.java @@ -8,7 +8,12 @@ */ package com.vaadin.flow.component.dashboard.tests; +import java.util.List; + +import com.vaadin.flow.component.dashboard.Dashboard; +import com.vaadin.flow.component.dashboard.DashboardWidget; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.NativeButton; import com.vaadin.flow.router.Route; /** @@ -16,4 +21,55 @@ */ @Route("vaadin-dashboard") public class DashboardPage extends Div { + + public DashboardPage() { + DashboardWidget widget1 = new DashboardWidget(); + widget1.setTitle("Widget 1"); + widget1.setId("widget-1"); + + DashboardWidget widget2 = new DashboardWidget(); + widget2.setTitle("Widget 2"); + widget2.setId("widget-2"); + + DashboardWidget widget3 = new DashboardWidget(); + widget3.setTitle("Widget 3"); + widget3.setId("widget-3"); + + Dashboard dashboard = new Dashboard(); + dashboard.add(widget1, widget2, widget3); + + NativeButton addWidgetAtIndex1 = new NativeButton( + "Add widget at index 1"); + addWidgetAtIndex1.addClickListener(click -> { + DashboardWidget widgetAtIndex1 = new DashboardWidget(); + widgetAtIndex1.setTitle("Widget at index 1"); + widgetAtIndex1.setId("widget-at-index-1"); + dashboard.addWidgetAtIndex(1, widgetAtIndex1); + }); + addWidgetAtIndex1.setId("add-widget-at-index-1"); + + NativeButton removeFirstAndLastWidgets = new NativeButton( + "Remove first and last widgets"); + removeFirstAndLastWidgets.addClickListener(click -> { + List currentWidgets = dashboard.getWidgets(); + if (currentWidgets.isEmpty()) { + return; + } + int currentWidgetCount = currentWidgets.size(); + if (currentWidgetCount == 1) { + dashboard.remove(dashboard.getWidgets().get(0)); + } else { + dashboard.remove(dashboard.getWidgets().get(0), + dashboard.getWidgets().get(currentWidgetCount - 1)); + } + }); + removeFirstAndLastWidgets.setId("remove-first-and-last-widgets"); + + NativeButton removeAllWidgets = new NativeButton("Remove all widgets"); + removeAllWidgets.addClickListener(click -> dashboard.removeAll()); + removeAllWidgets.setId("remove-all-widgets"); + + add(addWidgetAtIndex1, removeFirstAndLastWidgets, removeAllWidgets, + dashboard); + } } diff --git a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardIT.java b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardIT.java index d97c9463a62..68c20102e7b 100644 --- a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardIT.java +++ b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow-integration-tests/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardIT.java @@ -8,6 +8,15 @@ */ package com.vaadin.flow.component.dashboard.tests; +import java.util.Arrays; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.flow.component.dashboard.testbench.DashboardElement; +import com.vaadin.flow.component.dashboard.testbench.DashboardWidgetElement; import com.vaadin.flow.testutil.TestPath; import com.vaadin.tests.AbstractComponentIT; @@ -16,4 +25,43 @@ */ @TestPath("vaadin-dashboard") public class DashboardIT extends AbstractComponentIT { + + private DashboardElement dashboardElement; + + @Before + public void init() { + open(); + dashboardElement = $(DashboardElement.class).waitForFirst(); + } + + @Test + public void addWidgets_widgetsAreCorrectlyAdded() { + assertWidgetsByTitle("Widget 1", "Widget 2", "Widget 3"); + } + + @Test + public void addWidgetsAtIndex1_widgetIsAddedIntoTheCorrectPlace() { + clickElementWithJs("add-widget-at-index-1"); + assertWidgetsByTitle("Widget 1", "Widget at index 1", "Widget 2", + "Widget 3"); + } + + @Test + public void removeFirstAndLastWidgets_widgetsAreCorrectlyRemoved() { + clickElementWithJs("remove-first-and-last-widgets"); + assertWidgetsByTitle("Widget 2"); + } + + @Test + public void removeAllWidgets_widgetsAreCorrectlyRemoved() { + clickElementWithJs("remove-all-widgets"); + assertWidgetsByTitle(); + } + + private void assertWidgetsByTitle(String... expectedWidgetTitles) { + List widgets = dashboardElement.getWidgets(); + List widgetTitles = widgets.stream() + .map(DashboardWidgetElement::getTitle).toList(); + Assert.assertEquals(Arrays.asList(expectedWidgetTitles), widgetTitles); + } } diff --git a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/pom.xml b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/pom.xml index aac38f7d4d4..54f333ae89e 100644 --- a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/pom.xml +++ b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/pom.xml @@ -54,6 +54,11 @@ com.vaadin flow-html-components + + com.vaadin + vaadin-renderer-flow + ${project.version} + diff --git a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/main/java/com/vaadin/flow/component/dashboard/Dashboard.java b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/main/java/com/vaadin/flow/component/dashboard/Dashboard.java index 9ac14d04e14..b7526b27ba3 100644 --- a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/main/java/com/vaadin/flow/component/dashboard/Dashboard.java +++ b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/main/java/com/vaadin/flow/component/dashboard/Dashboard.java @@ -8,10 +8,30 @@ */ package com.vaadin.flow.component.dashboard; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.component.AttachEvent; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.dependency.NpmPackage; +import com.vaadin.flow.dom.Element; +import com.vaadin.flow.dom.ElementDetachEvent; +import com.vaadin.flow.dom.ElementDetachListener; +import com.vaadin.flow.shared.Registration; + +import elemental.json.Json; +import elemental.json.JsonArray; +import elemental.json.JsonObject; /** * @author Vaadin Ltd @@ -20,6 +40,210 @@ @NpmPackage(value = "@vaadin/polymer-legacy-adapter", version = "24.5.0-alpha8") @JsModule("@vaadin/polymer-legacy-adapter/style-modules.js") @JsModule("@vaadin/dashboard/src/vaadin-dashboard.js") +@JsModule("./flow-component-renderer.js") // @NpmPackage(value = "@vaadin/dashboard", version = "24.6.0-alpha0") public class Dashboard extends Component { + + private final List widgets = new ArrayList<>(); + + private boolean pendingUpdate = false; + + /** + * Creates an empty dashboard. + */ + public Dashboard() { + } + + /** + * Returns the widgets in the dashboard. + * + * @return The widgets in the dashboard + */ + public List getWidgets() { + return Collections.unmodifiableList(widgets); + } + + /** + * Adds the given widgets to the dashboard. + * + * @param widgets + * the widgets to add, not {@code null} + */ + public void add(DashboardWidget... widgets) { + Objects.requireNonNull(widgets, "Widgets to add cannot be null."); + List toAdd = new ArrayList<>(widgets.length); + for (DashboardWidget widget : widgets) { + Objects.requireNonNull(widget, "Widget to add cannot be null."); + toAdd.add(widget); + } + toAdd.forEach(this::doAddWidget); + updateClient(); + } + + /** + * Adds the given widget as child of this dashboard at the specific index. + *

+ * In case the specified widget has already been added to another parent, it + * will be removed from there and added to this one. + * + * @param index + * the index, where the widget will be added. The index must be + * non-negative and may not exceed the children count + * @param widget + * the widget to add, not {@code null} + */ + public void addWidgetAtIndex(int index, DashboardWidget widget) { + Objects.requireNonNull(widget, "Widget to add cannot be null."); + if (index < 0) { + throw new IllegalArgumentException( + "Cannot add a widget with a negative index."); + } + // The case when the index is bigger than the children count is handled + // inside the method below + doAddWidget(index, widget); + updateClient(); + } + + /** + * Removes the given widgets from this dashboard. + * + * @param widgets + * the widgets to remove, not {@code null} + * @throws IllegalArgumentException + * if there is a widget whose non {@code null} parent is not + * this dashboard + */ + public void remove(DashboardWidget... widgets) { + Objects.requireNonNull(widgets, "Widgets to remove cannot be null."); + List toRemove = new ArrayList<>(widgets.length); + for (DashboardWidget widget : widgets) { + Objects.requireNonNull(widget, "Widget to remove cannot be null."); + Element parent = widget.getElement().getParent(); + if (parent == null) { + LoggerFactory.getLogger(getClass()).debug( + "Removal of a widget with no parent does nothing."); + continue; + } + if (getElement().equals(parent)) { + toRemove.add(widget); + } else { + throw new IllegalArgumentException("The given widget (" + widget + + ") is not a child of this dashboard"); + } + } + toRemove.forEach(this::doRemoveWidget); + updateClient(); + } + + /** + * Removes all widgets from this dashboard. + */ + public void removeAll() { + doRemoveAllWidgets(); + updateClient(); + } + + @Override + public Stream getChildren() { + return getWidgets().stream().map(Component.class::cast); + } + + @Override + protected void onAttach(AttachEvent attachEvent) { + super.onAttach(attachEvent); + attachRenderer(); + doUpdateClient(); + } + + private final Map childDetachListenerMap = new HashMap<>(); + + // Must not use lambda here as that would break serialization. See + // https://github.com/vaadin/flow-components/issues/5597 + private final ElementDetachListener childDetachListener = new ElementDetachListener() { + @Override + public void onDetach(ElementDetachEvent e) { + var detachedElement = e.getSource(); + getWidgets().stream() + .filter(widget -> Objects.equals(detachedElement, + widget.getElement())) + .findAny().ifPresent(detachedWidget -> { + // The child was removed from the dashboard + + // Remove the registration for the child detach listener + childDetachListenerMap.get(detachedWidget.getElement()) + .remove(); + childDetachListenerMap + .remove(detachedWidget.getElement()); + + widgets.remove(detachedWidget); + updateClient(); + }); + } + }; + + private void updateClient() { + if (pendingUpdate) { + return; + } + pendingUpdate = true; + getElement().getNode() + .runWhenAttached(ui -> ui.beforeClientResponse(this, ctx -> { + doUpdateClient(); + pendingUpdate = false; + })); + } + + private void doUpdateClient() { + widgets.forEach(widget -> { + Element childWidgetElement = widget.getElement(); + if (!childDetachListenerMap.containsKey(childWidgetElement)) { + childDetachListenerMap.put(childWidgetElement, + childWidgetElement + .addDetachListener(childDetachListener)); + } + }); + getElement().setPropertyJson("items", createItemsJsonArray()); + } + + private void attachRenderer() { + getElement().executeJs( + "Vaadin.FlowComponentHost.patchVirtualContainer(this);"); + String appId = UI.getCurrent().getInternals().getAppId(); + getElement().executeJs( + "this.renderer = (root, _, model) => Vaadin.FlowComponentHost.setChildNodes($0, [model.item.nodeid], root);", + appId); + } + + private JsonArray createItemsJsonArray() { + JsonArray jsonItems = Json.createArray(); + for (DashboardWidget widget : widgets) { + JsonObject jsonItem = Json.createObject(); + jsonItem.put("nodeid", getWidgetNodeId(widget)); + jsonItems.set(jsonItems.length(), jsonItem); + } + return jsonItems; + } + + private int getWidgetNodeId(DashboardWidget widget) { + return widget.getElement().getNode().getId(); + } + + private void doRemoveAllWidgets() { + new ArrayList<>(widgets).forEach(this::doRemoveWidget); + } + + private void doRemoveWidget(DashboardWidget widget) { + getElement().removeChild(widget.getElement()); + widgets.remove(widget); + } + + private void doAddWidget(int index, DashboardWidget widget) { + getElement().appendChild(widget.getElement()); + widgets.add(index, widget); + } + + private void doAddWidget(DashboardWidget widget) { + getElement().appendChild(widget.getElement()); + widgets.add(widget); + } } diff --git a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardTest.java b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardTest.java index 883a15383fb..24c2af1d691 100644 --- a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardTest.java +++ b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardTest.java @@ -8,5 +8,238 @@ */ package com.vaadin.flow.component.dashboard.tests; +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.dashboard.Dashboard; +import com.vaadin.flow.component.dashboard.DashboardWidget; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.internal.JsonUtils; +import com.vaadin.flow.server.VaadinSession; + +import elemental.json.JsonArray; + public class DashboardTest { + + private final UI ui = new UI(); + private Dashboard dashboard; + + @Before + public void setup() { + UI.setCurrent(ui); + VaadinSession session = Mockito.mock(VaadinSession.class); + Mockito.when(session.hasLock()).thenReturn(true); + ui.getInternals().setSession(session); + dashboard = new Dashboard(); + ui.add(dashboard); + fakeClientCommunication(); + } + + @After + public void tearDown() { + UI.setCurrent(null); + } + + @Test + public void addWidget_widgetIsAdded() { + DashboardWidget widget1 = new DashboardWidget(); + DashboardWidget widget2 = new DashboardWidget(); + dashboard.add(widget1, widget2); + fakeClientCommunication(); + assertWidgets(dashboard, widget1, widget2); + } + + @Test + public void addNullWidget_exceptionIsThrown() { + Assert.assertThrows(NullPointerException.class, + () -> dashboard.add((DashboardWidget) null)); + } + + @Test + public void addNullWidgetInArray_noWidgetIsAdded() { + DashboardWidget widget = new DashboardWidget(); + try { + dashboard.add(widget, null); + } catch (NullPointerException e) { + // Do nothing + } + fakeClientCommunication(); + assertWidgets(dashboard); + } + + @Test + public void addWidgetAtIndex_widgetIsCorrectlyAdded() { + DashboardWidget widget1 = new DashboardWidget(); + DashboardWidget widget2 = new DashboardWidget(); + DashboardWidget widget3 = new DashboardWidget(); + dashboard.add(widget1, widget2); + fakeClientCommunication(); + dashboard.addWidgetAtIndex(1, widget3); + fakeClientCommunication(); + assertWidgets(dashboard, widget1, widget3, widget2); + } + + @Test + public void addNullWidgetAtIndex_exceptionIsThrown() { + Assert.assertThrows(NullPointerException.class, + () -> dashboard.addWidgetAtIndex(0, null)); + } + + @Test + public void removeWidget_widgetIsRemoved() { + DashboardWidget widget1 = new DashboardWidget(); + DashboardWidget widget2 = new DashboardWidget(); + dashboard.add(widget1, widget2); + fakeClientCommunication(); + dashboard.remove(widget1); + fakeClientCommunication(); + assertWidgets(dashboard, widget2); + } + + @Test + public void removeNullWidget_exceptionIsThrown() { + Assert.assertThrows(NullPointerException.class, + () -> dashboard.remove((DashboardWidget) null)); + } + + @Test + public void removeAllWidgets_widgetsAreRemoved() { + DashboardWidget widget1 = new DashboardWidget(); + DashboardWidget widget2 = new DashboardWidget(); + dashboard.add(widget1, widget2); + fakeClientCommunication(); + dashboard.removeAll(); + fakeClientCommunication(); + assertWidgets(dashboard); + } + + @Test + public void removeWidgetFromParent_widgetIsRemoved() { + DashboardWidget widget1 = new DashboardWidget(); + dashboard.add(widget1); + fakeClientCommunication(); + widget1.removeFromParent(); + fakeClientCommunication(); + assertWidgets(dashboard); + } + + @Test + public void addWidget_virtualNodeIdsInSync() { + DashboardWidget widget1 = new DashboardWidget(); + dashboard.add(widget1); + fakeClientCommunication(); + assertWidgets(dashboard, widget1); + } + + @Test + public void removeWidget_virtualNodeIdsInSync() { + DashboardWidget widget1 = new DashboardWidget(); + dashboard.add(widget1); + fakeClientCommunication(); + widget1.removeFromParent(); + fakeClientCommunication(); + assertWidgets(dashboard); + } + + @Test + public void selfRemoveChild_virtualNodeIdsInSync() { + DashboardWidget widget1 = new DashboardWidget(); + DashboardWidget widget2 = new DashboardWidget(); + dashboard.add(widget1, widget2); + fakeClientCommunication(); + widget1.removeFromParent(); + fakeClientCommunication(); + assertWidgets(dashboard, widget2); + } + + @Test + public void addSeparately_selfRemoveChild_doesNotThrow() { + DashboardWidget widget1 = new DashboardWidget(); + DashboardWidget widget2 = new DashboardWidget(); + dashboard.add(widget1); + dashboard.add(widget2); + fakeClientCommunication(); + widget1.removeFromParent(); + fakeClientCommunication(); + assertWidgets(dashboard, widget2); + } + + @Test + public void addWidgetFromLayoutToDashboard_widgetIsMoved() { + Div parent = new Div(); + ui.add(parent); + DashboardWidget widget = new DashboardWidget(); + parent.add(widget); + fakeClientCommunication(); + dashboard.add(widget); + fakeClientCommunication(); + Assert.assertTrue(parent.getChildren().noneMatch(widget::equals)); + assertWidgets(dashboard, widget); + } + + @Test + public void addWidgetFromDashboardToLayout_widgetIsMoved() { + DashboardWidget widget = new DashboardWidget(); + dashboard.add(widget); + fakeClientCommunication(); + Div parent = new Div(); + ui.add(parent); + parent.add(widget); + fakeClientCommunication(); + assertWidgets(dashboard); + Assert.assertTrue(parent.getChildren().anyMatch(widget::equals)); + } + + @Test + public void addWidgetToAnotherDashboard_widgetIsMoved() { + DashboardWidget widget = new DashboardWidget(); + dashboard.add(widget); + fakeClientCommunication(); + Dashboard newDashboard = new Dashboard(); + ui.add(newDashboard); + newDashboard.add(widget); + fakeClientCommunication(); + assertWidgets(dashboard); + assertWidgets(newDashboard, widget); + } + + private void fakeClientCommunication() { + ui.getInternals().getStateTree().runExecutionsBeforeClientResponse(); + ui.getInternals().getStateTree().collectChanges(ignore -> { + }); + } + + private static void assertWidgets(Dashboard dashboard, + DashboardWidget... expectedWidgets) { + assertVirtualChildren(dashboard, expectedWidgets); + Assert.assertEquals(Arrays.asList(expectedWidgets), + dashboard.getWidgets()); + } + + private static void assertVirtualChildren(Dashboard dashboard, + Component... components) { + // Get a List of the node ids + List expectedChildNodeIds = Arrays.stream(components) + .map(component -> component.getElement().getNode().getId()) + .toList(); + // Get the node ids from the items property of the dashboard + List actualChildNodeIds = getChildNodeIds(dashboard); + Assert.assertEquals(expectedChildNodeIds, actualChildNodeIds); + } + + private static List getChildNodeIds(Dashboard dashboard) { + JsonArray jsonArrayOfIds = (JsonArray) dashboard.getElement() + .getPropertyRaw("items"); + return JsonUtils.objectStream(jsonArrayOfIds) + .mapToInt(obj -> (int) obj.getNumber("nodeid")).boxed() + .toList(); + } } diff --git a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardWidgetTest.java b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardWidgetTest.java index bea69c56ff2..8ae7bcd2423 100644 --- a/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardWidgetTest.java +++ b/vaadin-dashboard-flow-parent/vaadin-dashboard-flow/src/test/java/com/vaadin/flow/component/dashboard/tests/DashboardWidgetTest.java @@ -8,5 +8,86 @@ */ package com.vaadin.flow.component.dashboard.tests; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.dashboard.DashboardWidget; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.server.VaadinSession; + public class DashboardWidgetTest { + + private final UI ui = new UI(); + + @Before + public void setup() { + UI.setCurrent(ui); + VaadinSession session = Mockito.mock(VaadinSession.class); + Mockito.when(session.hasLock()).thenReturn(true); + ui.getInternals().setSession(session); + } + + @After + public void tearDown() { + UI.setCurrent(null); + } + + @Test + public void addWidgetToLayout_widgetIsAdded() { + Div layout = new Div(); + ui.add(layout); + DashboardWidget widget = new DashboardWidget(); + layout.add(widget); + fakeClientCommunication(); + Assert.assertTrue(layout.getChildren().anyMatch(widget::equals)); + } + + @Test + public void removeWidgetFromLayout_widgetIsRemoved() { + Div layout = new Div(); + ui.add(layout); + DashboardWidget widget = new DashboardWidget(); + layout.add(widget); + fakeClientCommunication(); + layout.remove(widget); + fakeClientCommunication(); + Assert.assertTrue(layout.getChildren().noneMatch(widget::equals)); + } + + @Test + public void addWidgetToLayout_removeFromParent_widgetIsRemoved() { + Div layout = new Div(); + ui.add(layout); + DashboardWidget widget = new DashboardWidget(); + layout.add(widget); + fakeClientCommunication(); + widget.removeFromParent(); + fakeClientCommunication(); + Assert.assertTrue(layout.getChildren().noneMatch(widget::equals)); + } + + @Test + public void addWidgetFromLayoutToAnotherLayout_widgetIsMoved() { + Div parent = new Div(); + ui.add(parent); + DashboardWidget widget = new DashboardWidget(); + parent.add(widget); + fakeClientCommunication(); + Div newParent = new Div(); + ui.add(newParent); + newParent.add(widget); + fakeClientCommunication(); + Assert.assertTrue(parent.getChildren().noneMatch(widget::equals)); + Assert.assertTrue(newParent.getChildren().anyMatch(widget::equals)); + } + + private void fakeClientCommunication() { + ui.getInternals().getStateTree().runExecutionsBeforeClientResponse(); + ui.getInternals().getStateTree().collectChanges(ignore -> { + }); + } } diff --git a/vaadin-dashboard-flow-parent/vaadin-dashboard-testbench/src/main/java/com/vaadin/flow/component/dashboard/testbench/DashboardElement.java b/vaadin-dashboard-flow-parent/vaadin-dashboard-testbench/src/main/java/com/vaadin/flow/component/dashboard/testbench/DashboardElement.java index 3f86d9d7ba4..725f5b8008b 100644 --- a/vaadin-dashboard-flow-parent/vaadin-dashboard-testbench/src/main/java/com/vaadin/flow/component/dashboard/testbench/DashboardElement.java +++ b/vaadin-dashboard-flow-parent/vaadin-dashboard-testbench/src/main/java/com/vaadin/flow/component/dashboard/testbench/DashboardElement.java @@ -8,6 +8,8 @@ */ package com.vaadin.flow.component.dashboard.testbench; +import java.util.List; + import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elementsbase.Element; @@ -16,4 +18,13 @@ */ @Element("vaadin-dashboard") public class DashboardElement extends TestBenchElement { + + /** + * Returns the widgets in the dashboard. + * + * @return The widgets in the dashboard + */ + public List getWidgets() { + return $(DashboardWidgetElement.class).all(); + } }