/* * 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)); } } }