diff options
author | Teemu Suo-Anttila <tsuoanttila@users.noreply.github.com> | 2017-07-03 10:51:47 +0300 |
---|---|---|
committer | Henri Sara <henri.sara@gmail.com> | 2017-07-03 10:51:47 +0300 |
commit | dde0f91af4a21ae9a1d141bed46e3030abc2c2d9 (patch) | |
tree | 3f3c05fd3e6cb1114228853ac73ce959262c31bb | |
parent | 7d6408ac56c70c8463856a52d831629d8513625c (diff) | |
download | vaadin-framework-dde0f91af4a21ae9a1d141bed46e3030abc2c2d9.tar.gz vaadin-framework-dde0f91af4a21ae9a1d141bed46e3030abc2c2d9.zip |
Add internal state validators for HasValue (#9532)
This also fixes/improves AbstractDateField validation.
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()); + } +} |