--- /dev/null
+package com.vaadin.tests.server.components;\r
+\r
+import junit.framework.TestCase;\r
+\r
+import org.easymock.EasyMock;\r
+\r
+import com.vaadin.data.Property.ValueChangeEvent;\r
+import com.vaadin.data.Property.ValueChangeListener;\r
+import com.vaadin.data.Property.ValueChangeNotifier;\r
+import com.vaadin.data.util.ObjectProperty;\r
+import com.vaadin.ui.AbstractField;\r
+\r
+/**\r
+ * Base class for tests for checking that value change listeners for fields are\r
+ * not called exactly once when they should be, and not at other times.\r
+ * \r
+ * Does not check all cases (e.g. properties that do not implement\r
+ * {@link ValueChangeNotifier}).\r
+ * \r
+ * Subclasses should implement {@link #setValue()} and call\r
+ * <code>super.setValue(AbstractField)</code>. Also, subclasses should typically\r
+ * override {@link #setValue(AbstractField)} to set the field value via\r
+ * <code>changeVariables()</code>.\r
+ */\r
+public abstract class TestFieldValueChange extends TestCase {\r
+\r
+ private AbstractField field;\r
+ private ValueChangeListener listener;\r
+\r
+ protected void setUp(AbstractField field) throws Exception {\r
+ this.field = field;\r
+ listener = EasyMock.createStrictMock(ValueChangeListener.class);\r
+\r
+ }\r
+\r
+ /**\r
+ * Test that listeners are not called when they have been unregistered.\r
+ */\r
+ public void testRemoveListener() {\r
+ getField().setPropertyDataSource(new ObjectProperty(""));\r
+ getField().setWriteThrough(true);\r
+ getField().setReadThrough(true);\r
+\r
+ // Expectations and start test\r
+ listener.valueChange(EasyMock.isA(ValueChangeEvent.class));\r
+ EasyMock.replay(listener);\r
+\r
+ // Add listener and set the value -> should end up in listener once\r
+ getField().addListener(listener);\r
+ setValue(getField());\r
+\r
+ // Ensure listener was called once\r
+ EasyMock.verify(listener);\r
+\r
+ // Remove the listener and set the value -> should not end up in\r
+ // listener\r
+ getField().removeListener(listener);\r
+ setValue(getField());\r
+\r
+ // Ensure listener still has been called only once\r
+ EasyMock.verify(listener);\r
+ }\r
+\r
+ /**\r
+ * Common unbuffered case: both writeThrough (auto-commit) and readThrough\r
+ * are on. Calling commit() should not cause notifications.\r
+ * \r
+ * Using the readThrough mode allows changes made to the property value to\r
+ * be seen in some cases also when there is no notification of value change\r
+ * from the property.\r
+ * \r
+ * Field value change notifications closely mirror value changes of the data\r
+ * source behind the field.\r
+ */\r
+ public void testWriteThroughReadThrough() {\r
+ getField().setPropertyDataSource(new ObjectProperty(""));\r
+ getField().setWriteThrough(true);\r
+ getField().setReadThrough(true);\r
+\r
+ expectValueChangeFromSetValueNotCommit();\r
+ }\r
+\r
+ /**\r
+ * Fully buffered use where the data source is neither read nor modified\r
+ * during editing, and is updated at commit().\r
+ * \r
+ * Field value change notifications reflect the buffered value in the field,\r
+ * not the original data source value changes.\r
+ */\r
+ public void testNoWriteThroughNoReadThrough() {\r
+ getField().setPropertyDataSource(new ObjectProperty(""));\r
+ getField().setWriteThrough(false);\r
+ getField().setReadThrough(false);\r
+\r
+ expectValueChangeFromSetValueNotCommit();\r
+ }\r
+\r
+ /**\r
+ * Less common partly buffered case: writeThrough (auto-commit) is on and\r
+ * readThrough is off. Calling commit() should not cause notifications.\r
+ * \r
+ * Without readThrough activated, changes to the data source that do not\r
+ * cause notifications are not reflected by the field value.\r
+ * \r
+ * Field value change notifications correspond to changes made to the data\r
+ * source value through the text field or the (notifying) property.\r
+ */\r
+ public void testWriteThroughNoReadThrough() {\r
+ getField().setPropertyDataSource(new ObjectProperty(""));\r
+ getField().setWriteThrough(true);\r
+ getField().setReadThrough(false);\r
+\r
+ expectValueChangeFromSetValueNotCommit();\r
+ }\r
+\r
+ /**\r
+ * Partly buffered use where the data source is read but not nor modified\r
+ * during editing, and is updated at commit().\r
+ * \r
+ * When used like this, a field is updated from the data source if necessary\r
+ * when its value is requested and the property value has changed but the\r
+ * field has not been modified in its buffer.\r
+ * \r
+ * Field value change notifications reflect the buffered value in the field,\r
+ * not the original data source value changes.\r
+ */\r
+ public void testNoWriteThroughReadThrough() {\r
+ getField().setPropertyDataSource(new ObjectProperty(""));\r
+ getField().setWriteThrough(false);\r
+ getField().setReadThrough(true);\r
+\r
+ expectValueChangeFromSetValueNotCommit();\r
+ }\r
+\r
+ protected void expectValueChangeFromSetValueNotCommit() {\r
+ // Expectations and start test\r
+ listener.valueChange(EasyMock.isA(ValueChangeEvent.class));\r
+ EasyMock.replay(listener);\r
+\r
+ // Add listener and set the value -> should end up in listener once\r
+ getField().addListener(listener);\r
+ setValue(getField());\r
+\r
+ // Ensure listener was called once\r
+ EasyMock.verify(listener);\r
+\r
+ // commit\r
+ getField().commit();\r
+\r
+ // Ensure listener was not called again\r
+ EasyMock.verify(listener);\r
+ }\r
+\r
+ protected AbstractField getField() {\r
+ return field;\r
+ }\r
+\r
+ /**\r
+ * Override in subclasses to set value with changeVariables().\r
+ */\r
+ protected void setValue(AbstractField field) {\r
+ field.setValue("newValue");\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.vaadin.tests.server.components;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import com.vaadin.ui.AbstractField;\r
+import com.vaadin.ui.TextField;\r
+\r
+/**\r
+ * Check that the value change listener for a text field is triggered exactly\r
+ * once when setting the value, at the correct time.\r
+ * \r
+ * See <a href="http://dev.vaadin.com/ticket/4394">Ticket 4394</a>.\r
+ */\r
+public class TestTextFieldValueChange extends TestFieldValueChange {\r
+\r
+ @Override\r
+ protected void setUp() throws Exception {\r
+ super.setUp(new TextField());\r
+ }\r
+\r
+ /**\r
+ * Case where the text field only uses its internal buffer, no external\r
+ * property data source.\r
+ */\r
+ public void testNoDataSource() {\r
+ getField().setPropertyDataSource(null);\r
+\r
+ expectValueChangeFromSetValueNotCommit();\r
+ }\r
+\r
+ @Override\r
+ protected void setValue(AbstractField field) {\r
+ Map<String, Object> variables = new HashMap<String, Object>();\r
+ variables.put("text", "newValue");\r
+ field.changeVariables(field, variables);\r
+ }\r
+\r
+}\r