summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArtur <artur@vaadin.com>2017-04-18 10:57:11 +0300
committerHenri Sara <henri.sara@gmail.com>2017-04-18 10:57:11 +0300
commit9a0f1c136168fbc7b63570b88da7a9cba9de389c (patch)
tree1f08ad9d488a00fabdb639f7ffe9d7a02978aad3
parentb480c7166ac56801cda11b73a6ad4694d467b98b (diff)
downloadvaadin-framework-9a0f1c136168fbc7b63570b88da7a9cba9de389c.tar.gz
vaadin-framework-9a0f1c136168fbc7b63570b88da7a9cba9de389c.zip
Composite component (#8952)
A composite is included in the server side hierarchy and in the connector hierarchy on the client side but does not have its own widget or DOM. To ensure that captions etc are renderer correctly for the root contents, the client side connector returns both the widget and state for the content connector. Server side API related to width and height are automatically forwarded to the root component to enable easy use of the composite inside different layout configurations. Other server side API inherited from AbstractComponent is unwanted, should be optional and therefore throw an exception by default. Resolves #2458
-rw-r--r--client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java90
-rw-r--r--server/src/main/java/com/vaadin/ui/ComponentRootSetter.java54
-rw-r--r--server/src/main/java/com/vaadin/ui/Composite.java339
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/StateGetDoesNotMarkDirtyTest.java16
-rw-r--r--shared/src/main/java/com/vaadin/shared/composite/CompositeState.java27
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/composite/CompositeChainUI.java97
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/composite/CompositeChainUITest.java55
7 files changed, 677 insertions, 1 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java b/client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java
new file mode 100644
index 0000000000..ad5e019b6b
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2000-2016 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.client.ui.composite;
+
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorHierarchyChangeEvent;
+import com.vaadin.client.HasComponentsConnector;
+import com.vaadin.client.ui.AbstractHasComponentsConnector;
+import com.vaadin.shared.AbstractComponentState;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.ui.Composite;
+
+@Connect(value = Composite.class, loadStyle = LoadStyle.EAGER)
+public class CompositeConnector extends AbstractHasComponentsConnector {
+
+ private ComponentConnector childConnector;
+
+ @Override
+ protected Widget createWidget() {
+ throw new UnsupportedOperationException(
+ "Composite has no widget of its own");
+ }
+
+ private boolean hasChildConnector() {
+ return getChildConnector() != null;
+ }
+
+ private ComponentConnector getChildConnector() {
+ // Must store the child connector to have it available when removing the
+ // connector
+ if (childConnector == null && !getChildren().isEmpty()) {
+ childConnector = (ComponentConnector) getChildren().get(0);
+ }
+ return childConnector;
+ }
+
+ @Override
+ public Widget getWidget() {
+ if (!hasChildConnector()) {
+ // This happens in doInit for instance when setConnectorId is called
+ return new Label("This widget should not end up anywhere ever");
+ } else {
+ return getChildConnector().getWidget();
+ }
+ }
+
+ @Override
+ public HasComponentsConnector getParent() {
+ return (HasComponentsConnector) super.getParent();
+ }
+
+ @Override
+ public void updateCaption(ComponentConnector component) {
+ // Parent might assume that the connector is always a child connector,
+ // therefore passing "this" instead of the child connector. The child
+ // caption will be returned as getState() returns the child's state.
+ getParent().updateCaption(this);
+ }
+
+ @Override
+ public AbstractComponentState getState() {
+ if (!hasChildConnector()) {
+ return new AbstractComponentState();
+ } else {
+ return getChildConnector().getState();
+ }
+ }
+
+ @Override
+ public void onConnectorHierarchyChange(
+ ConnectorHierarchyChangeEvent event) {
+ // Handled in getChildConnector
+ }
+}
diff --git a/server/src/main/java/com/vaadin/ui/ComponentRootSetter.java b/server/src/main/java/com/vaadin/ui/ComponentRootSetter.java
new file mode 100644
index 0000000000..1bc3565de8
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/ComponentRootSetter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2016 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.ui;
+
+import java.io.Serializable;
+
+/**
+ * Internal utility class.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class ComponentRootSetter implements Serializable {
+
+ private ComponentRootSetter() {
+ // Util methods only
+ }
+
+ /**
+ * Sets the composition root for the given custom component or composite.
+ * <p>
+ * For internal use only.
+ *
+ * @param customComponent
+ * the custom component or composite
+ * @param component
+ * the component to assign as composition root
+ */
+ public static void setRoot(Component customComponent, Component component) {
+ if (customComponent instanceof CustomComponent) {
+ ((CustomComponent) customComponent).setCompositionRoot(component);
+ } else if (customComponent instanceof Composite) {
+ ((Composite) customComponent).setCompositionRoot(component);
+ } else {
+ throw new IllegalArgumentException(
+ "Parameter is of an unsupported type: "
+ + customComponent.getClass().getName());
+ }
+ }
+
+}
diff --git a/server/src/main/java/com/vaadin/ui/Composite.java b/server/src/main/java/com/vaadin/ui/Composite.java
new file mode 100644
index 0000000000..10bcff0510
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/Composite.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2000-2016 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.ui;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.Resource;
+import com.vaadin.shared.composite.CompositeState;
+import com.vaadin.shared.ui.ContentMode;
+
+/**
+ * Composite allows creating new UI components by composition of existing
+ * server-side components.
+ * <p>
+ * A composite is created by extending the Composite class and setting the
+ * composition root component using {@link #setCompositionRoot(Component)}.
+ * </p>
+ * <p>
+ * The composition root itself can contain more components. The advantage of
+ * wrapping it in a composite is that the details of the composition root, such
+ * as its public API, are hidden from the users of the composite.
+ * </p>
+ * <p>
+ * A composite itself does not contribute to the DOM in any way (contrary to
+ * {@link CustomComponent} which adds a {@code <div>} to the DOM.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since
+ */
+public class Composite extends AbstractComponent implements HasComponents {
+
+ private static final String COMPOSITE_HAS_NO_DOM_OR_WIDGET = "A composite has no DOM or widget";
+ /**
+ * The contained component.
+ */
+ private Component root = null;
+
+ /**
+ * Constructs a new empty composite.
+ * <p>
+ * Use {@link #setCompositionRoot(Component)} to define the contents of the
+ * composite.
+ */
+ public Composite() {
+ }
+
+ /**
+ * Constructs a new composite containing the given component.
+ *
+ * @param compositionRoot
+ * the root of the composition component tree. It must not be
+ * null.
+ */
+ public Composite(AbstractComponent compositionRoot) {
+ this();
+ Objects.requireNonNull(compositionRoot);
+ setCompositionRoot(compositionRoot);
+ }
+
+ /**
+ * Returns the composition root.
+ *
+ * @return the Component Composition root.
+ */
+ protected Component getCompositionRoot() {
+ return root;
+ }
+
+ /**
+ * Sets the component contained in the composite.
+ * <p>
+ * You must set the composition root to a non-null value before the
+ * component can be used. It cannot be changed.
+ * </p>
+ *
+ * @param compositionRoot
+ * the root of the composition component tree.
+ */
+ protected void setCompositionRoot(Component compositionRoot) {
+ if (root != null) {
+ throw new IllegalStateException(
+ "Composition root cannot be changed");
+ }
+ if (compositionRoot == null) {
+ throw new IllegalArgumentException(
+ "Composition root cannot be null");
+ }
+
+ // set new component
+ if (compositionRoot.getParent() != null) {
+ // If the component already has a parent, try to remove it
+ AbstractSingleComponentContainer.removeFromParent(compositionRoot);
+ }
+
+ compositionRoot.setParent(this);
+ root = compositionRoot;
+ markAsDirty();
+ }
+
+ /* Basic component features ------------------------------------------ */
+
+ @Override
+ public Iterator<Component> iterator() {
+ if (getCompositionRoot() != null) {
+ return Collections.singletonList(getCompositionRoot()).iterator();
+ } else {
+ return Collections.<Component> emptyList().iterator();
+ }
+ }
+
+ /**
+ * Gets the number of contained components.
+ *
+ * @return the number of contained components (zero or one)
+ */
+ public int getComponentCount() {
+ return (getCompositionRoot() != null ? 1 : 0);
+ }
+
+ @Override
+ protected CompositeState getState() {
+ return (CompositeState) super.getState();
+ }
+
+ @Override
+ protected CompositeState getState(boolean markAsDirty) {
+ return (CompositeState) super.getState(markAsDirty);
+ }
+
+ @Override
+ public void beforeClientResponse(boolean initial) {
+ super.beforeClientResponse(initial);
+ if (getComponentCount() != 1) {
+ throw new IllegalStateException(
+ "A composite must always have a composition root");
+ }
+ }
+
+ @Override
+ public String getStyleName() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setStyleName(String style, boolean add) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void addStyleName(String style) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void removeStyleName(String style) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public String getPrimaryStyleName() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setPrimaryStyleName(String style) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ private Component getRootOrThrow() {
+ return Optional.ofNullable(getCompositionRoot())
+ .orElseThrow(() -> new IllegalStateException(
+ "Composition root has not been set"));
+ }
+
+ @Override
+ public float getWidth() {
+ return getRootOrThrow().getWidth();
+ }
+
+ @Override
+ public float getHeight() {
+ return getRootOrThrow().getHeight();
+ }
+
+ @Override
+ public Unit getWidthUnits() {
+ return getRootOrThrow().getWidthUnits();
+ }
+
+ @Override
+ public Unit getHeightUnits() {
+ return getRootOrThrow().getHeightUnits();
+ }
+
+ @Override
+ public void setHeight(String height) {
+ getRootOrThrow().setHeight(height);
+ }
+
+ @Override
+ public void setWidth(float width, Unit unit) {
+ getRootOrThrow().setWidth(width, unit);
+ }
+
+ @Override
+ public void setHeight(float height, Unit unit) {
+ getRootOrThrow().setHeight(height, unit);
+ }
+
+ @Override
+ public void setWidth(String width) {
+ getRootOrThrow().setWidth(width);
+ }
+
+ @Override
+ public void setSizeFull() {
+ getRootOrThrow().setSizeFull();
+ }
+
+ @Override
+ public void setSizeUndefined() {
+ getRootOrThrow().setSizeUndefined();
+ }
+
+ @Override
+ public void setWidthUndefined() {
+ getRootOrThrow().setWidthUndefined();
+ }
+
+ @Override
+ public void setHeightUndefined() {
+ getRootOrThrow().setHeightUndefined();
+ }
+
+ @Override
+ public void setId(String id) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public String getId() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setDebugId(String id) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public String getDebugId() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public String getCaption() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setCaption(String caption) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setCaptionAsHtml(boolean captionAsHtml) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public boolean isCaptionAsHtml() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public Resource getIcon() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setIcon(Resource icon) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public String getDescription() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setDescription(String description) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setDescription(String description, ContentMode mode) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public ErrorMessage getErrorMessage() {
+ return null;
+ }
+
+ @Override
+ public ErrorMessage getComponentError() {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+ @Override
+ public void setComponentError(ErrorMessage componentError) {
+ throw new UnsupportedOperationException(COMPOSITE_HAS_NO_DOM_OR_WIDGET);
+ }
+
+}
diff --git a/server/src/test/java/com/vaadin/tests/server/component/StateGetDoesNotMarkDirtyTest.java b/server/src/test/java/com/vaadin/tests/server/component/StateGetDoesNotMarkDirtyTest.java
index 3b4c2413f9..29dd6638fa 100644
--- a/server/src/test/java/com/vaadin/tests/server/component/StateGetDoesNotMarkDirtyTest.java
+++ b/server/src/test/java/com/vaadin/tests/server/component/StateGetDoesNotMarkDirtyTest.java
@@ -17,6 +17,8 @@ import org.mockito.Mockito;
import com.vaadin.server.VaadinSession;
import com.vaadin.tests.VaadinClasses;
import com.vaadin.ui.Component;
+import com.vaadin.ui.ComponentRootSetter;
+import com.vaadin.ui.Composite;
import com.vaadin.ui.ConnectorTracker;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
@@ -75,7 +77,16 @@ public class StateGetDoesNotMarkDirtyTest {
}
// just to make sure we can invoke it
method.setAccessible(true);
- method.invoke(newInstance);
+ try {
+ method.invoke(newInstance);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof UnsupportedOperationException) {
+ // Overridden getter which is not supposed to be
+ // called
+ } else {
+ throw e;
+ }
+ }
}
} catch (Exception e) {
System.err.println("problem with method " + clazz.getName()
@@ -116,6 +127,9 @@ public class StateGetDoesNotMarkDirtyTest {
if (component instanceof UI) {
return component;
}
+ if (component instanceof Composite) {
+ ComponentRootSetter.setRoot(component, new Label());
+ }
emulateAttach(component);
return component;
} catch (NoSuchMethodException e) {
diff --git a/shared/src/main/java/com/vaadin/shared/composite/CompositeState.java b/shared/src/main/java/com/vaadin/shared/composite/CompositeState.java
new file mode 100644
index 0000000000..1f552390df
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/composite/CompositeState.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2000-2016 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.shared.composite;
+
+import com.vaadin.shared.AbstractComponentState;
+
+/**
+ * Shared state for Composite.
+ *
+ * @author Vaadin Ltd
+ * @since
+ */
+public class CompositeState extends AbstractComponentState {
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/composite/CompositeChainUI.java b/uitest/src/main/java/com/vaadin/tests/components/composite/CompositeChainUI.java
new file mode 100644
index 0000000000..481f7c958f
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/composite/CompositeChainUI.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2016 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.tests.components.composite;
+
+import java.util.Iterator;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Composite;
+import com.vaadin.ui.HasComponents;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.VerticalLayout;
+
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class CompositeChainUI extends AbstractTestUIWithLog {
+
+ private Label innermostComponent;
+ private Composite innerComposite;
+ private Composite outerComposite;
+ private VerticalLayout container;
+ private HorizontalLayout layout;
+
+ @Override
+ protected void setup(VaadinRequest request) {
+
+ createComposite();
+ layout = new HorizontalLayout(outerComposite);
+ container = new VerticalLayout(layout);
+ addComponent(container);
+
+ Button updateCaption = new Button("Update caption");
+ updateCaption.addClickListener(e -> {
+ innermostComponent
+ .setCaption(innermostComponent.getCaption() + " - updated");
+ });
+ addComponent(updateCaption);
+ Button replaceWithAnotherComposite = new Button(
+ "Replace with another Composite", e -> {
+ Composite oldOuter = outerComposite;
+ createComposite();
+ layout.replaceComponent(oldOuter, outerComposite);
+ });
+ addComponent(replaceWithAnotherComposite);
+ logHierarchy();
+ }
+
+ private void createComposite() {
+ innermostComponent = new Label("Label text");
+ innermostComponent.setCaption("Label caption");
+ innermostComponent.setId("innermost");
+
+ innerComposite = new Composite(innermostComponent);
+ outerComposite = new Composite(innerComposite);
+ }
+
+ private void logHierarchy() {
+ String msg = "Hierarchy: ";
+ if (container != null) {
+ msg += getHierarchy(container);
+ }
+ log(msg);
+ }
+
+ private static String getHierarchy(Component component) {
+ String msg = component.getClass().getSimpleName();
+ if (component instanceof HasComponents) {
+
+ Iterator<Component> iterator = ((HasComponents) component)
+ .iterator();
+ if (iterator.hasNext()) {
+ Component content = iterator.next();
+ if (content != null) {
+ msg += " -> " + getHierarchy(content);
+ }
+ }
+ }
+ return msg;
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/composite/CompositeChainUITest.java b/uitest/src/test/java/com/vaadin/tests/components/composite/CompositeChainUITest.java
new file mode 100644
index 0000000000..228d20f058
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/composite/CompositeChainUITest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2016 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.tests.components.composite;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.LabelElement;
+import com.vaadin.tests.tb3.SingleBrowserTest;
+
+public class CompositeChainUITest extends SingleBrowserTest {
+
+ @Test
+ public void compositeRenderedAndUpdatedCorrectly() {
+ openTestURL();
+ LabelElement label = $(LabelElement.class).id("innermost");
+ WebElement labelGrandParent = label.findElement(By.xpath("../.."));
+
+ Assert.assertEquals("v-slot", labelGrandParent.getAttribute("class"));
+ Assert.assertEquals("Label caption", label.getCaption());
+
+ $(ButtonElement.class).caption("Update caption").first().click();
+ Assert.assertEquals("Label caption - updated", label.getCaption());
+
+ }
+
+ @Test
+ public void compositeRemovedCorrectly() {
+ openTestURL("debug");
+ LabelElement label = $(LabelElement.class).id("innermost");
+ $(ButtonElement.class).caption("Update caption").first().click();
+ Assert.assertEquals("Label caption - updated", label.getCaption());
+ $(ButtonElement.class).caption("Replace with another Composite").first()
+ .click();
+ label = $(LabelElement.class).id("innermost");
+ Assert.assertEquals("Label caption", label.getCaption());
+ assertNoErrorNotifications();
+ }
+}