From 5d864b14b385e303f6796bd55969ac5c44124ae0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Johannes=20Dahlstr=C3=B6m?= Date: Thu, 4 Aug 2016 22:18:57 +0300 Subject: [PATCH] Implement new simple AbstractField Change-Id: I9addcf8bec802967b1dfa39512dd140b8a4e4a25 --- .../main/java/com/vaadin/data/HasValue.java | 153 +++++++++++++++ .../java/com/vaadin/event/ConnectorEvent.java | 18 ++ .../java/com/vaadin/event/Registration.java | 33 ++++ .../vaadin/legacy/ui/LegacyAbstractField.java | 51 ++--- .../com/vaadin/legacy/ui/LegacyField.java | 34 ++-- .../java/com/vaadin/ui/AbstractField.java | 182 ++++++++++++++++++ .../main/java/com/vaadin/ui/Component.java | 39 ++-- .../java/com/vaadin/ui/AbstractFieldTest.java | 122 ++++++++++++ 8 files changed, 582 insertions(+), 50 deletions(-) create mode 100644 server/src/main/java/com/vaadin/data/HasValue.java create mode 100644 server/src/main/java/com/vaadin/event/Registration.java create mode 100644 server/src/main/java/com/vaadin/ui/AbstractField.java create mode 100644 server/src/test/java/com/vaadin/ui/AbstractFieldTest.java diff --git a/server/src/main/java/com/vaadin/data/HasValue.java b/server/src/main/java/com/vaadin/data/HasValue.java new file mode 100644 index 0000000000..6032fcb2be --- /dev/null +++ b/server/src/main/java/com/vaadin/data/HasValue.java @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2014 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.data; + +import java.io.Serializable; +import java.util.function.Consumer; + +import com.vaadin.event.ConnectorEvent; +import com.vaadin.event.ConnectorEventListener; +import com.vaadin.event.Registration; +import com.vaadin.server.ClientConnector; + +/** + * A generic interface for field components and other user interface objects + * that have a user-editable value. Emits change events whenever the value is + * changed, either by the user or programmatically. + * + * @since + * @param + * the value type + */ +public interface HasValue extends Serializable { + + /** + * An event fired when the value of a {@code HasValue} changes. + * + * @param + * the value type + */ + public class ValueChange extends ConnectorEvent { + + private final V value; + private final boolean userOriginated; + + /** + * Creates a new {@code ValueChange} event containing the current value + * of the given value-bearing source connector. + * + * @param + * the type of the source connector + * @param source + * the source connector bearing the value, not null + * @param userOriginated + * {@code true} if this event originates from the client, + * {@code false} otherwise. + */ + public > ValueChange( + C source, boolean userOriginated) { + super(source); + this.value = source.getValue(); + this.userOriginated = userOriginated; + } + + /** + * Returns the new value of the source connector. + * + * @return the new value + */ + public V getValue() { + return value; + } + + /** + * Returns whether this event was triggered by user interaction, on the + * client side, or programmatically, on the server side. + * + * @return {@code true} if this event originates from the client, + * {@code false} otherwise. + */ + public boolean isUserOriginated() { + return userOriginated; + } + } + + /** + * A listener for value change events. + * + * @param + * the value type + * + * @see ValueChange + * @see Registration + */ + @FunctionalInterface + public interface ValueChangeListener extends Consumer>, + ConnectorEventListener { + + /** + * Invoked when this listener receives a value change event from an + * event source to which it has been added. + * + * @param event + * the received event, not null + */ + // In addition to customizing the Javadoc, this override is needed + // to make ReflectTools.findMethod work as expected. It uses + // Class.getDeclaredMethod, but even if it used getMethod instead, the + // superinterface argument type is Object, not Event, after type + // erasure. + @Override + public void accept(ValueChange event); + } + + /** + * Sets the value of this object. If the new value is not equal to + * {@code getValue()}, fires a value change event. May throw + * {@code IllegalArgumentException} if the value is not acceptable. + *

+ * Implementation note: the implementing class should document + * whether null values are accepted or not. + * + * @param value + * the new value + * @throws IllegalArgumentException + * if the value is invalid + */ + public void setValue(V value); + + /** + * Returns the current value of this object. + *

+ * Implementation note: the implementing class should document + * whether null values may be returned or not. + * + * @return the current value + */ + public V getValue(); + + /** + * Adds an {@link ValueChangeListener}. The listener is called when the + * value of this {@code hasValue} is changed either by the user or + * programmatically. + * + * @param listener + * the value change listener, not null + * @return a registration for the listener + */ + public Registration addValueChangeListener( + ValueChangeListener listener); +} diff --git a/server/src/main/java/com/vaadin/event/ConnectorEvent.java b/server/src/main/java/com/vaadin/event/ConnectorEvent.java index 3dc73b864a..9e9d3b00a6 100644 --- a/server/src/main/java/com/vaadin/event/ConnectorEvent.java +++ b/server/src/main/java/com/vaadin/event/ConnectorEvent.java @@ -20,11 +20,29 @@ import java.util.EventObject; import com.vaadin.server.ClientConnector; +/** + * A base class for user interface events fired by connectors. + * + * @author Vaadin Ltd. + * @since 7.0 + */ public abstract class ConnectorEvent extends EventObject { + + /** + * Creates a new event fired by the given source. + * + * @param source + * the source connector + */ public ConnectorEvent(ClientConnector source) { super(source); } + /** + * Returns the connector that fired the event. + * + * @return the source connector + */ public ClientConnector getConnector() { return (ClientConnector) getSource(); } diff --git a/server/src/main/java/com/vaadin/event/Registration.java b/server/src/main/java/com/vaadin/event/Registration.java new file mode 100644 index 0000000000..ab393e4750 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/Registration.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2014 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.event; + +import java.io.Serializable; + +/** + * A registration object for removing an event listener added to a source. + * + * @author Vaadin Ltd. + * @since + */ +@FunctionalInterface +public interface Registration extends Serializable { + + /** + * Removes the associated listener from the event source. + */ + public void remove(); +} diff --git a/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java b/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java index d1b013a64a..8cce999371 100644 --- a/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java +++ b/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java @@ -62,10 +62,10 @@ import com.vaadin.ui.declarative.DesignContext; *

* *

- * LegacyAbstractField also provides the {@link com.vaadin.data.Buffered} interface - * for buffering the data source value. By default the LegacyField is in write - * through-mode and {@link #setWriteThrough(boolean)}should be called to enable - * buffering. + * LegacyAbstractField also provides the {@link com.vaadin.data.Buffered} + * interface for buffering the data source value. By default the LegacyField is + * in write through-mode and {@link #setWriteThrough(boolean)}should be called + * to enable buffering. *

* *

@@ -75,10 +75,17 @@ import com.vaadin.ui.declarative.DesignContext; * * @author Vaadin Ltd. * @since 3.0 + * + * @deprecated This class is, apart from the rename, identical to the Vaadin 7 + * {@code com.vaadin.ui.AbstractField}. It is provided for + * compatibility and migration purposes. As of 8.0, new field + * implementations should extend the new + * {@link com.vaadin.ui.AbstractField} instead. */ @SuppressWarnings("serial") -public abstract class LegacyAbstractField extends AbstractComponent implements - LegacyField, Property.ReadOnlyStatusChangeListener, +@Deprecated +public abstract class LegacyAbstractField extends AbstractComponent + implements LegacyField, Property.ReadOnlyStatusChangeListener, Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier { /* Private members */ @@ -187,11 +194,11 @@ public abstract class LegacyAbstractField extends AbstractComponent implement } /** - * Returns the type of the LegacyField. The methods getValue and - * setValue must be compatible with this type: one must be able - * to safely cast the value returned from getValue to the given - * type and pass any variable assignable to this type as an argument to - * setValue. + * Returns the type of the LegacyField. The methods getValue + * and setValue must be compatible with this type: one must be + * able to safely cast the value returned from getValue to the + * given type and pass any variable assignable to this type as an argument + * to setValue. * * @return the type of the LegacyField */ @@ -834,9 +841,9 @@ public abstract class LegacyAbstractField extends AbstractComponent implement * Returns the current value (as returned by {@link #getValue()}) converted * to the data source type. *

- * This returns the same as {@link LegacyAbstractField#getValue()} if no converter - * has been set. The value is not necessarily the same as the data source - * value e.g. if the field is in buffered mode and has been modified. + * This returns the same as {@link LegacyAbstractField#getValue()} if no + * converter has been set. The value is not necessarily the same as the data + * source value e.g. if the field is in buffered mode and has been modified. *

* * @return The converted value that is compatible with the data source type @@ -870,7 +877,7 @@ public abstract class LegacyAbstractField extends AbstractComponent implement @Override public void addValidator(Validator validator) { if (validators == null) { - validators = new LinkedList(); + validators = new LinkedList<>(); } validators.add(validator); markAsDirty(); @@ -993,7 +1000,7 @@ public abstract class LegacyAbstractField extends AbstractComponent implement } } - List validationExceptions = new ArrayList(); + List validationExceptions = new ArrayList<>(); if (validators != null) { // Gets all the validation errors for (Validator v : validators) { @@ -1384,10 +1391,10 @@ public abstract class LegacyAbstractField extends AbstractComponent implement } /** - * Sets the internal field value. This is purely used by LegacyAbstractField to - * change the internal LegacyField value. It does not trigger valuechange events. - * It can be overridden by the inheriting classes to update all dependent - * variables. + * Sets the internal field value. This is purely used by LegacyAbstractField + * to change the internal LegacyField value. It does not trigger valuechange + * events. It can be overridden by the inheriting classes to update all + * dependent variables. * * Subclasses can also override {@link #getInternalValue()} if necessary. * @@ -1619,8 +1626,8 @@ public abstract class LegacyAbstractField extends AbstractComponent implement /** * A ready-made {@link ShortcutListener} that focuses the given - * {@link Focusable} (usually a {@link LegacyField}) when the keyboard shortcut is - * invoked. + * {@link Focusable} (usually a {@link LegacyField}) when the keyboard + * shortcut is invoked. * */ public static class FocusShortcut extends ShortcutListener { diff --git a/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java b/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java index a3ac84871d..ff38cf2859 100644 --- a/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java +++ b/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java @@ -17,32 +17,37 @@ package com.vaadin.legacy.ui; import com.vaadin.data.BufferedValidatable; +import com.vaadin.data.HasValue.ValueChange; import com.vaadin.data.Property; import com.vaadin.ui.Component; -import com.vaadin.ui.Component.Event; import com.vaadin.ui.Component.Focusable; /** - * LegacyField interface is implemented by all classes (field components) that have a - * value that the user can change through the user interface. + * LegacyField interface is implemented by all legacy field components that have + * a value that the user can change through the user interface. * - * LegacyField components are built upon the framework defined in the LegacyField interface - * and the {@link com.vaadin.LegacyAbstractField} base class. + * LegacyField components are built upon the framework defined in the + * LegacyField interface and the {@link com.vaadin.LegacyAbstractField} base + * class. * * The LegacyField interface inherits the {@link com.vaadin.ui.Component} * superinterface and also the {@link com.vaadin.ui.Property} interface to have * a value for the field. * - * * @author Vaadin Ltd. * - * @param T + * @param * the type of values in the field, which might not be the same type * as that of the data source if converters are used * - * @author IT Mill Ltd. + * @deprecated This interface is, apart from the rename, identical to the Vaadin + * 7 {@code com.vaadin.ui.Field}. It is provided for compatibility + * and migration purposes. As of 8.0, new field components should + * extend {@link com.vaadin.ui.AbstractField} instead. */ -public interface LegacyField extends Component, BufferedValidatable, Property, +@Deprecated +public interface LegacyField extends Component, BufferedValidatable, + Property, Property.ValueChangeNotifier, Property.ValueChangeListener, Property.Editor, Focusable { @@ -85,14 +90,18 @@ public interface LegacyField extends Component, BufferedValidatable, Property public String getRequiredError(); /** - * An Event object specifying the LegacyField whose value has been - * changed. + * An Event object specifying the LegacyField whose value has + * been changed. * * @author Vaadin Ltd. * @since 3.0 + * + * @deprecated As of 8.0, replaced by {@link ValueChange}. */ + @Deprecated @SuppressWarnings("serial") - public static class ValueChangeEvent extends Component.Event implements + public static class ValueChangeEvent extends + Component.Event implements Property.ValueChangeEvent { /** @@ -114,6 +123,7 @@ public interface LegacyField extends Component, BufferedValidatable, Property public Property getProperty() { return (Property) getSource(); } + } /** diff --git a/server/src/main/java/com/vaadin/ui/AbstractField.java b/server/src/main/java/com/vaadin/ui/AbstractField.java new file mode 100644 index 0000000000..7a9d1b9b0c --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/AbstractField.java @@ -0,0 +1,182 @@ +/* + * Copyright 2000-2014 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 java.lang.reflect.Method; +import java.util.Collection; +import java.util.Objects; + +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; + +import com.vaadin.data.HasValue; +import com.vaadin.event.Registration; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.util.ReflectTools; + +/** + * An abstract implementation of a field, or a {@code Component} allowing user + * input. Implements {@link HasValue} to represent the input value. Examples of + * typical field components include text fields, date pickers, and check boxes. + *

+ * This class replaces the Vaadin 7 {@code com.vaadin.ui.AbstractField} class. + * The old {@code AbstractField} is retained, under the new name + * {@link com.vaadin.legacy.ui.LegacyAbstractField}, for compatibility and + * migration purposes. + * + * @author Vaadin Ltd. + * @since + * + * @param + * the input value type + */ +public abstract class AbstractField extends AbstractComponent + implements HasValue { + + @Deprecated + private static final Method VALUE_CHANGE_METHOD = ReflectTools.findMethod( + ValueChangeListener.class, "accept", ValueChange.class); + + @Override + public void setValue(T value) { + setValue(value, false); + } + + /** + * Returns whether the value of this field can be changed by the user or + * not. By default fields are not read-only. + * + * @return {@code true} if this field is in read-only mode, {@code false} + * otherwise. + * + * @see #setReadOnly(boolean) + */ + @Override + public boolean isReadOnly() { + return super.isReadOnly(); + } + + /** + * Sets whether the value of this field can be changed by the user or not. A + * field in read-only mode typically looks visually different to signal to + * the user that the value cannot be edited. + *

+ * The server ignores (potentially forged) value change requests from the + * client to fields that are read-only. Programmatically changing the field + * value via {@link #setValue(T)} is still possible. + *

+ * The read-only mode is distinct from the + * {@linkplain Component#setEnabled(boolean) disabled} state. When disabled, + * a component cannot be interacted with at all, and its content should be + * considered irrelevant or not applicable. In contrast, the user should + * still be able to read the content and otherwise interact with a read-only + * field even though changing the value is disallowed. + * + * @param readOnly + * {@code true} to set read-only mode, {@code false} otherwise. + */ + @Override + public void setReadOnly(boolean readOnly) { + super.setReadOnly(readOnly); + } + + @Override + public Registration addValueChangeListener( + ValueChangeListener listener) { + Objects.requireNonNull(listener, "listener cannot be null"); + addListener(ValueChange.class, listener, VALUE_CHANGE_METHOD); + return () -> removeListener(ValueChange.class, listener); + } + + @Override + public void readDesign(Element design, DesignContext designContext) { + super.readDesign(design, designContext); + Attributes attr = design.attributes(); + if (attr.hasKey("readonly")) { + setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr, + Boolean.class)); + } + } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + AbstractField def = designContext.getDefaultInstance(this); + Attributes attr = design.attributes(); + DesignAttributeHandler.writeAttribute("readonly", attr, + super.isReadOnly(), def.isReadOnly(), Boolean.class); + } + + @Override + protected Collection getCustomAttributes() { + Collection attributes = super.getCustomAttributes(); + attributes.add("readonly"); + // must be handled by subclasses + attributes.add("value"); + return attributes; + } + + /** + * Sets the value of this field if it has changed and fires a value change + * event. If the value originates from the client and this field is + * read-only, does nothing. Invokes {@link #doSetValue(Object) doSetValue} + * to actually store the value. + * + * @param value + * the new value to set + * @return {@code true} if this event originates from the client, + * {@code false} otherwise. + */ + protected void setValue(T value, boolean userOriginated) { + if (userOriginated && isReadOnly()) { + return; + } + if (Objects.equals(value, getValue())) { + return; + } + doSetValue(value); + if (!userOriginated) { + markAsDirty(); + } + fireEvent(createValueChange(userOriginated)); + } + + /** + * Sets the value of this field. May do sanitization or throw + * {@code IllegalArgumentException} if the value is invalid. Typically saves + * the value to shared state. + * + * @param value + * the new value of the field + * @throws IllegalArgumentException + * if the value is invalid + */ + protected abstract void doSetValue(T value); + + /** + * Returns a new value change event instance. + * + * @param userOriginated + * {@code true} if this event originates from the client, + * {@code false} otherwise. + * @return the new event + */ + protected ValueChange createValueChange(boolean userOriginated) { + return new ValueChange<>(this, userOriginated); + } +} diff --git a/server/src/main/java/com/vaadin/ui/Component.java b/server/src/main/java/com/vaadin/ui/Component.java index 0e8b385128..9bfd823fc4 100644 --- a/server/src/main/java/com/vaadin/ui/Component.java +++ b/server/src/main/java/com/vaadin/ui/Component.java @@ -379,10 +379,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable { /** * Tests whether the component is in the read-only mode. The user can not - * change the value of a read-only component. As only {@link LegacyField} - * components normally have a value that can be input or changed by the - * user, this is mostly relevant only to field components, though not - * restricted to them. + * change the value of a read-only component. As only {@link AbstractField} + * or {@link LegacyField} components normally have a value that can be input + * or changed by the user, this is mostly relevant only to field components, + * though not restricted to them. * *

* Notice that the read-only mode only affects whether the user can change @@ -406,9 +406,9 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * can not change the value of a read-only component. * *

- * As only {@link LegacyField} components normally have a value that can be input - * or changed by the user, this is mostly relevant only to field components, - * though not restricted to them. + * As only {@link AbstractField} or{@link LegacyField} components normally + * have a value that can be input or changed by the user, this is mostly + * relevant only to field components, though not restricted to them. *

* *

@@ -596,7 +596,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * public class AttachExample extends CustomComponent { * public AttachExample() { * // ERROR: We can't access the application object yet. - * ClassResource r = new ClassResource("smiley.jpg", getApplication()); + * ClassResource r = new ClassResource("smiley.jpg", + * getApplication()); * Embedded image = new Embedded("Image:", r); * setCompositionRoot(image); * } @@ -622,7 +623,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * super.attach(); // Must call. * * // Now we know who ultimately owns us. - * ClassResource r = new ClassResource("smiley.jpg", getApplication()); + * ClassResource r = new ClassResource("smiley.jpg", + * getApplication()); * Embedded image = new Embedded("Image:", r); * setCompositionRoot(image); * } @@ -879,7 +881,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * getWindow().showNotification("Click!"); * * // Display source component and event class names - * status.setValue("Event from " + event.getSource().getClass().getName() + * status.setValue("Event from " + event.getSource().getClass() + * .getName() * + ": " + event.getClass().getName()); * } * } @@ -904,11 +907,13 @@ public interface Component extends ClientConnector, Sizeable, Serializable { *

          * public void componentEvent(Event event) {
          *     // Act according to the source of the event
-         *     if (event.getSource() == ok && event.getClass() == Button.ClickEvent.class)
+         *     if (event.getSource() == ok && event
+         *             .getClass() == Button.ClickEvent.class)
          *         getWindow().showNotification("Click!");
          * 
          *     // Display source component and event class names
-         *     status.setValue("Event from " + event.getSource().getClass().getName()
+         *     status.setValue("Event from " + event.getSource().getClass()
+         *             .getName()
          *             + ": " + event.getClass().getName());
          * }
          * 
@@ -956,7 +961,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * if (event.getSource() == ok) * getWindow().showNotification("Click!"); * - * status.setValue("Event from " + event.getSource().getClass().getName() + * status.setValue("Event from " + event.getSource().getClass() + * .getName() * + ": " + event.getClass().getName()); * } * } @@ -1028,9 +1034,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable { *

* Focus can be set with {@link #focus()}. This interface does not provide * an accessor that would allow finding out the currently focused component; - * focus information can be acquired for some (but not all) {@link LegacyField} - * components through the {@link com.vaadin.event.FieldEvents.FocusListener} - * and {@link com.vaadin.event.FieldEvents.BlurListener} interfaces. + * focus information can be acquired for some (but not all) + * {@link LegacyField} components through the + * {@link com.vaadin.event.FieldEvents.FocusListener} and + * {@link com.vaadin.event.FieldEvents.BlurListener} interfaces. *

* * @see FieldEvents diff --git a/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java b/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java new file mode 100644 index 0000000000..bdd63caaa4 --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java @@ -0,0 +1,122 @@ +package com.vaadin.ui; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.easymock.EasyMockSupport; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.HasValue.ValueChange; +import com.vaadin.data.HasValue.ValueChangeListener; +import com.vaadin.server.ClientConnector; + +public class AbstractFieldTest extends EasyMockSupport { + + class TextField extends AbstractField { + + String value = ""; + + @Override + public String getValue() { + return value; + } + + @Override + protected void doSetValue(String value) { + this.value = value; + } + } + + TextField field; + + ValueChangeListener l; + Capture> capture; + + @Before + public void setUp() { + field = new TextField(); + l = mockListener(); + capture = new Capture<>(); + } + + @Test + public void readOnlyFieldAcceptsValueChangeFromServer() { + field.setReadOnly(true); + field.setValue("foo"); + assertEquals("foo", field.getValue()); + } + + @Test + public void readOnlyFieldIgnoresValueChangeFromClient() { + field.setReadOnly(true); + field.setValue("bar", true); + assertEquals("", field.getValue()); + } + + @Test + public void valueChangeListenerInvoked() { + l.accept(EasyMock.capture(capture)); + replayAll(); + + field.setValue("foo"); + field.addValueChangeListener(l); + field.setValue("bar"); + + assertEventEquals(capture.getValue(), "bar", field, false); + + verifyAll(); + } + + @Test + public void valueChangeListenerInvokedFromClient() { + l.accept(EasyMock.capture(capture)); + replayAll(); + + field.setValue("foo"); + field.addValueChangeListener(l); + field.setValue("bar", true); + + assertEventEquals(capture.getValue(), "bar", field, true); + + verifyAll(); + } + + @Test + public void valueChangeListenerNotInvokedIfValueUnchanged() { + // expect zero invocations of l + replayAll(); + + field.setValue("foo"); + field.addValueChangeListener(l); + field.setValue("foo"); + + verifyAll(); + } + + @Test + public void valueChangeListenerNotInvokedAfterRemove() { + // expect zero invocations of l + replayAll(); + + field.addValueChangeListener(l).remove(); + field.setValue("foo"); + + verifyAll(); + } + + @SuppressWarnings("unchecked") + private ValueChangeListener mockListener() { + return createStrictMock(ValueChangeListener.class); + } + + private void assertEventEquals(ValueChange e, String value, + ClientConnector source, boolean userOriginated) { + assertEquals("event value", value, e.getValue()); + assertSame("event source", source, e.getSource()); + assertSame("event source connector", source, e.getConnector()); + assertEquals("event from user", userOriginated, e.isUserOriginated()); + } +} -- 2.39.5