diff options
author | Leif Åstrand <leif@vaadin.com> | 2015-06-19 15:34:29 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2015-12-02 11:37:37 +0000 |
commit | 3cf57002b11ad400b8d4a33de1104b68a37e681a (patch) | |
tree | f01e0d9c88b7baaaacb31a1038a99840b805b627 | |
parent | 907e689a418f04013a58428e687ebc5132b0f45d (diff) | |
download | vaadin-framework-3cf57002b11ad400b8d4a33de1104b68a37e681a.tar.gz vaadin-framework-3cf57002b11ad400b8d4a33de1104b68a37e681a.zip |
Detect hierarchy changes not sent to the client (#18317)
Change-Id: I77b420738738a42ff50e2a509e4ac4072b1b6e1f
5 files changed, 218 insertions, 4 deletions
diff --git a/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java index 503bf8c0ae..fe1cc0770c 100644 --- a/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java +++ b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java @@ -26,6 +26,8 @@ import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.ClientConnector; import com.vaadin.server.LegacyCommunicationManager; import com.vaadin.server.PaintException; +import com.vaadin.server.VaadinRequest; +import com.vaadin.server.VaadinService; import com.vaadin.ui.UI; import elemental.json.Json; @@ -87,6 +89,22 @@ public class ConnectorHierarchyWriter implements Serializable { } } } + // Dummy assert just for conditionally storing away data that will be + // used by the real assert later on + assert storeSentHierarchy(hierarchyInfo); + writer.write(JsonUtil.stringify(hierarchyInfo)); } + + private boolean storeSentHierarchy(JsonObject hierarchyInfo) { + VaadinRequest request = VaadinService.getCurrentRequest(); + if (request != null) { + request.setAttribute(ConnectorHierarchyWriter.class.getName() + + ".hierarchyInfo", hierarchyInfo); + } + + // Always true, we're just setting up for another assert + return true; + } + } diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java index 84454f9126..a060702319 100644 --- a/server/src/com/vaadin/ui/ConnectorTracker.java +++ b/server/src/com/vaadin/ui/ConnectorTracker.java @@ -37,6 +37,9 @@ import com.vaadin.server.DragAndDropService; import com.vaadin.server.GlobalResourceHandler; import com.vaadin.server.LegacyCommunicationManager; import com.vaadin.server.StreamVariable; +import com.vaadin.server.VaadinRequest; +import com.vaadin.server.VaadinService; +import com.vaadin.server.communication.ConnectorHierarchyWriter; import elemental.json.Json; import elemental.json.JsonException; @@ -326,6 +329,13 @@ public class ConnectorTracker implements Serializable { .isConnectorVisibleToClient(connector)) { uninitializedConnectors.add(connector); diffStates.remove(connector); + + assert isRemovalSentToClient(connector) : "Connector " + + connector + + " (id = " + + connector.getConnectorId() + + ") is no longer visible to the client, but no corresponding hierarchy change is being sent."; + if (getLogger().isLoggable(Level.FINE)) { getLogger() .log(Level.FINE, @@ -338,6 +348,48 @@ public class ConnectorTracker implements Serializable { cleanStreamVariables(); } + private boolean isRemovalSentToClient(ClientConnector connector) { + VaadinRequest request = VaadinService.getCurrentRequest(); + if (request == null) { + // Probably run from a unit test without normal request handling + return true; + } + + String attributeName = ConnectorHierarchyWriter.class.getName() + + ".hierarchyInfo"; + Object hierarchyInfoObj = request.getAttribute(attributeName); + if (hierarchyInfoObj instanceof JsonObject) { + JsonObject hierachyInfo = (JsonObject) hierarchyInfoObj; + + ClientConnector firstVisibleParent = findFirstVisibleParent(connector); + if (firstVisibleParent == null) { + // Connector is detached, not our business + return true; + } + + if (!hierachyInfo.hasKey(firstVisibleParent.getConnectorId())) { + return false; + } + } else { + getLogger().warning( + "Request attribute " + attributeName + + " is not a JsonObject"); + } + + return true; + } + + private ClientConnector findFirstVisibleParent(ClientConnector connector) { + while (connector != null) { + connector = connector.getParent(); + if (LegacyCommunicationManager + .isConnectorVisibleToClient(connector)) { + return connector; + } + } + return null; + } + private void removeUnregisteredConnectors() { GlobalResourceHandler globalResourceHandler = uI.getSession() .getGlobalResourceHandler(false); diff --git a/uitest/src/com/vaadin/tests/application/MissingHierarchyDetection.java b/uitest/src/com/vaadin/tests/application/MissingHierarchyDetection.java new file mode 100644 index 0000000000..3f55e7bd60 --- /dev/null +++ b/uitest/src/com/vaadin/tests/application/MissingHierarchyDetection.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 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.application; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Component; +import com.vaadin.ui.CssLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.SelectiveRenderer; + +public class MissingHierarchyDetection extends AbstractTestUI { + + private boolean isChildRendered = true; + private BrokenCssLayout layout = new BrokenCssLayout(); + + public class BrokenCssLayout extends CssLayout implements SelectiveRenderer { + public BrokenCssLayout() { + setCaption("Broken layout"); + Label label = new Label("Child component"); + label.setId("label"); + addComponent(label); + } + + @Override + public boolean isRendered(Component childComponent) { + return isChildRendered; + } + } + + @Override + protected void setup(VaadinRequest request) { + addComponent(layout); + addComponent(new Button("Toggle properly", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + toggle(true); + } + })); + addComponent(new Button("Toggle improperly", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + toggle(false); + } + })); + } + + private void toggle(boolean properly) { + isChildRendered = !isChildRendered; + if (properly) { + layout.markAsDirtyRecursive(); + } + } +} diff --git a/uitest/src/com/vaadin/tests/application/MissingHierarchyDetectionTest.java b/uitest/src/com/vaadin/tests/application/MissingHierarchyDetectionTest.java new file mode 100644 index 0000000000..3d43b8f885 --- /dev/null +++ b/uitest/src/com/vaadin/tests/application/MissingHierarchyDetectionTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2014 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.application; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.LabelElement; +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class MissingHierarchyDetectionTest extends SingleBrowserTest { + @Test + public void testMissingHierarchyDetection() { + openTestURL(); + + Assert.assertTrue(isElementPresent(By.id("label"))); + + ButtonElement toggleProperly = $(ButtonElement.class).caption( + "Toggle properly").first(); + + toggleProperly.click(); + assertNoSystemNotifications(); + Assert.assertFalse(isElementPresent(By.id("label"))); + + toggleProperly.click(); + assertNoSystemNotifications(); + Assert.assertTrue(isElementPresent(LabelElement.class)); + + ButtonElement toggleInproperly = $(ButtonElement.class).caption( + "Toggle improperly").first(); + toggleInproperly.click(); + + assertSystemNotification(); + } +} diff --git a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java index 5572c79ff5..a9fea9408b 100644 --- a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java +++ b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java @@ -1120,12 +1120,36 @@ public abstract class AbstractTB3Test extends ParallelTest { * "?debug" as exceptions are otherwise not shown as notifications. */ protected void assertNoErrorNotifications() { - Assert.assertTrue( - "Debug window must be open to be able to see error notifications", - isDebugWindowOpen()); Assert.assertFalse( "Error notification with client side exception is shown", - isElementPresent(By.className("v-Notification-error"))); + isNotificationPresent("error")); + } + + /** + * Asserts that no system notifications are shown. + */ + protected void assertNoSystemNotifications() { + Assert.assertFalse( + "Error notification with system error exception is shown", + isNotificationPresent("system")); + } + + /** + * Asserts that a system notification is shown. + */ + protected void assertSystemNotification() { + Assert.assertTrue( + "Error notification with system error exception is not shown", + isNotificationPresent("system")); + } + + private boolean isNotificationPresent(String type) { + if ("error".equals(type)) { + Assert.assertTrue( + "Debug window must be open to be able to see error notifications", + isDebugWindowOpen()); + } + return isElementPresent(By.className("v-Notification-" + type)); } private boolean isDebugWindowOpen() { |