Change-Id: If2401d724f782ee76f92a6b89c54e51f90218beetags/8.0.0.alpha5
@@ -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 |
@@ -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"); | |||
} | |||
} |
@@ -16,11 +16,17 @@ | |||
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()))); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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") | |||
} | |||
} |
@@ -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); | |||
} | |||
} |