]> source.dussan.org Git - vaadin-framework.git/commitdiff
Use diffstate for JS connectors (#20335)
authorLeif Åstrand <leif@vaadin.com>
Mon, 26 Sep 2016 13:19:33 +0000 (16:19 +0300)
committerVaadin Code Review <review@vaadin.com>
Mon, 17 Oct 2016 06:25:55 +0000 (06:25 +0000)
Change-Id: If2401d724f782ee76f92a6b89c54e51f90218bee

server/src/main/java/com/vaadin/server/LegacyCommunicationManager.java
server/src/test/java/com/vaadin/ui/AbstractJavaScriptComponentTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/ui/ComponentTest.java
uitest/src/main/java/com/vaadin/tests/components/javascriptcomponent/StateChangeCounter.java [new file with mode: 0644]
uitest/src/main/resources/com/vaadin/tests/components/javascriptcomponent/StateChangeCounter.js [new file with mode: 0644]
uitest/src/test/java/com/vaadin/tests/components/javascriptcomponent/StateChangeCounterTest.java [new file with mode: 0644]

index 858f61d0c7b18c8193ae6d8675ae55a1804c3680..7c438ca999f1940b5960a3db8b8dc2f0fb663afb 100644 (file)
@@ -31,7 +31,9 @@ import java.util.logging.Logger;
 import com.vaadin.server.ClientConnector.ConnectorErrorEvent;
 import com.vaadin.shared.ApplicationConstants;
 import com.vaadin.shared.JavaScriptConnectorState;
+import com.vaadin.shared.JavaScriptExtensionState;
 import com.vaadin.shared.communication.SharedState;
+import com.vaadin.shared.ui.JavaScriptComponentState;
 import com.vaadin.ui.Component;
 import com.vaadin.ui.ConnectorTracker;
 import com.vaadin.ui.HasComponents;
@@ -94,9 +96,8 @@ public class LegacyCommunicationManager implements Serializable {
         ConnectorTracker connectorTracker = uI.getConnectorTracker();
         Class<? extends SharedState> stateType = connector.getStateType();
         JsonValue diffState = connectorTracker.getDiffState(connector);
-        boolean supportsDiffState = !JavaScriptConnectorState.class
-                .isAssignableFrom(stateType);
-        if (diffState == null && supportsDiffState) {
+
+        if (diffState == null) {
             // Use an empty state object as reference for full
             // repaints
             diffState = referenceDiffStates.get(stateType);
@@ -107,15 +108,24 @@ public class LegacyCommunicationManager implements Serializable {
         }
         EncodeResult encodeResult = JsonCodec.encode(state, diffState,
                 stateType, uI.getConnectorTracker());
-        if (supportsDiffState) {
-            connectorTracker.setDiffState(connector,
-                    (JsonObject) encodeResult.getEncodedValue());
-        }
+        connectorTracker.setDiffState(connector,
+                (JsonObject) encodeResult.getEncodedValue());
+
         return (JsonObject) encodeResult.getDiff();
     }
 
     private static JsonValue createReferenceDiffStateState(
             Class<? extends SharedState> stateType) {
+        if (JavaScriptConnectorState.class.isAssignableFrom(stateType)) {
+            /*
+             * For JS state types, we should only include the framework-provided
+             * state fields in the reference diffstate since other fields are
+             * not know by the client and would therefore not get the right
+             * initial value if it would be recorded in the diffstate.
+             */
+            stateType = findJsStateReferenceType(stateType);
+        }
+
         try {
             SharedState referenceState = stateType.newInstance();
             EncodeResult encodeResult = JsonCodec.encode(referenceState, null,
@@ -129,6 +139,35 @@ public class LegacyCommunicationManager implements Serializable {
         }
     }
 
+    /**
+     * Finds the highest super class which implements
+     * {@link JavaScriptConnectorState}. In practice, this finds either
+     * {@link JavaScriptComponentState} or {@link JavaScriptExtensionState}.
+     * This is used to find which state properties the client side knows
+     * something about.
+     *
+     * @param stateType
+     *            the state type for which the reference type should be found
+     * @return the found reference type
+     */
+    private static Class<? extends SharedState> findJsStateReferenceType(
+            Class<? extends SharedState> stateType) {
+        assert JavaScriptConnectorState.class.isAssignableFrom(stateType);
+
+        Class<?> type = stateType;
+
+        while (type != null) {
+            Class<?> superclass = type.getSuperclass();
+            if (!JavaScriptConnectorState.class.isAssignableFrom(superclass)) {
+                break;
+            }
+
+            type = superclass;
+        }
+
+        return type.asSubclass(SharedState.class);
+    }
+
     /**
      * Resolves a dependency URI, registering the URI with this
      * {@code LegacyCommunicationManager} if needed and returns a fully
diff --git a/server/src/test/java/com/vaadin/ui/AbstractJavaScriptComponentTest.java b/server/src/test/java/com/vaadin/ui/AbstractJavaScriptComponentTest.java
new file mode 100644 (file)
index 0000000..0c92382
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 org.junit.Test;
+
+import com.vaadin.shared.ui.JavaScriptComponentState;
+import com.vaadin.tests.util.MockUI;
+
+public class AbstractJavaScriptComponentTest {
+    public static class TestJsComponentState extends JavaScriptComponentState {
+        public String ownField = "foo";
+    }
+
+    public static class TestJsComponent extends AbstractJavaScriptComponent {
+        @Override
+        protected TestJsComponentState getState() {
+            return (TestJsComponentState) super.getState();
+        }
+    }
+
+    @Test
+    public void testComponentStateEncoding() {
+        MockUI ui = new MockUI();
+        TestJsComponent component = new TestJsComponent();
+        ui.setContent(component);
+
+        ComponentTest.assertEncodedStateProperties(component,
+                "Only defaults not known by the client should be sent",
+                "ownField");
+
+        component.setCaption("My caption");
+        ComponentTest.assertEncodedStateProperties(component,
+                "Caption should be the only changed state property", "caption");
+    }
+}
index cc58dbbf9393e5a7534d0d015c22fab69ad05b25..09870b866ba8f847d16638f55f36abfb575454f5 100644 (file)
 package com.vaadin.ui;
 
 import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.junit.Assert;
 
 import com.vaadin.server.ClientConnector;
 import com.vaadin.server.ServerRpcManager;
 import com.vaadin.shared.communication.ServerRpc;
 
+import elemental.json.JsonObject;
+
 /**
  * Base class for component unit tests, providing helper methods for e.g.
  * invoking RPC and updating diff state.
@@ -87,4 +93,27 @@ public class ComponentTest {
         }
     }
 
+    /**
+     * Asserts the set of properties that would be sent as state changes for the
+     * given connector.
+     *
+     * @param connector
+     *            the connector that has state changes
+     * @param message
+     *            the message to show if the properties are not as expected
+     * @param expectedProperties
+     *            names of the expected properties
+     */
+    public static void assertEncodedStateProperties(ClientConnector connector,
+            String message, String... expectedProperties) {
+        assert connector.isAttached();
+
+        JsonObject encodeState = connector.encodeState();
+
+        // Collect to HashSet so that order doesn't matter
+        Assert.assertEquals(message,
+                new HashSet<>(Arrays.asList(expectedProperties)),
+                new HashSet<>(Arrays.asList(encodeState.keys())));
+    }
+
 }
diff --git a/uitest/src/main/java/com/vaadin/tests/components/javascriptcomponent/StateChangeCounter.java b/uitest/src/main/java/com/vaadin/tests/components/javascriptcomponent/StateChangeCounter.java
new file mode 100644 (file)
index 0000000..a82151c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.javascriptcomponent;
+
+import com.vaadin.annotations.JavaScript;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.JavaScriptComponentState;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.AbstractJavaScriptComponent;
+import com.vaadin.ui.Button;
+
+public class StateChangeCounter extends AbstractTestUI {
+
+    @Override
+    public String getTestDescription() {
+        return "onStateChange should be called only if the state has actually changed";
+    }
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        StateChangeCounterComponent counter = new StateChangeCounterComponent();
+
+        addComponents(new Button("Send RPC", event -> counter.sendRpc()),
+                new Button("Change state", event -> counter.changeState()),
+                new Button("Mark as dirty", event -> counter.markAsDirty()),
+                counter);
+    }
+
+    @JavaScript("StateChangeCounter.js")
+    public static class StateChangeCounterComponent
+            extends AbstractJavaScriptComponent {
+        public void sendRpc() {
+            callFunction("sendRpc");
+        }
+
+        public void changeState() {
+            getState().stateCounter++;
+        }
+
+        @Override
+        protected StateChangeCounterState getState() {
+            return (StateChangeCounterState) super.getState();
+        }
+    }
+
+    public static class StateChangeCounterState
+            extends JavaScriptComponentState {
+        public int stateCounter = 0;
+    }
+
+}
diff --git a/uitest/src/main/resources/com/vaadin/tests/components/javascriptcomponent/StateChangeCounter.js b/uitest/src/main/resources/com/vaadin/tests/components/javascriptcomponent/StateChangeCounter.js
new file mode 100644 (file)
index 0000000..79e2ef7
--- /dev/null
@@ -0,0 +1,18 @@
+window.com_vaadin_tests_components_javascriptcomponent_StateChangeCounter_StateChangeCounterComponent = function() {
+       var self = this;
+       
+       var logRow = function(text) {
+               var child = document.createElement("div");
+               child.className="logRow";
+               child.textContent = text;
+               self.getElement().appendChild(child);
+       }
+       
+       this.onStateChange = function() {
+               logRow("State change, counter = " + this.getState().stateCounter);
+       }
+       
+       this.sendRpc = function() {
+               logRow("RPC")
+       }
+}
\ No newline at end of file
diff --git a/uitest/src/test/java/com/vaadin/tests/components/javascriptcomponent/StateChangeCounterTest.java b/uitest/src/test/java/com/vaadin/tests/components/javascriptcomponent/StateChangeCounterTest.java
new file mode 100644 (file)
index 0000000..0dd88ef
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.javascriptcomponent;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+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.tests.tb3.SingleBrowserTest;
+
+public class StateChangeCounterTest extends SingleBrowserTest {
+    @Test
+    public void testStateChanges() {
+        openTestURL();
+
+        // Expecting message from initial state change
+        assertMessages("State change, counter = 0");
+
+        $(ButtonElement.class).caption("Mark as dirty").first().click();
+        // Shouldn't change anything
+        assertMessages("State change, counter = 0");
+
+        $(ButtonElement.class).caption("Send RPC").first().click();
+
+        // Should only add an RPC message, no state change message
+        assertMessages("State change, counter = 0", "RPC");
+
+        $(ButtonElement.class).caption("Change state").first().click();
+
+        // Should add one message, about a new state change
+        assertMessages("State change, counter = 0", "RPC",
+                "State change, counter = 1");
+    }
+
+    private void assertMessages(String... expectedMessages) {
+        List<String> actualMessages = findElements(By.className("logRow"))
+                .stream().map(WebElement::getText).collect(Collectors.toList());
+        Assert.assertEquals(Arrays.asList(expectedMessages), actualMessages);
+    }
+}