diff --git a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java index a8aeb2903a8..9bcd6dfa777 100644 --- a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java +++ b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java @@ -169,9 +169,9 @@ public class DatePicker private Validator defaultValidator = (value, context) -> { boolean fromComponent = context == null; - if (unparsableValue != null && fallbackParserErrorMessage != null) { + if (isInputUnparsable() && fallbackParserErrorMessage != null) { return ValidationResult.error(fallbackParserErrorMessage); - } else if (unparsableValue != null) { + } else if (isInputUnparsable()) { return ValidationResult.error(getI18nErrorMessage( DatePickerI18n::getBadInputErrorMessage)); } @@ -668,14 +668,28 @@ private void fireValidationStatusChangeEvent() { /** * Returns whether the input element has a value or not. + *

+ * For internal use only. * * @return true if the input element's value is populated, * false otherwise + * @deprecated Since v24.8 */ + @Deprecated(since = "24.8") protected boolean isInputValuePresent() { return !getInputElementValue().isEmpty(); } + /** + * Returns whether the input value is unparsable. + * + * @return true if the input element's value is populated and + * unparsable, false otherwise + */ + protected boolean isInputUnparsable() { + return unparsableValue != null; + } + /** * Gets the value of the input element. This value is updated on the server * when the web component dispatches a `change` or `unparsable-change` @@ -762,7 +776,7 @@ private Result runFallbackParser(String s) { @Override public void setValue(LocalDate value) { LocalDate oldValue = getValue(); - if (oldValue == null && value == null && unparsableValue != null) { + if (oldValue == null && value == null && isInputUnparsable()) { // When the value is programmatically cleared while the field // contains an unparsable input, ValueChangeEvent isn't fired, // so we need to call setModelValue manually to clear the bad @@ -797,7 +811,7 @@ protected void setModelValue(LocalDate newModelValue, boolean fromClient) { try { isFallbackParserRunning = true; - if (fallbackParser != null && unparsableValue != null) { + if (fallbackParser != null && isInputUnparsable()) { Result result = runFallbackParser(unparsableValue); if (result.isError()) { fallbackParserErrorMessage = result.getMessage() diff --git a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationPage.java b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationPage.java index a2fcc85c9c9..192b62c0d46 100644 --- a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationPage.java +++ b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationPage.java @@ -30,15 +30,17 @@ public class BasicValidationPage public static final String CLEAR_VALUE_BUTTON = "clear-value-button"; public static final String REQUIRED_ERROR_MESSAGE = "Field is required"; - public static final String BAD_INPUT_ERROR_MESSAGE = "Value has incorrect format"; - public static final String MIN_ERROR_MESSAGE = "Value is too small"; - public static final String MAX_ERROR_MESSAGE = "Value is too big"; + public static final String BAD_INPUT_ERROR_MESSAGE = "Invalid date format"; + public static final String INCOMPLETE_INPUT_ERROR_MESSAGE = "Must fill in both date and time"; + public static final String MIN_ERROR_MESSAGE = "Date is too early"; + public static final String MAX_ERROR_MESSAGE = "Date is too late"; public BasicValidationPage() { super(); testField.setI18n(new DateTimePicker.DateTimePickerI18n() .setRequiredErrorMessage(REQUIRED_ERROR_MESSAGE) + .setIncompleteInputErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE) .setBadInputErrorMessage(BAD_INPUT_ERROR_MESSAGE) .setMinErrorMessage(MIN_ERROR_MESSAGE) .setMaxErrorMessage(MAX_ERROR_MESSAGE)); @@ -63,6 +65,12 @@ public BasicValidationPage() { } protected DateTimePicker createTestField() { - return new DateTimePicker(); + return new DateTimePicker() { + @Override + protected void validate() { + super.validate(); + incrementServerValidationCounter(); + } + }; } } diff --git a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationPage.java b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationPage.java index b0bac82ca5d..2637bc9f908 100644 --- a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationPage.java +++ b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationPage.java @@ -32,6 +32,7 @@ public class BinderValidationPage public static final String REQUIRED_ERROR_MESSAGE = "Field is required"; public static final String BAD_INPUT_ERROR_MESSAGE = "Value has incorrect format"; + public static final String INCOMPLETE_INPUT_ERROR_MESSAGE = "Value is incomplete"; public static final String MIN_ERROR_MESSAGE = "Value is too small"; public static final String MAX_ERROR_MESSAGE = "Value is too big"; public static final String UNEXPECTED_VALUE_ERROR_MESSAGE = "Value does not match the expected value"; @@ -63,6 +64,7 @@ public BinderValidationPage() { testField.setI18n(new DateTimePicker.DateTimePickerI18n() .setBadInputErrorMessage(BAD_INPUT_ERROR_MESSAGE) + .setIncompleteInputErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE) .setMinErrorMessage(MIN_ERROR_MESSAGE) .setMaxErrorMessage(MAX_ERROR_MESSAGE)); @@ -88,6 +90,12 @@ public BinderValidationPage() { } protected DateTimePicker createTestField() { - return new DateTimePicker(); + return new DateTimePicker() { + @Override + protected void validate() { + super.validate(); + incrementServerValidationCounter(); + } + }; } } diff --git a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationIT.java b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationIT.java index a917fc027c2..52dff5b280e 100644 --- a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationIT.java +++ b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationIT.java @@ -17,6 +17,7 @@ import static com.vaadin.flow.component.datetimepicker.validation.BasicValidationPage.BAD_INPUT_ERROR_MESSAGE; import static com.vaadin.flow.component.datetimepicker.validation.BasicValidationPage.CLEAR_VALUE_BUTTON; +import static com.vaadin.flow.component.datetimepicker.validation.BasicValidationPage.INCOMPLETE_INPUT_ERROR_MESSAGE; import static com.vaadin.flow.component.datetimepicker.validation.BasicValidationPage.MAX_ERROR_MESSAGE; import static com.vaadin.flow.component.datetimepicker.validation.BasicValidationPage.MAX_INPUT; import static com.vaadin.flow.component.datetimepicker.validation.BasicValidationPage.MIN_ERROR_MESSAGE; @@ -59,7 +60,8 @@ public void triggerBlur_assertValidity() { timeInput.sendKeys(Keys.TAB); assertServerValid(); assertClientValid(); - assertErrorMessage(""); + assertErrorMessage(null); + assertValidationCount(0); } @Test @@ -68,78 +70,99 @@ public void required_triggerBlur_assertValidity() { dateInput.sendKeys(Keys.TAB); timeInput.sendKeys(Keys.TAB); - assertServerInvalid(); - assertClientInvalid(); - assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertServerValid(); + assertClientValid(); + assertErrorMessage(null); + assertValidationCount(0); + } + + @Test + public void required_changeInputTemporarily_triggerBlur_assertValidity() { + $("button").id(REQUIRED_BUTTON).click(); + dateInput.sendKeys("1", Keys.BACK_SPACE, Keys.ENTER); + dateInput.sendKeys(Keys.TAB); + timeInput.sendKeys(Keys.TAB); + assertServerValid(); + assertClientValid(); + assertErrorMessage(null); + assertValidationCount(0); } @Test public void required_changeValue_assertValidity() { $("button").id(REQUIRED_BUTTON).click(); - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "12:00"); + setValue("1/1/2000", "12:00"); assertServerValid(); assertClientValid(); assertErrorMessage(""); + assertValidationCount(1); + + setInputValue(timeInput, ""); + assertServerInvalid(); + assertServerInvalid(); + assertErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE); + assertValidationCount(1); setInputValue(dateInput, ""); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(1); setInputValue(timeInput, ""); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(0); - setInputValue(dateInput, "INVALID"); setInputValue(timeInput, "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, ""); setInputValue(timeInput, ""); timeInput.sendKeys(Keys.TAB); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(1); } @Test public void min_changeValue_assertValidity() { $("input").id(MIN_INPUT).sendKeys("2000-02-02T12:00", Keys.ENTER); - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "11:00"); + setValue("1/1/2000", "11:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MIN_ERROR_MESSAGE); + assertValidationCount(2); setInputValue(dateInput, "2/2/2000"); - setInputValue(timeInput, "11:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MIN_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "12:00"); assertClientValid(); assertServerValid(); assertErrorMessage(""); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "13:00"); assertClientValid(); assertServerValid(); assertErrorMessage(""); + assertValidationCount(1); - setInputValue(dateInput, "3/3/2000"); - setInputValue(timeInput, "11:00"); + setValue("3/3/2000", "11:00"); assertClientValid(); assertServerValid(); assertErrorMessage(""); + assertValidationCount(2); } @Test @@ -147,69 +170,70 @@ public void max_changeDateInputValue_assertValidity() { $("input").id(MAX_INPUT).sendKeys("2000-02-02T12:00", Keys.ENTER); setInputValue(dateInput, "3/3/2000"); - setInputValue(timeInput, "13:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MAX_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); - setInputValue(timeInput, "13:00"); + setValue("2/2/2000", "13:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MAX_ERROR_MESSAGE); + assertValidationCount(2); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "12:00"); assertClientValid(); assertServerValid(); assertErrorMessage(""); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "11:00"); assertClientValid(); assertServerValid(); assertErrorMessage(""); + assertValidationCount(1); - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "13:00"); + setValue("1/1/2000", "13:00"); assertClientValid(); assertServerValid(); assertErrorMessage(""); + assertValidationCount(2); } @Test public void setValue_clearValue_assertValidity() { - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "10:00"); + setValue("1/1/2000", "10:00"); assertServerValid(); assertClientValid(); assertErrorMessage(""); + assertValidationCount(1); $("button").id(CLEAR_VALUE_BUTTON).click(); assertServerValid(); assertClientValid(); assertErrorMessage(""); + assertValidationCount(1); } @Test public void badInput_changeValue_assertValidity() { - setInputValue(dateInput, "INVALID"); - setInputValue(timeInput, "INVALID"); + setValue("1/1/2000", "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "1/1/2000"); setInputValue(timeInput, "10:00"); assertServerValid(); assertClientValid(); assertErrorMessage(""); + assertValidationCount(1); setInputValue(dateInput, "INVALID"); - setInputValue(timeInput, "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); } @Test @@ -220,6 +244,7 @@ public void badInput_setDateInputValue_blur_assertValidity() { assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); } @Test @@ -229,20 +254,22 @@ public void badInput_setTimeInputValue_blur_assertValidity() { assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); } @Test public void badInput_setValue_clearValue_assertValidity() { setInputValue(dateInput, "INVALID"); - setInputValue(timeInput, "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); $("button").id(CLEAR_VALUE_BUTTON).click(); assertServerValid(); assertClientValid(); assertErrorMessage(""); + assertValidationCount(1); } @Test @@ -253,11 +280,13 @@ public void badInput_setDateInputValue_blur_clearValue_assertValidity() { assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); $("button").id(CLEAR_VALUE_BUTTON).click(); assertServerValid(); assertClientValid(); assertErrorMessage(""); + assertValidationCount(1); } @Test @@ -267,19 +296,117 @@ public void badInput_setTimeInputValue_blur_clearValue_assertValidity() { assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); $("button").id(CLEAR_VALUE_BUTTON).click(); assertServerValid(); assertClientValid(); assertErrorMessage(""); + assertValidationCount(1); } @Test - public void detach_attach_preservesInvalidState() { - // Make field invalid - $("button").id(REQUIRED_BUTTON).click(); + public void incompleteInput_assertValidity() { + setInputValue(dateInput, "1/1/2000"); + assertServerInvalid(); + assertClientInvalid(); + assertErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE); + assertValidationCount(1); + } + + @Test + public void incompleteInput_changeToValidValue_assertValidity() { + setInputValue(dateInput, "1/1/2000"); + resetValidationCount(); + + setValue("1/1/2001", "10:00"); + assertServerValid(); + assertClientValid(); + assertErrorMessage(""); + assertValidationCount(2); + } + + @Test + public void validInput_changeToIncompleteInput_assertValidity() { + setValue("1/1/2001", "10:00"); + resetValidationCount(); + + setInputValue(timeInput, ""); + assertServerInvalid(); + assertClientInvalid(); + assertErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE); + assertValidationCount(1); + } + + @Test + public void incompleteInput_setDateInputValue_blur_assertValidity() { + setInputValue(dateInput, "1/1/2000"); dateInput.sendKeys(Keys.TAB); timeInput.sendKeys(Keys.TAB); + assertServerInvalid(); + assertClientInvalid(); + assertErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE); + assertValidationCount(1); + } + + @Test + public void incompleteInput_setTimeInputValue_blur_assertValidity() { + setInputValue(timeInput, "10:00"); + timeInput.sendKeys(Keys.TAB); + assertServerInvalid(); + assertClientInvalid(); + assertErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE); + assertValidationCount(1); + } + + @Test + public void incompleteInput_setValue_clearValue_assertValidity() { + setInputValue(dateInput, "1/1/2000"); + timeInput.sendKeys(Keys.ENTER); + resetValidationCount(); + + $("button").id(CLEAR_VALUE_BUTTON).click(); + assertServerValid(); + assertClientValid(); + assertErrorMessage(""); + assertValidationCount(1); + } + + @Override + protected void assertValidationCount(int expected) { + super.assertValidationCount(expected); + } + + @Test + public void incompleteInput_setDateInputValue_blur_clearValue_assertValidity() { + setInputValue(dateInput, "1/1/2000"); + dateInput.sendKeys(Keys.TAB); + timeInput.sendKeys(Keys.TAB); + resetValidationCount(); + + $("button").id(CLEAR_VALUE_BUTTON).click(); + assertServerValid(); + assertClientValid(); + assertErrorMessage(""); + assertValidationCount(1); + } + + @Test + public void incompleteInput_setTimeInputValue_blur_clearValue_assertValidity() { + setInputValue(timeInput, "10:00"); + timeInput.sendKeys(Keys.TAB); + resetValidationCount(); + + $("button").id(CLEAR_VALUE_BUTTON).click(); + assertServerValid(); + assertClientValid(); + assertErrorMessage(""); + assertValidationCount(1); + } + + @Test + public void detach_attach_preservesInvalidState() { + setInputValue(dateInput, "INVALID"); detachAndReattachField(); @@ -309,10 +436,7 @@ public void detach_hide_attach_showAndInvalidate_preservesInvalidState() { @Test public void clientSideInvalidStateIsNotPropagatedToServer() { - // Make the field invalid - $("button").id(REQUIRED_BUTTON).click(); - dateInput.sendKeys(Keys.TAB); - timeInput.sendKeys(Keys.TAB); + setInputValue(dateInput, "INVALID"); executeScript("arguments[0].invalid = false", testField); @@ -323,6 +447,12 @@ protected DateTimePickerElement getTestField() { return $(DateTimePickerElement.class).first(); } + private void setValue(String dateValue, String timeValue) { + setInputValue(dateInput, dateValue); + dateInput.sendKeys(Keys.TAB); + setInputValue(timeInput, timeValue); + } + private void setInputValue(TestBenchElement input, String value) { input.sendKeys(Keys.chord(Keys.SHIFT, Keys.HOME), Keys.BACK_SPACE); input.sendKeys(value, Keys.ENTER); diff --git a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationIT.java b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationIT.java index 7ed8a57d1f9..2185825c3b0 100644 --- a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationIT.java +++ b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BinderValidationIT.java @@ -18,6 +18,7 @@ import static com.vaadin.flow.component.datetimepicker.validation.BinderValidationPage.BAD_INPUT_ERROR_MESSAGE; import static com.vaadin.flow.component.datetimepicker.validation.BinderValidationPage.CLEAR_VALUE_BUTTON; import static com.vaadin.flow.component.datetimepicker.validation.BinderValidationPage.EXPECTED_VALUE_INPUT; +import static com.vaadin.flow.component.datetimepicker.validation.BinderValidationPage.INCOMPLETE_INPUT_ERROR_MESSAGE; import static com.vaadin.flow.component.datetimepicker.validation.BinderValidationPage.MAX_ERROR_MESSAGE; import static com.vaadin.flow.component.datetimepicker.validation.BinderValidationPage.MAX_INPUT; import static com.vaadin.flow.component.datetimepicker.validation.BinderValidationPage.MIN_ERROR_MESSAGE; @@ -58,17 +59,19 @@ public void fieldIsInitiallyValid() { public void required_triggerDateInputBlur_assertValidity() { dateInput.sendKeys(Keys.TAB); timeInput.sendKeys(Keys.TAB); - assertServerInvalid(); - assertClientInvalid(); - assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertServerValid(); + assertClientValid(); + assertErrorMessage(null); + assertValidationCount(0); } @Test public void required_triggerTimeInputBlur_assertValidity() { timeInput.sendKeys(Keys.TAB); - assertServerInvalid(); - assertClientInvalid(); - assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertServerValid(); + assertClientValid(); + assertErrorMessage(null); + assertValidationCount(0); } @Test @@ -76,33 +79,35 @@ public void required_changeValue_assertValidity() { $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2000-01-01T12:00", Keys.ENTER); - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "12:00"); + setValue("1/1/2000", "12:00"); assertServerValid(); assertClientValid(); + assertValidationCount(1); setInputValue(dateInput, ""); assertServerInvalid(); assertClientInvalid(); - assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertErrorMessage(INCOMPLETE_INPUT_ERROR_MESSAGE); + assertValidationCount(1); setInputValue(timeInput, ""); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(1); setInputValue(dateInput, "INVALID"); - setInputValue(timeInput, "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); setInputValue(dateInput, ""); - setInputValue(timeInput, ""); timeInput.sendKeys(Keys.TAB); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(1); } @Test @@ -111,34 +116,35 @@ public void min_changeValue_assertValidity() { $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2000-03-03T11:00", Keys.ENTER); - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "11:00"); + setValue("1/1/2000", "11:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MIN_ERROR_MESSAGE); + assertValidationCount(2); setInputValue(dateInput, "2/2/2000"); - setInputValue(timeInput, "11:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MIN_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "12:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(UNEXPECTED_VALUE_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "13:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(UNEXPECTED_VALUE_ERROR_MESSAGE); + assertValidationCount(1); setInputValue(dateInput, "3/3/2000"); setInputValue(timeInput, "11:00"); assertClientValid(); assertServerValid(); + assertValidationCount(2); } @Test @@ -147,34 +153,34 @@ public void max_changeValue_assertValidity() { $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2000-01-01T13:00", Keys.ENTER); - setInputValue(dateInput, "3/3/2000"); - setInputValue(timeInput, "13:00"); + setValue("3/3/2000", "13:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MAX_ERROR_MESSAGE); + assertValidationCount(2); setInputValue(dateInput, "2/2/2000"); - setInputValue(timeInput, "13:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(MAX_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "12:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(UNEXPECTED_VALUE_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "2/2/2000"); setInputValue(timeInput, "11:00"); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(UNEXPECTED_VALUE_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "13:00"); + setValue("1/1/2000", "13:00"); assertClientValid(); assertServerValid(); + assertValidationCount(2); } @Test @@ -182,15 +188,16 @@ public void setValue_clearValue_assertValidity() { $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2000-01-01T10:00", Keys.ENTER); - setInputValue(dateInput, "1/1/2000"); - setInputValue(timeInput, "10:00"); + setValue("1/1/2000", "10:00"); assertServerValid(); assertClientValid(); + assertValidationCount(1); $("button").id(CLEAR_VALUE_BUTTON).click(); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(1); } @Test @@ -198,42 +205,49 @@ public void badInput_changeValue_assertValidity() { $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2000-01-01T10:00", Keys.ENTER); - setInputValue(dateInput, "INVALID"); - setInputValue(timeInput, "INVALID"); + setValue("1/1/2000", "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); - setInputValue(dateInput, "1/1/2000"); setInputValue(timeInput, "10:00"); assertServerValid(); assertClientValid(); + assertValidationCount(1); setInputValue(dateInput, "INVALID"); - setInputValue(timeInput, "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(1); } @Test public void badInput_setValue_clearValue_assertValidity() { - setInputValue(dateInput, "INVALID"); - setInputValue(timeInput, "INVALID"); + setValue("INVALID", "INVALID"); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(BAD_INPUT_ERROR_MESSAGE); + assertValidationCount(2); $("button").id(CLEAR_VALUE_BUTTON).click(); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(1); } protected DateTimePickerElement getTestField() { return $(DateTimePickerElement.class).first(); } + private void setValue(String dateValue, String timeValue) { + setInputValue(dateInput, dateValue); + dateInput.sendKeys(Keys.TAB); + setInputValue(timeInput, timeValue); + } + private void setInputValue(TestBenchElement input, String value) { input.sendKeys(Keys.chord(Keys.SHIFT, Keys.HOME), Keys.BACK_SPACE); input.sendKeys(value, Keys.ENTER); diff --git a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/main/java/com/vaadin/flow/component/datetimepicker/DateTimePicker.java b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/main/java/com/vaadin/flow/component/datetimepicker/DateTimePicker.java index feb5928c4bc..fac4ee30054 100644 --- a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/main/java/com/vaadin/flow/component/datetimepicker/DateTimePicker.java +++ b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/main/java/com/vaadin/flow/component/datetimepicker/DateTimePicker.java @@ -22,6 +22,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import com.vaadin.flow.component.AbstractField; @@ -62,8 +63,8 @@ protected void validate() { } @Override - protected boolean isInputValuePresent() { - return super.isInputValuePresent(); + protected boolean isInputUnparsable() { + return super.isInputUnparsable(); } } @@ -76,8 +77,8 @@ protected void validate() { } @Override - protected boolean isInputValuePresent() { - return super.isInputValuePresent(); + protected boolean isInputUnparsable() { + return super.isInputUnparsable(); } } @@ -123,18 +124,45 @@ public class DateTimePicker private LocalDateTime max; private LocalDateTime min; - private Validator defaultValidator = (value, context) -> { - boolean fromComponent = context == null; + private final CopyOnWriteArrayList> validationStatusChangeListeners = new CopyOnWriteArrayList<>(); - boolean hasBadDatePickerInput = Objects.equals(datePicker.getValue(), - datePicker.getEmptyValue()) && datePicker.isInputValuePresent(); - boolean hasBadTimePickerInput = Objects.equals(timePicker.getValue(), - timePicker.getEmptyValue()) && timePicker.isInputValuePresent(); - if (hasBadDatePickerInput || hasBadTimePickerInput) { + private final Validator defaultValidator = (value, + context) -> { + var fromComponent = context == null; + + // Report error if any of the pickers has bad input + if (isInputUnparsable()) { return ValidationResult.error(getI18nErrorMessage( DateTimePickerI18n::getBadInputErrorMessage)); } + // Report error if only date picker has a value, and it's outside the + // range. + if (Objects.equals(value, getEmptyValue()) && !datePicker.isEmpty()) { + var maxDate = max != null ? max.toLocalDate() : null; + var minDate = min != null ? min.toLocalDate() : null; + + var maxResult = ValidationUtil.validateMaxConstraint( + getI18nErrorMessage(DateTimePickerI18n::getMaxErrorMessage), + datePicker.getValue(), maxDate); + if (maxResult.isError()) { + return maxResult; + } + + var minResult = ValidationUtil.validateMinConstraint( + getI18nErrorMessage(DateTimePickerI18n::getMinErrorMessage), + datePicker.getValue(), minDate); + if (minResult.isError()) { + return minResult; + } + } + + // Report error if only one of the pickers has a value + if (isInputIncomplete()) { + return ValidationResult.error(getI18nErrorMessage( + DateTimePickerI18n::getIncompleteInputErrorMessage)); + } + // Do the required check only if the validator is called from the // component, and not from Binder. Binder has its own implementation // of required validation. @@ -149,14 +177,14 @@ public class DateTimePicker } } - ValidationResult maxResult = ValidationUtil.validateMaxConstraint( + var maxResult = ValidationUtil.validateMaxConstraint( getI18nErrorMessage(DateTimePickerI18n::getMaxErrorMessage), value, max); if (maxResult.isError()) { return maxResult; } - ValidationResult minResult = ValidationUtil.validateMinConstraint( + var minResult = ValidationUtil.validateMinConstraint( getI18nErrorMessage(DateTimePickerI18n::getMinErrorMessage), value, min); if (minResult.isError()) { @@ -237,9 +265,7 @@ public DateTimePicker(LocalDateTime initialDateTime) { // workaround for https://github.com/vaadin/flow/issues/3496 setInvalid(false); - addValueChangeListener(e -> validate()); - - addClientValidatedEventListener(e -> validate()); + addValidationListeners(); } /** @@ -327,6 +353,11 @@ public DateTimePicker(LocalDateTime initialDateTime, Locale locale) { setLocale(locale); } + private void addValidationListeners() { + addValueChangeListener(e -> validate()); + getElement().addEventListener("unparsable-change", e -> validate(true)); + } + /** * Sets the selected date and time value of the component. The value can be * cleared by setting null. @@ -343,23 +374,16 @@ public DateTimePicker(LocalDateTime initialDateTime, Locale locale) { */ @Override public void setValue(LocalDateTime value) { - LocalDateTime oldValue = getValue(); - + var oldValue = getValue(); value = sanitizeValue(value); + var shouldFireValidationStatusChangeEvent = oldValue == null + && value == null + && (isInputUnparsable() || isInputIncomplete()); super.setValue(value); - - boolean isInputValuePresent = timePicker.isInputValuePresent() - || datePicker.isInputValuePresent(); - boolean isValueRemainedEmpty = valueEquals(oldValue, getEmptyValue()) - && valueEquals(value, getEmptyValue()); - if (isValueRemainedEmpty && isInputValuePresent) { - // Clear the input elements from possible bad input. - synchronizeChildComponentValues(value); - fireEvent(new ClientValidatedEvent(this, false)); - } else { - synchronizeChildComponentValues(value); + synchronizeChildComponentValues(value); + if (shouldFireValidationStatusChangeEvent) { + validate(true); } - } /** @@ -749,6 +773,14 @@ public void removeThemeNames(String... themeNames) { synchronizeTheme(); } + private boolean isInputUnparsable() { + return datePicker.isInputUnparsable() || timePicker.isInputUnparsable(); + } + + private boolean isInputIncomplete() { + return datePicker.isEmpty() != timePicker.isEmpty(); + } + @Override public Validator getDefaultValidator() { return defaultValidator; @@ -757,9 +789,8 @@ public Validator getDefaultValidator() { @Override public Registration addValidationStatusChangeListener( ValidationStatusChangeListener listener) { - return addClientValidatedEventListener(event -> listener - .validationStatusChanged(new ValidationStatusChangeEvent<>(this, - event.isValid()))); + return Registration.addAndRemove(validationStatusChangeListeners, + listener); } @Override @@ -780,6 +811,26 @@ protected void validate() { validationController.validate(getValue()); } + /** + * Delegates the call to {@link #validate()} and additionally fires + * {@link ValidationStatusChangeEvent} to notify Binder that it needs to + * revalidate since the component's own validity state may have changed. + *

+ * NOTE: There is no need to notify Binder separately when running + * validation on {@link ValueChangeEvent}, as Binder already listens to this + * event and revalidates automatically. + */ + private void validate(boolean shouldFireValidationStatusChangeEvent) { + validate(); + + if (shouldFireValidationStatusChangeEvent) { + ValidationStatusChangeEvent event = new ValidationStatusChangeEvent<>( + this, !isInvalid()); + validationStatusChangeListeners.forEach( + listener -> listener.validationStatusChanged(event)); + } + } + /** * Sets the minimum date and time in the date time picker. Dates and times * before that will be disabled in the popups. @@ -929,6 +980,7 @@ public static class DateTimePickerI18n implements Serializable { private String dateLabel; private String timeLabel; private String badInputErrorMessage; + private String incompleteInputErrorMessage; private String requiredErrorMessage; private String minErrorMessage; private String maxErrorMessage; @@ -936,7 +988,7 @@ public static class DateTimePickerI18n implements Serializable { /** * Gets the aria-label suffix for the date picker. *

- * The date picker's final aria-label is a concatanation of the + * The date picker's final aria-label is a concatenation of the * DateTimePicker's {@link #getAriaLabel()} or {@link #getLabel()} * methods and this suffix. * @@ -949,7 +1001,7 @@ public String getDateLabel() { /** * Sets the aria-label suffix for the date picker. *

- * The date picker's final aria-label is a concatanation of the + * The date picker's final aria-label is a concatenation of the * DateTimePicker's {@link #getAriaLabel()} or {@link #getLabel()} * methods and this suffix. * @@ -966,7 +1018,7 @@ public DateTimePickerI18n setDateLabel(String dateLabel) { /** * Gets the aria-label suffix for the time picker. *

- * The time picker's aria-label is a concatanation of the + * The time picker's aria-label is a concatenation of the * DateTimePicker's {@link #getAriaLabel()} or {@link #getLabel()} * methods and this suffix. * @@ -979,7 +1031,7 @@ public String getTimeLabel() { /** * Sets the aria-label suffix for the time picker. *

- * The time picker's aria-label is a concatanation of the + * The time picker's aria-label is a concatenation of the * DateTimePicker's {@link #getAriaLabel()} or {@link #getLabel()} * methods and this suffix. * @@ -1020,6 +1072,34 @@ public DateTimePickerI18n setBadInputErrorMessage(String errorMessage) { return this; } + /** + * Gets the error message displayed when either the date or time is + * empty. + * + * @return the error message or {@code null} if not set + */ + public String getIncompleteInputErrorMessage() { + return incompleteInputErrorMessage; + } + + /** + * Sets the error message to display when either the date or time is + * empty. + *

+ * Note, custom error messages set with + * {@link DateTimePicker#setErrorMessage(String)} take priority over + * i18n error messages. + * + * @param errorMessage + * the error message to set, or {@code null} to clear + * @return this instance for method chaining + */ + public DateTimePickerI18n setIncompleteInputErrorMessage( + String errorMessage) { + incompleteInputErrorMessage = errorMessage; + return this; + } + /** * Gets the error message displayed when the field is required but * empty. diff --git a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationTest.java b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationTest.java index 6a7a88ea2b2..90d7032654b 100644 --- a/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationTest.java +++ b/vaadin-date-time-picker-flow-parent/vaadin-date-time-picker-flow/src/test/java/com/vaadin/flow/component/datetimepicker/validation/BasicValidationTest.java @@ -15,7 +15,11 @@ */ package com.vaadin.flow.component.datetimepicker.validation; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.Test; @@ -25,6 +29,7 @@ import com.vaadin.flow.component.shared.SlotUtils; import com.vaadin.flow.component.timepicker.TimePicker; import com.vaadin.flow.dom.DomEvent; +import com.vaadin.flow.dom.Element; import com.vaadin.flow.internal.nodefeature.ElementListenerMap; import com.vaadin.tests.validation.AbstractBasicValidationTest; @@ -32,28 +37,97 @@ public class BasicValidationTest extends AbstractBasicValidationTest { + @Test - public void badInput_validate_emptyErrorMessageDisplayed() { + public void badInputOnDatePicker_validate_emptyErrorMessageDisplayed() { getDatePicker().getElement().setProperty("_inputElementValue", "foo"); - fireValidatedDomEvent(); + fireUnparsableChangeDomEvent(); Assert.assertEquals("", testField.getErrorMessage()); } @Test - public void badInput_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + public void badInputOnTimePicker_validate_emptyErrorMessageDisplayed() { + getTimePicker().getElement().setProperty("_inputElementValue", "foo"); + fireUnparsableChangeDomEvent(); + Assert.assertEquals("", testField.getErrorMessage()); + } + + @Test + public void badInputOnDatePicker_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + var errorMessage = "Value has invalid format"; testField.setI18n(new DateTimePicker.DateTimePickerI18n() - .setBadInputErrorMessage("Value has invalid format")); + .setBadInputErrorMessage(errorMessage)); getDatePicker().getElement().setProperty("_inputElementValue", "foo"); - fireValidatedDomEvent(); - Assert.assertEquals("Value has invalid format", - testField.getErrorMessage()); + fireDomEvent("unparsable-change", getDatePicker().getElement()); + fireUnparsableChangeDomEvent(); + Assert.assertEquals(errorMessage, testField.getErrorMessage()); + } + + @Test + public void badInputOnTimePicker_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + var errorMessage = "Value has invalid format"; + testField.setI18n(new DateTimePicker.DateTimePickerI18n() + .setBadInputErrorMessage(errorMessage)); + getTimePicker().getElement().setProperty("_inputElementValue", "foo"); + fireDomEvent("unparsable-change", getTimePicker().getElement()); + fireUnparsableChangeDomEvent(); + Assert.assertEquals(errorMessage, testField.getErrorMessage()); + } + + @Test + public void incompleteInputOnDatePicker_validate_emptyErrorMessageDisplayed() { + var picker = getDatePicker(); + picker.setValue(LocalDate.now()); + fireUnparsableChangeDomEvent(); + Assert.assertEquals("", testField.getErrorMessage()); + } + + @Test + public void incompleteInputOnTimePicker_validate_emptyErrorMessageDisplayed() { + var picker = getTimePicker(); + picker.setValue(LocalTime.now()); + fireUnparsableChangeDomEvent(); + Assert.assertEquals("", testField.getErrorMessage()); + } + + @Test + public void incompleteInputOnDatePicker_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + var errorMessage = "Value is incomplete"; + testField.setI18n(new DateTimePicker.DateTimePickerI18n() + .setIncompleteInputErrorMessage(errorMessage)); + var picker = getDatePicker(); + picker.setValue(LocalDate.now()); + fireUnparsableChangeDomEvent(); + Assert.assertEquals(errorMessage, testField.getErrorMessage()); + } + + @Test + public void incompleteInputOnTimePicker_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + var errorMessage = "Value is incomplete"; + testField.setI18n(new DateTimePicker.DateTimePickerI18n() + .setIncompleteInputErrorMessage(errorMessage)); + var picker = getTimePicker(); + picker.setValue(LocalTime.now()); + fireUnparsableChangeDomEvent(); + Assert.assertEquals(errorMessage, testField.getErrorMessage()); + } + + @Test + public void setIncompleteInputErrorMessage_errorMessageIsSet() { + var errorMessage = "Value is incomplete"; + testField.setI18n(new DateTimePicker.DateTimePickerI18n() + .setIncompleteInputErrorMessage(errorMessage)); + Assert.assertEquals(errorMessage, + testField.getI18n().getIncompleteInputErrorMessage()); } @Test public void required_validate_emptyErrorMessageDisplayed() { testField.setRequiredIndicatorVisible(true); testField.setValue(LocalDateTime.now()); + fireChangeDomEvent(); testField.setValue(null); + fireChangeDomEvent(); Assert.assertEquals("", testField.getErrorMessage()); } @@ -63,7 +137,9 @@ public void required_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { testField.setI18n(new DateTimePicker.DateTimePickerI18n() .setRequiredErrorMessage("Field is required")); testField.setValue(LocalDateTime.now()); + fireChangeDomEvent(); testField.setValue(null); + fireChangeDomEvent(); Assert.assertEquals("Field is required", testField.getErrorMessage()); } @@ -71,6 +147,7 @@ public void required_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { public void min_validate_emptyErrorMessageDisplayed() { testField.setMin(LocalDateTime.now()); testField.setValue(LocalDateTime.now().minusDays(1)); + fireChangeDomEvent(); Assert.assertEquals("", testField.getErrorMessage()); } @@ -80,6 +157,7 @@ public void min_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { testField.setI18n(new DateTimePicker.DateTimePickerI18n() .setMinErrorMessage("Value is too small")); testField.setValue(LocalDateTime.now().minusDays(1)); + fireChangeDomEvent(); Assert.assertEquals("Value is too small", testField.getErrorMessage()); } @@ -87,6 +165,7 @@ public void min_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { public void max_validate_emptyErrorMessageDisplayed() { testField.setMax(LocalDateTime.now()); testField.setValue(LocalDateTime.now().plusDays(1)); + fireChangeDomEvent(); Assert.assertEquals("", testField.getErrorMessage()); } @@ -96,6 +175,7 @@ public void max_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { testField.setI18n(new DateTimePicker.DateTimePickerI18n() .setMaxErrorMessage("Value is too big")); testField.setValue(LocalDateTime.now().plusDays(1)); + fireChangeDomEvent(); Assert.assertEquals("Value is too big", testField.getErrorMessage()); } @@ -106,7 +186,9 @@ public void setI18nAndCustomErrorMessage_validate_customErrorMessageDisplayed() .setRequiredErrorMessage("Field is required")); testField.setErrorMessage("Custom error message"); testField.setValue(LocalDateTime.now()); + fireChangeDomEvent(); testField.setValue(null); + fireChangeDomEvent(); Assert.assertEquals("Custom error message", testField.getErrorMessage()); } @@ -118,10 +200,14 @@ public void setI18nAndCustomErrorMessage_validate_removeCustomErrorMessage_valid .setRequiredErrorMessage("Field is required")); testField.setErrorMessage("Custom error message"); testField.setValue(LocalDateTime.now()); + fireChangeDomEvent(); testField.setValue(null); + fireChangeDomEvent(); testField.setErrorMessage(""); testField.setValue(LocalDateTime.now()); + fireChangeDomEvent(); testField.setValue(null); + fireChangeDomEvent(); Assert.assertEquals("Field is required", testField.getErrorMessage()); } @@ -132,6 +218,33 @@ public void setInvalid_nestedPickersAreInvalid() { Assert.assertTrue(getTimePicker().isInvalid()); } + @Test + public void setValueProgrammatically_fieldValidatedOnce() { + var dateTimePicker = new TestDateTimePicker(); + dateTimePicker.setValue(LocalDateTime.now()); + Assert.assertEquals(1, dateTimePicker.getValidationCount()); + } + + @Test + public void clearValueProgrammatically_fieldValidatedOnce() { + var dateTimePicker = new TestDateTimePicker(); + dateTimePicker.setValue(LocalDateTime.now()); + var validationCount = dateTimePicker.getValidationCount(); + dateTimePicker.setValue(null); + Assert.assertEquals(validationCount + 1, + dateTimePicker.getValidationCount()); + } + + @Test + public void setValueProgrammatically_invalidStateIsUpdatedInValueChangeListener() { + var isInvalid = new AtomicBoolean(); + testField.addValueChangeListener( + e -> isInvalid.set(e.getSource().isInvalid())); + testField.setMax(LocalDateTime.now()); + testField.setValue(LocalDateTime.now().plusDays(1)); + Assert.assertTrue(isInvalid.get()); + } + @Override protected DateTimePicker createTestField() { return new DateTimePicker(); @@ -145,10 +258,31 @@ private TimePicker getTimePicker() { return (TimePicker) SlotUtils.getChildInSlot(testField, "time-picker"); } - private void fireValidatedDomEvent() { - DomEvent validatedDomEvent = new DomEvent(testField.getElement(), - "validated", Json.createObject()); - testField.getElement().getNode().getFeature(ElementListenerMap.class) - .fireEvent(validatedDomEvent); + private void fireChangeDomEvent() { + fireDomEvent("change", testField.getElement()); + } + + private void fireUnparsableChangeDomEvent() { + fireDomEvent("unparsable-change", testField.getElement()); + } + + private void fireDomEvent(String eventType, Element element) { + var domEvent = new DomEvent(element, eventType, Json.createObject()); + element.getNode().getFeature(ElementListenerMap.class) + .fireEvent(domEvent); + } + + private class TestDateTimePicker extends DateTimePicker { + private final AtomicInteger validationCount = new AtomicInteger(0); + + @Override + protected void validate() { + super.validate(); + validationCount.incrementAndGet(); + } + + public int getValidationCount() { + return validationCount.get(); + } } } diff --git a/vaadin-time-picker-flow-parent/vaadin-time-picker-flow/src/main/java/com/vaadin/flow/component/timepicker/TimePicker.java b/vaadin-time-picker-flow-parent/vaadin-time-picker-flow/src/main/java/com/vaadin/flow/component/timepicker/TimePicker.java index 4a1da282f90..01564b00f39 100644 --- a/vaadin-time-picker-flow-parent/vaadin-time-picker-flow/src/main/java/com/vaadin/flow/component/timepicker/TimePicker.java +++ b/vaadin-time-picker-flow-parent/vaadin-time-picker-flow/src/main/java/com/vaadin/flow/component/timepicker/TimePicker.java @@ -148,7 +148,7 @@ public class TimePicker private Validator defaultValidator = (value, context) -> { boolean fromComponent = context == null; - if (unparsableValue != null) { + if (isInputUnparsable()) { return ValidationResult.error(getI18nErrorMessage( TimePickerI18n::getBadInputErrorMessage)); } @@ -364,7 +364,7 @@ public void setLabel(String label) { @Override public void setValue(LocalTime value) { LocalTime oldValue = getValue(); - if (oldValue == null && value == null && unparsableValue != null) { + if (oldValue == null && value == null && isInputUnparsable()) { // When the value is programmatically cleared while the field // contains an unparsable input, ValueChangeEvent isn't fired, // so we need to call setModelValue manually to clear the bad @@ -479,11 +479,23 @@ private void fireValidationStatusChangeEvent() { * * @return true if the input element's value is populated, * false otherwise + * @deprecated Since v24.8 */ + @Deprecated(since = "24.8") protected boolean isInputValuePresent() { return !getInputElementValue().isEmpty(); } + /** + * Returns whether the input value is unparsable. + * + * @return true if the input element's value is populated and + * unparsable, false otherwise + */ + protected boolean isInputUnparsable() { + return unparsableValue != null; + } + /** * Gets the value of the input element. This value is updated on the server * when the web component dispatches a `change` or `unparsable-change`