diff options
3 files changed, 176 insertions, 14 deletions
diff --git a/server/src/main/java/com/vaadin/ui/AbstractDateField.java b/server/src/main/java/com/vaadin/ui/AbstractDateField.java index 48bc3e13f5..f03c900b97 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractDateField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractDateField.java @@ -38,10 +38,9 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; -import elemental.json.Json; +import com.googlecode.gentyref.GenericTypeReflector; 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; @@ -64,6 +63,8 @@ import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.util.TimeZoneUtil; +import elemental.json.Json; + /** * A date editor component with {@link LocalDate} as an input value. * @@ -107,14 +108,14 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & newDate = reconstructDateFromFields(resolutions, oldDate); } - boolean parseErrorWasSet = currentParseErrorMessage != null; + boolean parseErrorWasSet = currentErrorMessage != null; hasChanges |= !Objects.equals(dateString, newDateString) || !Objects.equals(oldDate, newDate) || parseErrorWasSet; if (hasChanges) { dateString = newDateString; - currentParseErrorMessage = null; + currentErrorMessage = null; if (newDateString == null || newDateString.isEmpty()) { boolean valueChanged = setValue(newDate, true); if (!valueChanged && parseErrorWasSet) { @@ -138,8 +139,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & }); if (parsedDate.isError()) { dateString = null; - currentParseErrorMessage = parsedDate - .getMessage().orElse("Parsing error"); + currentErrorMessage = parsedDate.getMessage() + .orElse("Parsing error"); if (!isDifferentValue(null)) { doSetValue(null); @@ -187,7 +188,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & private String dateString = ""; - private String currentParseErrorMessage; + private String currentErrorMessage; private String defaultParseErrorMessage = "Date format not recognized"; @@ -607,7 +608,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & */ @Override public void setValue(T value) { - currentParseErrorMessage = null; + currentErrorMessage = null; /* * First handle special case when the client side component have a date * string but value is null (e.g. unparsable date string typed in by the @@ -783,16 +784,16 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & new ValueContext(this, this)); if (result.isError()) { - currentParseErrorMessage = getDateOutOfRangeMessage(); + currentErrorMessage = getDateOutOfRangeMessage(); } - getState().parsable = currentParseErrorMessage == null; + getState().parsable = currentErrorMessage == null; ErrorMessage errorMessage; - if (currentParseErrorMessage == null) { + if (currentErrorMessage == null) { errorMessage = null; } else { - errorMessage = new UserError(currentParseErrorMessage); + errorMessage = new UserError(currentErrorMessage); } setComponentError(errorMessage); @@ -876,9 +877,27 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & return new Validator<T>() { @Override public ValidationResult apply(T value, ValueContext context) { - if (currentParseErrorMessage != null) { - return ValidationResult.error(currentParseErrorMessage); + + // currentErrorMessage contains two type of messages, one is + // DateOutOfRangeMessage and the other one is the ParseError + if (currentErrorMessage != null) { + if (currentErrorMessage + .equals(getDateOutOfRangeMessage())) { + // if the currentErrorMessage is DateOutOfRangeMessage, + // then need to double check whether the error message + // has been updated, that is because of #11276. + ValidationResult validationResult = getRangeValidator() + .apply(value, context); + if (validationResult.isError()) { + return ValidationResult.error(currentErrorMessage); + } + } else { + // if the current Error is parsing error, pass it to the + // ValidationResult + return ValidationResult.error(currentErrorMessage); + } } + // Pass to range validator. return getRangeValidator().apply(value, context); } diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldBinderCrossValidation.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldBinderCrossValidation.java new file mode 100644 index 0000000000..21685f6523 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldBinderCrossValidation.java @@ -0,0 +1,103 @@ +package com.vaadin.tests.components.datefield; + +import java.time.LocalDate; +import java.util.Objects; + +import com.gargoylesoftware.htmlunit.javascript.host.html.FormField; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.data.Binder; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.ui.ErrorLevel; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Button; +import com.vaadin.ui.DateField; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +@Widgetset("com.vaadin.DefaultWidgetSet") +public class DateFieldBinderCrossValidation extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + HorizontalLayout horizontalLayout = new HorizontalLayout(); + Label label = new Label(); + label.setId("status"); + + Binder<FromToModel> fromToModelBinder = new Binder<>(); + + DateField fromField = new DateField(); + fromField.setId("from-field"); + fromField.setDateFormat("yyyy/MM/dd"); + final Binder.Binding<FromToModel, LocalDate> fromBinding = fromToModelBinder + .forField(fromField).asRequired() + .bind(FromToModel::getFromDate, FromToModel::setFromDate); + DateField toField = new DateField(); + toField.setId("to-field"); + toField.setDateFormat("yyyy/MM/dd"); + final Binder.Binding<FromToModel, LocalDate> toBinding = fromToModelBinder + .forField(toField).asRequired() + .bind(FromToModel::getToDate, FromToModel::setToDate); + + fromField.addValueChangeListener(e -> { + toField.setRangeStart(e.getValue()); + if (toField.getValue() != null) { + toBinding.validate(); + } + label.setValue("from field is " + fromField.getErrorMessage() + + ". To field is " + toField.getErrorMessage()); + }); + toField.addValueChangeListener(e -> { + fromField.setRangeEnd(e.getValue()); + if (fromField.getValue() != null) { + fromBinding.validate(); + } + label.setValue("from field is " + fromField.getErrorMessage() + + ". To field is " + toField.getErrorMessage()); + }); + + horizontalLayout.addComponents(fromField, toField, label); + + addComponents(horizontalLayout); + } + + private static class FromToModel { + + private LocalDate fromDate; + private LocalDate toDate; + + public LocalDate getFromDate() { + return fromDate; + } + + public void setFromDate(LocalDate fromDate) { + this.fromDate = fromDate; + } + + public LocalDate getToDate() { + return toDate; + } + + public void setToDate(LocalDate toDate) { + this.toDate = toDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof FromToModel)) + return false; + FromToModel that = (FromToModel) o; + return Objects.equals(getFromDate(), that.getFromDate()) + && Objects.equals(getToDate(), that.getToDate()); + } + + @Override + public int hashCode() { + return Objects.hash(getFromDate(), getToDate()); + } + } +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldBinderCrossValidationTest.java b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldBinderCrossValidationTest.java new file mode 100644 index 0000000000..77bf68d9a3 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldBinderCrossValidationTest.java @@ -0,0 +1,40 @@ +package com.vaadin.tests.components.datefield; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.DateFieldElement; +import com.vaadin.testbench.elements.LabelElement; +import com.vaadin.tests.tb3.SingleBrowserTest; + +import static org.junit.Assert.assertEquals; + +public class DateFieldBinderCrossValidationTest extends SingleBrowserTest { + + private final static String EXPECTED_ERROR = "from field is Date is out of allowed range. To field is Date is out of allowed range"; + private final static String EXPECTED_NULL_ERROR = "from field is null. To field is null"; + + @Test + public void makeBothFieldInvalidThenValid() { + openTestURL(); + + DateFieldElement fromField = $(DateFieldElement.class).id("from-field"); + WebElement fromFieldText = fromField.findElement(By.tagName("input")); + DateFieldElement toField = $(DateFieldElement.class).id("to-field"); + WebElement toFieldText = toField.findElement(By.tagName("input")); + LabelElement label = $(LabelElement.class).id("status"); + + fromFieldText.sendKeys("2019/01/01", Keys.ENTER); + toFieldText.sendKeys("2018/02/02", Keys.ENTER); + + assertEquals("Error message should contain the information", + EXPECTED_ERROR, label.getText()); + + fromFieldText.clear(); + fromFieldText.sendKeys("2018/01/01", Keys.ENTER); + assertEquals("Error message should be null", EXPECTED_NULL_ERROR, + label.getText()); + } +} |