/*
* Copyright (C) 2011 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.databinding.client.test;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.List;
import org.jboss.errai.databinding.client.BindableProxy;
import org.jboss.errai.databinding.client.InvalidPropertyExpressionException;
import org.jboss.errai.databinding.client.MockHandler;
import org.jboss.errai.databinding.client.ModelWithDeclarativeHandler;
import org.jboss.errai.databinding.client.PropertyChangeUnsubscribeHandle;
import org.jboss.errai.databinding.client.TestModel;
import org.jboss.errai.databinding.client.TestModelWithBindableTypeList;
import org.jboss.errai.databinding.client.TestModelWithList;
import org.jboss.errai.databinding.client.TestModelWithListWidget;
import org.jboss.errai.databinding.client.api.Convert;
import org.jboss.errai.databinding.client.api.Converter;
import org.jboss.errai.databinding.client.api.DataBinder;
import org.jboss.errai.databinding.client.api.StateSync;
import org.jboss.errai.databinding.client.api.handler.property.PropertyChangeEvent;
import org.jboss.errai.databinding.client.api.handler.property.PropertyChangeHandler;
import org.jboss.errai.ioc.client.container.Proxy;
import org.jboss.errai.ioc.client.container.RefHolder;
import org.jboss.errai.ioc.client.test.AbstractErraiIOCTest;
import org.jboss.errai.marshalling.client.api.MarshallerFramework;
import org.junit.Test;
import com.google.gwt.user.client.ui.TextBox;
/**
* Tests the functionality provided by the {@link DataBinder} API for property change events.
*
* @author Christian Sadilek <csadilek@redhat.com>
*/
@SuppressWarnings("unchecked")
public class PropertyChangeHandlerIntegrationTest extends AbstractErraiIOCTest {
@Override
public String getModuleName() {
return "org.jboss.errai.databinding.DataBindingTestModule";
}
@Override
protected void gwtSetUp() throws Exception {
super.gwtSetUp();
Convert.deregisterDefaultConverters();
MarshallerFramework.initializeDefaultSessionProvider();
}
@Test
public void testPropertyChangeHandling() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "value");
binder.addPropertyChangeHandler(handler);
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "UI change", handler.getEvents().get(0).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel(), handler.getEvents().get(0).getSource());
// This should not cause additional events to be fired
binder.setModel(new TestModel(), StateSync.FROM_MODEL);
binder.getModel().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(1).getPropertyName());
assertEquals("Wrong property value in event", "model change", handler.getEvents().get(1).getNewValue());
assertEquals("Wrong previous value in event", null, handler.getEvents().get(1).getOldValue());
assertEquals("Wrong event source", binder.getModel(), handler.getEvents().get(1).getSource());
}
@Test
public void testPropertyChangeHandlingWithPropertyChain() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
final PropertyChangeUnsubscribeHandle unsubHandle = binder.addPropertyChangeHandler("child.child.value", handler);
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "UI change", handler.getEvents().get(0).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(),
handler.getEvents().get(0).getSource());
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(1).getPropertyName());
assertEquals("Wrong property value in event", "model change", handler.getEvents().get(1).getNewValue());
assertEquals("Wrong previous value in event", "UI change", handler.getEvents().get(1).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(),
handler.getEvents().get(1).getSource());
unsubHandle.unsubscribe();
textBox.setValue("UI change 2", true);
assertEquals("Should have received no additional event", 2, handler.getEvents().size());
}
@Test
public void testPropertyChangeHandlingWithPropertyChainAndRootInstanceChange() {
final MockHandler childHandler = new MockHandler();
final MockHandler valueHandler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.value");
binder.addPropertyChangeHandler("child", childHandler);
binder.addPropertyChangeHandler("child.value", valueHandler);
final TestModel oldChild = binder.getModel().getChild();
final TestModel newChild = new TestModel("model change");
binder.getModel().setChild(newChild);
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly one property change event", 1, childHandler.getEvents().size());
assertEquals("Wrong property name in event", "child", childHandler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", newChild, childHandler.getEvents().get(0).getNewValue());
assertEquals("Wrong previous value in event", oldChild, childHandler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel(), childHandler.getEvents().get(0).getSource());
assertEquals("Should have received exactly one property change event", 1, valueHandler.getEvents().size());
assertEquals("Wrong property name in event", "value", valueHandler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "model change", valueHandler.getEvents().get(0).getNewValue());
assertEquals("Wrong previous value in event", null, valueHandler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild(), valueHandler.getEvents().get(0).getSource());
}
@Test
public void testPropertyChangeHandlingWithPropertyChainAndRootInstanceChangeOfTwoLevels() {
final MockHandler childHandler = new MockHandler();
final MockHandler valueHandler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
binder.addPropertyChangeHandler("child", childHandler);
binder.addPropertyChangeHandler("child.child.value", valueHandler);
final TestModel oldChild = binder.getModel().getChild();
final TestModel newChild = new TestModel();
newChild.setChild(new TestModel("model change"));
binder.getModel().setChild(newChild);
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly one property change event", 1, childHandler.getEvents().size());
assertEquals("Wrong property name in event", "child", childHandler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", newChild, childHandler.getEvents().get(0).getNewValue());
assertEquals("Wrong previous value in event", oldChild, childHandler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel(), childHandler.getEvents().get(0).getSource());
assertEquals("Should have received exactly one property change event", 1, valueHandler.getEvents().size());
assertEquals("Wrong property name in event", "value", valueHandler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "model change", valueHandler.getEvents().get(0).getNewValue());
assertEquals("Wrong previous value in event", null, valueHandler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(), valueHandler.getEvents().get(0).getSource());
}
@Test
public void testPropertyChangeHandlingWithWildcardAndPropertyChain() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
binder.addPropertyChangeHandler("child.child.*", handler);
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "UI change", handler.getEvents().get(0).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(),
handler.getEvents().get(0).getSource());
// This should not cause additional events to be fired
binder.setModel(new TestModel(), StateSync.FROM_MODEL);
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(1).getPropertyName());
assertEquals("Wrong property value in event", "model change", handler.getEvents().get(1).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(1).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(), handler.getEvents().get(1).getSource());
}
@Test
public void testPropertyChangeHandlingOfBoundList() {
final MockHandler handler = new MockHandler();
final TestModelWithListWidget widget = new TestModelWithListWidget();
final DataBinder<TestModelWithList> binder = DataBinder.forType(TestModelWithList.class).bind(widget, "list");
binder.getModel().setList(null);
binder.addPropertyChangeHandler(handler);
List<String> list = new ArrayList<>();
widget.setValue(list, true);
assertEquals("Model not properly updated", list, binder.getModel().getList());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "list", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", list, handler.getEvents().get(0).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel(), handler.getEvents().get(0).getSource());
list = new ArrayList<>(Arrays.asList("1"));
binder.getModel().setList(list);
assertEquals("Widget not properly updated", list, widget.getValue());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
assertEquals("Wrong property name in event", "list", handler.getEvents().get(1).getPropertyName());
assertEquals("Wrong property value in event", Arrays.asList("1"), handler.getEvents().get(1).getNewValue());
assertEquals("Wrong event source", binder.getModel(), handler.getEvents().get(1).getSource());
list = binder.getModel().getList();
list.add("2");
assertEquals("Should have received exactly three property change events", 3, handler.getEvents().size());
assertEquals("Wrong property name in event", "list", handler.getEvents().get(2).getPropertyName());
assertEquals("Wrong old property value in event", Arrays.asList("1"), handler.getEvents().get(2).getOldValue());
assertEquals("Wrong property value in event", Arrays.asList("1", "2"), handler.getEvents().get(2).getNewValue());
assertEquals("Wrong event source", binder.getModel(), handler.getEvents().get(2).getSource());
list.remove(1);
assertEquals("Should have received exactly four property change events", 4, handler.getEvents().size());
assertEquals("Wrong property name in event", "list", handler.getEvents().get(3).getPropertyName());
assertEquals("Wrong old property value in event", Arrays.asList("1", "2"), handler.getEvents().get(3).getOldValue());
assertEquals("Wrong property value in event", Arrays.asList("1"), handler.getEvents().get(3).getNewValue());
assertEquals("Wrong event source", binder.getModel(), handler.getEvents().get(3).getSource());
}
@Test
public void testBoundListFiresPropertyChangeEventOnElementChange() {
final MockHandler handler = new MockHandler();
final DataBinder<TestModelWithBindableTypeList> binder =
DataBinder.forType(TestModelWithBindableTypeList.class).bind(new TextBox(), "list", new Converter<List<TestModelWithBindableTypeList>, String>() {
@SuppressWarnings("rawtypes")
@Override
public Class<List<TestModelWithBindableTypeList>> getModelType() {
return (Class) List.class;
}
@Override
public Class<String> getComponentType() {
return String.class;
}
@Override
public List<TestModelWithBindableTypeList> toModelValue(final String widgetValue) {
return Collections.emptyList();
}
@Override
public String toWidgetValue(final List<TestModelWithBindableTypeList> modelValue) {
return "";
}
});
binder.getModel().getList().add(new TestModelWithBindableTypeList("id"));
binder.addPropertyChangeHandler(handler);
// Mutating the list element should cause a property change event for the list
final TestModelWithBindableTypeList element = binder.getModel().getList().get(0);
// Guards against regressions of ERRAI-848: no list operation should re-wrap an element proxy and add
// additional change handlers
binder.getModel().getList().contains(element);
element.setId("id-change");
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "list", handler.getEvents().get(0).getPropertyName());
assertTrue("Wrong property value in event",handler.getEvents().get(0).getNewValue().equals(
Arrays.asList(new TestModelWithBindableTypeList("id-change"))));
assertTrue("Wrong property value in event",handler.getEvents().get(0).getNewValue().equals(
binder.getModel().getList()));
assertEquals("Wrong event source", binder.getModel(), handler.getEvents().get(0).getSource());
binder.getModel().getList().remove(0);
assertEquals("Should have received exactly two property change event", 2, handler.getEvents().size());
// Once the element is removed from the list mutations should no longer cause change events
element.setId("id-change2");
assertEquals("Should have received no additional property change event", 2, handler.getEvents().size());
}
@Test
public void testCascadingPropertyChangeHandlingSetBindingBeforeHandler() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
binder.addPropertyChangeHandler("**", handler);
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "UI change", handler.getEvents().get(0).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(),
handler.getEvents().get(0).getSource());
// This should not cause additional events to be fired
binder.setModel(new TestModel(), StateSync.FROM_MODEL);
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(1).getPropertyName());
assertEquals("Wrong property value in event", "model change", handler.getEvents().get(1).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(1).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(), handler.getEvents().get(1).getSource());
}
@Test
public void testCascadingPropertyChangeHandlingSetHandlerBeforeBinding() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class);
binder.addPropertyChangeHandler("**", handler);
binder.bind(textBox, "child.child.value");
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "UI change", handler.getEvents().get(0).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(),
handler.getEvents().get(0).getSource());
// This should not cause additional events to be fired
binder.setModel(new TestModel(), StateSync.FROM_MODEL);
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(1).getPropertyName());
assertEquals("Wrong property value in event", "model change", handler.getEvents().get(1).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(1).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(), handler.getEvents().get(1).getSource());
}
@Test
public void testCascadingPropertyChangeHandlingWithPropertyChain() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
binder.addPropertyChangeHandler("child.**", handler);
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "UI change", handler.getEvents().get(0).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(0).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(),
handler.getEvents().get(0).getSource());
// This should not cause additional events to be fired
binder.setModel(new TestModel(), StateSync.FROM_MODEL);
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(1).getPropertyName());
assertEquals("Wrong property value in event", "model change", handler.getEvents().get(1).getNewValue());
assertNull("Previous value should have been null", handler.getEvents().get(1).getOldValue());
assertEquals("Wrong event source", binder.getModel().getChild().getChild(), handler.getEvents().get(1).getSource());
}
@Test
public void testBinderRetainsPropertyChangeHandlersAfterModelChange() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "value");
binder.addPropertyChangeHandler(handler);
binder.addPropertyChangeHandler("value", handler);
binder.setModel(new TestModel());
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getValue());
assertEquals("Should have received exactly one property change event", 2, handler.getEvents().size());
binder.getModel().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 4, handler.getEvents().size());
}
@Test
public void testBinderRetainsPropertyChangeHandlersWithPropertyChainAfterModelChange() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
binder.addPropertyChangeHandler("child.child.value", handler);
binder.setModel(new TestModel());
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
}
@Test
public void testBinderRetainsCascadingPropertyChangeHandlerAfterModelChange() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
binder.addPropertyChangeHandler("**", handler);
binder.setModel(new TestModel());
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
}
@Test
public void testPropertyChangeEventsAreFiredDuringStateSync() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
textBox.setValue("UI change");
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class);
binder.addPropertyChangeHandler(handler);
binder.bind(textBox, "value", null, StateSync.FROM_UI);
assertEquals("Model not properly updated", "UI change", binder.getModel().getValue());
assertEquals("Should have received exactly one property change event", 1, handler.getEvents().size());
assertEquals("Wrong property name in event", "value", handler.getEvents().get(0).getPropertyName());
assertEquals("Wrong property value in event", "UI change", handler.getEvents().get(0).getNewValue());
}
@Test
public void testNewValueIsSetBeforePropertyChangeEventIsFired() {
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "value");
binder.getModel().setValue("Old Value");
class MyHandler implements PropertyChangeHandler<String> {
String observedValueWhenEventFired;
@Override
public void onPropertyChange(final PropertyChangeEvent<String> event) {
observedValueWhenEventFired = binder.getModel().getValue();
}
}
final MyHandler handler = new MyHandler();
binder.addPropertyChangeHandler(handler);
textBox.setValue("New Value", true);
assertEquals("New Value", handler.observedValueWhenEventFired);
binder.getModel().setValue("New New Value");
assertEquals("New New Value", handler.observedValueWhenEventFired);
}
@Test
public void testPropertyChangeHandlingWithWildcardDoesNotCascade() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
binder.addPropertyChangeHandler("child.*", handler);
textBox.setValue("UI change", true);
assertEquals("Model not properly updated", "UI change", binder.getModel().getChild().getChild().getValue());
assertEquals("Should have received no property change events", 0, handler.getEvents().size());
binder.getModel().getChild().getChild().setValue("model change");
assertEquals("Widget not properly updated", "model change", textBox.getText());
assertEquals("Should have received no property change events", 0, handler.getEvents().size());
}
@Test
public void testRemovingOfCascadedPropertyChangeHandling() {
final MockHandler handler = new MockHandler();
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "child.child.value");
final PropertyChangeUnsubscribeHandle childUnsubHandle = binder.addPropertyChangeHandler("child.**", handler);
final PropertyChangeUnsubscribeHandle childChildUnsubHandle = binder.addPropertyChangeHandler("child.child.*", handler);
textBox.setValue("UI change", true);
assertEquals("Should have received exactly two property change events", 2, handler.getEvents().size());
// Remove the binders
childUnsubHandle.unsubscribe();
childChildUnsubHandle.unsubscribe();
textBox.setValue("Second UI change", true);
assertEquals("Should have received no additional event", 2, handler.getEvents().size());
}
@Test
public void testWildcardFailsIfNotTheEndOfExpression() {
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class);
try {
binder.addPropertyChangeHandler("child.*.xx", new MockHandler());
fail("Expected InvalidPropertyExpressionException");
}
catch(final InvalidPropertyExpressionException e) {
// expected
}
}
@Test
public void testDoubleWildcardFailsIfNotTheEndOfExpression() {
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class);
try {
binder.addPropertyChangeHandler("**.xx", new MockHandler());
fail("Expected InvalidPropertyExpressionException");
}
catch(final InvalidPropertyExpressionException e) {
// expected
}
}
@Test
@SuppressWarnings("rawtypes")
public void testUpdateWidgetsInChangeHandlerDoesNotCauseRecursion() {
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class);
final List<PropertyChangeEvent> events = new ArrayList<>();
binder.addPropertyChangeHandler("value", new PropertyChangeHandler() {
@Override
public void onPropertyChange(final PropertyChangeEvent event) {
((BindableProxy) binder.getModel()).updateWidgets();
events.add(event);
}
});
binder.getModel().setValue("value");
assertEquals("Should have received exactly one event", 1, events.size());
}
@Test
public void testMutateHandlersInPropertyChangeEvent() {
final TextBox textBox = new TextBox();
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "value");
final List<PropertyChangeEvent<?>> observedEvents = new ArrayList<>();
final PropertyChangeHandler<String> handler = new PropertyChangeHandler<String>() {
@Override
public void onPropertyChange(final PropertyChangeEvent<String> event) {
observedEvents.add(event);
binder.addPropertyChangeHandler(new MockHandler());
}
};
binder.addPropertyChangeHandler(handler);
binder.addPropertyChangeHandler("value", handler);
try {
binder.getModel().setValue("test");
}
catch (final ConcurrentModificationException e) {
fail("Failed to mutate property change handlers in change event");
}
assertEquals("Should have received exactly 2 change events", 2, observedEvents.size());
}
@Test
public void testPropertyChangeHandlerIsRemovedIfRemoveCalledAfterUnbind() throws Exception {
final RefHolder<Integer> propertyChanges = new RefHolder<>();
propertyChanges.set(0);
final TextBox textBox = new TextBox();
final PropertyChangeHandler<String> testHandler = new PropertyChangeHandler<String>() {
@Override
public void onPropertyChange(final PropertyChangeEvent<String> event) {
propertyChanges.set(propertyChanges.get() + 1);
}
};
final DataBinder<TestModel> binder = DataBinder.forType(TestModel.class).bind(textBox, "value");
final PropertyChangeUnsubscribeHandle unsubHandle = binder.addPropertyChangeHandler(testHandler);
final TestModel model = binder.getModel();
model.setValue("hello");
assertEquals("Precondition failed: The handler should have been invoked for this change.", 1, (int) propertyChanges.get());
binder.unbind();
unsubHandle.unsubscribe();
model.setValue("good bye");
assertEquals("The handler should not have been invoked for this change since remove was called.", 1, (int) propertyChanges.get());
}
@Test
public void testDeclarativePropertyChangeHandler() throws Exception {
final ModelWithDeclarativeHandler unwrapped = new ModelWithDeclarativeHandler();
unwrapped.setStr(null);
unwrapped.setBool(false);
final DataBinder<ModelWithDeclarativeHandler> binder = DataBinder.forModel(unwrapped);
final ModelWithDeclarativeHandler model = binder.getModel();
model.setStr("foo");
model.setBool(true);
final List<PropertyChangeEvent<?>> expected = asList(
new PropertyChangeEvent<>(unwrapped, "str", null, "foo"),
new PropertyChangeEvent<>(unwrapped, "str", null, "foo"),
new PropertyChangeEvent<>(unwrapped, "bool", false, true),
new PropertyChangeEvent<>(unwrapped, "bool", false, true)
);
assertEquals(expected.size(), model.observedEvents().size());
for (int i = 0; i < expected.size(); i++) {
assertEquals(expected.get(i), model.observedEvents().get(i));
}
}
}