Change-Id: If2401d724f782ee76f92a6b89c54e51f90218beetags/8.0.0.alpha5
import com.vaadin.server.ClientConnector.ConnectorErrorEvent; | import com.vaadin.server.ClientConnector.ConnectorErrorEvent; | ||||
import com.vaadin.shared.ApplicationConstants; | import com.vaadin.shared.ApplicationConstants; | ||||
import com.vaadin.shared.JavaScriptConnectorState; | import com.vaadin.shared.JavaScriptConnectorState; | ||||
import com.vaadin.shared.JavaScriptExtensionState; | |||||
import com.vaadin.shared.communication.SharedState; | import com.vaadin.shared.communication.SharedState; | ||||
import com.vaadin.shared.ui.JavaScriptComponentState; | |||||
import com.vaadin.ui.Component; | import com.vaadin.ui.Component; | ||||
import com.vaadin.ui.ConnectorTracker; | import com.vaadin.ui.ConnectorTracker; | ||||
import com.vaadin.ui.HasComponents; | import com.vaadin.ui.HasComponents; | ||||
ConnectorTracker connectorTracker = uI.getConnectorTracker(); | ConnectorTracker connectorTracker = uI.getConnectorTracker(); | ||||
Class<? extends SharedState> stateType = connector.getStateType(); | Class<? extends SharedState> stateType = connector.getStateType(); | ||||
JsonValue diffState = connectorTracker.getDiffState(connector); | 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 | // Use an empty state object as reference for full | ||||
// repaints | // repaints | ||||
diffState = referenceDiffStates.get(stateType); | diffState = referenceDiffStates.get(stateType); | ||||
} | } | ||||
EncodeResult encodeResult = JsonCodec.encode(state, diffState, | EncodeResult encodeResult = JsonCodec.encode(state, diffState, | ||||
stateType, uI.getConnectorTracker()); | stateType, uI.getConnectorTracker()); | ||||
if (supportsDiffState) { | |||||
connectorTracker.setDiffState(connector, | |||||
(JsonObject) encodeResult.getEncodedValue()); | |||||
} | |||||
connectorTracker.setDiffState(connector, | |||||
(JsonObject) encodeResult.getEncodedValue()); | |||||
return (JsonObject) encodeResult.getDiff(); | return (JsonObject) encodeResult.getDiff(); | ||||
} | } | ||||
private static JsonValue createReferenceDiffStateState( | private static JsonValue createReferenceDiffStateState( | ||||
Class<? extends SharedState> stateType) { | 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 { | try { | ||||
SharedState referenceState = stateType.newInstance(); | SharedState referenceState = stateType.newInstance(); | ||||
EncodeResult encodeResult = JsonCodec.encode(referenceState, null, | EncodeResult encodeResult = JsonCodec.encode(referenceState, null, | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 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 | * Resolves a dependency URI, registering the URI with this | ||||
* {@code LegacyCommunicationManager} if needed and returns a fully | * {@code LegacyCommunicationManager} if needed and returns a fully |
/* | |||||
* 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"); | |||||
} | |||||
} |
package com.vaadin.ui; | package com.vaadin.ui; | ||||
import java.lang.reflect.Method; | 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.ClientConnector; | ||||
import com.vaadin.server.ServerRpcManager; | import com.vaadin.server.ServerRpcManager; | ||||
import com.vaadin.shared.communication.ServerRpc; | import com.vaadin.shared.communication.ServerRpc; | ||||
import elemental.json.JsonObject; | |||||
/** | /** | ||||
* Base class for component unit tests, providing helper methods for e.g. | * Base class for component unit tests, providing helper methods for e.g. | ||||
* invoking RPC and updating diff state. | * invoking RPC and updating diff state. | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 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()))); | |||||
} | |||||
} | } |
/* | |||||
* 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; | |||||
} | |||||
} |
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") | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |