summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeemu Suo-Anttila <tsuoanttila@users.noreply.github.com>2017-07-03 10:51:47 +0300
committerHenri Sara <henri.sara@gmail.com>2017-07-03 10:51:47 +0300
commitdde0f91af4a21ae9a1d141bed46e3030abc2c2d9 (patch)
tree3f3c05fd3e6cb1114228853ac73ce959262c31bb
parent7d6408ac56c70c8463856a52d831629d8513625c (diff)
downloadvaadin-framework-dde0f91af4a21ae9a1d141bed46e3030abc2c2d9.tar.gz
vaadin-framework-dde0f91af4a21ae9a1d141bed46e3030abc2c2d9.zip
Add internal state validators for HasValue (#9532)
This also fixes/improves AbstractDateField validation.
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java3
-rw-r--r--server/src/main/java/com/vaadin/data/HasValue.java15
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractDateField.java20
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValid.java28
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValidTest.java72
5 files changed, 136 insertions, 2 deletions
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index 26720a3274..ae467be123 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -1190,7 +1190,8 @@ public class Binder<BEAN> implements Serializable {
getStatusLabel().ifPresent(label -> label.setValue(""));
return createBinding(field, createNullRepresentationAdapter(field),
- this::handleValidationStatus);
+ this::handleValidationStatus)
+ .withValidator(field.getDefaultValidator());
}
/**
diff --git a/server/src/main/java/com/vaadin/data/HasValue.java b/server/src/main/java/com/vaadin/data/HasValue.java
index 935f365e10..aad436a089 100644
--- a/server/src/main/java/com/vaadin/data/HasValue.java
+++ b/server/src/main/java/com/vaadin/data/HasValue.java
@@ -292,4 +292,19 @@ public interface HasValue<V> extends Serializable {
public default void clear() {
setValue(getEmptyValue());
}
+
+ /**
+ * Returns a validator that checks the internal state of the HasValue. This
+ * should be overridden for components with internal value conversion or
+ * validation, eg. when the user is providing a string that has to be parsed
+ * into a date. An invalid input from user will be exposed to
+ * a {@link Binder} and can be seen as a validation failure.
+ *
+ * @since 8.1
+ * @return internal state validator
+ * @see Binder#validate()
+ */
+ public default Validator<V> getDefaultValidator() {
+ return Validator.alwaysPass();
+ }
}
diff --git a/server/src/main/java/com/vaadin/ui/AbstractDateField.java b/server/src/main/java/com/vaadin/ui/AbstractDateField.java
index 0e738de253..0b1f60c0e4 100644
--- a/server/src/main/java/com/vaadin/ui/AbstractDateField.java
+++ b/server/src/main/java/com/vaadin/ui/AbstractDateField.java
@@ -38,6 +38,7 @@ import org.jsoup.nodes.Element;
import com.googlecode.gentyref.GenericTypeReflector;
import com.vaadin.data.Result;
import com.vaadin.data.ValidationResult;
+import com.vaadin.data.Validator;
import com.vaadin.data.ValueContext;
import com.vaadin.data.validator.RangeValidator;
import com.vaadin.event.FieldEvents.BlurEvent;
@@ -302,7 +303,10 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
markAsDirty();
} else {
- parsedDate.ifOk(value -> setValue(value, true));
+ parsedDate.ifOk(value -> {
+ currentParseErrorMessage = null;
+ setValue(value, true);
+ });
/*
* Ensure the value is sent to the client if the value is
@@ -315,6 +319,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
} else if (newDate != oldDate
&& (newDate == null || !newDate.equals(oldDate))) {
+ currentParseErrorMessage = null;
setValue(newDate, true); // Don't require a repaint, client
// updates itself
} else if (!uiHasValidDateString) {
@@ -767,4 +772,17 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
.collect(Collectors.toList());
}
+ @Override
+ public Validator<T> getDefaultValidator() {
+ return new Validator<T>() {
+ @Override
+ public ValidationResult apply(T value, ValueContext context) {
+ if (currentParseErrorMessage != null) {
+ return ValidationResult.error(currentParseErrorMessage);
+ }
+ // Pass to range validator.
+ return getRangeValidator().apply(value, context);
+ }
+ };
+ }
}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValid.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValid.java
new file mode 100644
index 0000000000..cbc87e5386
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValid.java
@@ -0,0 +1,28 @@
+package com.vaadin.tests.components.datefield;
+
+import java.time.LocalDate;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.Binder;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.DateField;
+import com.vaadin.ui.Notification;
+
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class DateFieldFaultyInputNotValid extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ DateField df = new DateField();
+ Binder<Void> binder = new Binder<>();
+ binder.forField(df).bind(v -> LocalDate.now(), (v, t) -> {
+ /* NO-OP */ });
+ df.setRangeStart(LocalDate.now().minusDays(2));
+ addComponent(df);
+ addComponent(new Button("Validate", e -> Notification
+ .show(binder.validate().isOk() ? "OK" : "Fail")));
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValidTest.java b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValidTest.java
new file mode 100644
index 0000000000..1fd335b671
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldFaultyInputNotValidTest.java
@@ -0,0 +1,72 @@
+package com.vaadin.tests.components.datefield;
+
+import java.time.LocalDate;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.DateFieldElement;
+import com.vaadin.testbench.elements.NotificationElement;
+import com.vaadin.tests.tb3.SingleBrowserTest;
+
+public class DateFieldFaultyInputNotValidTest extends SingleBrowserTest {
+
+ @Test
+ public void testEmptyDateFieldOK() {
+ openTestURL();
+ $(ButtonElement.class).first().click();
+ Assert.assertEquals("Empty DateField should be ok", "OK",
+ $(NotificationElement.class).first().getText());
+ }
+
+ @Test
+ public void testFaultyUserInput() {
+ openTestURL();
+ DateFieldElement dateField = $(DateFieldElement.class).first();
+ dateField.setDate(LocalDate.now());
+
+ $(ButtonElement.class).first().click();
+ Assert.assertEquals("Current date should be ok", "OK",
+ $(NotificationElement.class).first().getText());
+ $(NotificationElement.class).first().close();
+
+ dateField.findElement(By.tagName("input")).click();
+ new Actions(getDriver()).sendKeys("asd").perform();
+
+ $(ButtonElement.class).first().click();
+ Assert.assertEquals("Added 'asd' should make date not parse correctly.",
+ "Fail", $(NotificationElement.class).first().getText());
+ }
+
+ @Test
+ public void testDateOutOfRange() {
+ openTestURL();
+ DateFieldElement dateField = $(DateFieldElement.class).first();
+ dateField.setDate(LocalDate.now());
+
+ $(ButtonElement.class).first().click();
+ Assert.assertEquals("Current date should be ok", "OK",
+ $(NotificationElement.class).first().getText());
+ $(NotificationElement.class).first().close();
+
+ dateField.setDate(LocalDate.now().minusDays(7));
+
+ $(ButtonElement.class).first().click();
+ Assert.assertEquals("Last week should not be ok", "Fail",
+ $(NotificationElement.class).first().getText());
+ }
+
+ @Test
+ public void testParseErrorClearedOnValidInput() {
+ testFaultyUserInput();
+ $(NotificationElement.class).first().close();
+
+ $(DateFieldElement.class).first().setDate(LocalDate.now());
+ $(ButtonElement.class).first().click();
+ Assert.assertEquals("Current date should be ok", "OK",
+ $(NotificationElement.class).first().getText());
+ }
+}