From c40f9ef0864e06554f82c30da635aeac95dcf29c Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Wed, 15 May 2024 13:05:50 +0300 Subject: [PATCH 1/9] feat!: support setting error messages with TextFieldI18n checkRequired => validateRequiredConstraint add unit test polish run formatter docs: improve grid empty state documentation (#6351) add more unit tests wip --- .../shared/HasValidationProperties.java | 9 +- .../flow/component/shared/ValidationUtil.java | 68 +++++ .../TextFieldBasicValidationPage.java | 11 + .../TextFieldBinderValidationPage.java | 12 +- .../TextFieldBasicValidationIT.java | 17 ++ .../TextFieldBinderValidationIT.java | 14 +- .../flow/component/textfield/TextField.java | 270 +++++++++++++++--- .../component/textfield/TextFieldBase.java | 2 + .../textfield/tests/TextFieldTest.java | 12 + .../TextFieldBasicValidationTest.java | 71 +++++ 10 files changed, 438 insertions(+), 48 deletions(-) diff --git a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java index 9ac40b5ce4d..2bd08ffc19c 100644 --- a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java +++ b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java @@ -28,6 +28,11 @@ public interface HasValidationProperties extends HasElement, HasValidation { /** * Sets the error message to show to the user when the component is invalid. + *

+ * NOTE: If you need to manually control error messages, consider enabling + * manual validation mode with {@link #setManualValidation(boolean)} to + * avoid conflicts between your custom validation and the component's + * built-in validation. * * @param errorMessage * the error message or {@code null} to clear it @@ -53,8 +58,8 @@ default String getErrorMessage() { *

* NOTE: If you need to manually control the invalid state, consider * enabling manual validation mode with - * {@link #setManualValidation(boolean)} to avoid potential conflicts - * between your custom validation and the component's built-in validation. + * {@link #setManualValidation(boolean)} to avoid conflicts between your + * custom validation and the component's built-in validation. * * @param invalid * {@code true} for invalid, {@code false} for valid diff --git a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java index 6f894509ed6..67aa4b58764 100644 --- a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java +++ b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java @@ -170,4 +170,72 @@ public static > ValidationResult validateMinConstraint( return isError ? ValidationResult.error(errorMessage) : ValidationResult.ok(); } + + /** + * Checks if the value satisfies the minimum length constraint and returns a + * {@code ValidationResult.ok()} or {@code ValidationResult.error()} with + * the given error message depending on the result. + * + * @param errorMessage + * the error message to return if the check fails + * @param value + * the value to validate + * @param minLength + * the minimum allowed length + * @return {@code ValidationResult.ok()} if the value is shorter than or + * equal to the minimum length, {@code ValidationResult.error()} + * otherwise + */ + public static ValidationResult validateMinLengthConstraint( + String errorMessage, String value, Integer minLength) { + boolean isError = value != null && !value.isEmpty() && minLength != null + && value.length() < minLength; + return isError ? ValidationResult.error(errorMessage) + : ValidationResult.ok(); + } + + /** + * Checks if the value satisfies the maximum length constraint and returns a + * {@code ValidationResult.ok()} or {@code ValidationResult.error()} with + * the given error message depending on the result. + * + * @param errorMessage + * the error message to return if the check fails + * @param value + * the value to validate + * @param maxLength + * the maximum allowed length + * @return {@code ValidationResult.ok()} if the value is longer than or + * equal to the minimum length, {@code ValidationResult.error()} + * otherwise + */ + public static ValidationResult validateMaxLengthConstraint( + String errorMessage, String value, Integer maxLength) { + boolean isError = value != null && maxLength != null + && value.length() > maxLength; + return isError ? ValidationResult.error(errorMessage) + : ValidationResult.ok(); + } + + /** + * Checks if the value satisfies the pattern constraint and returns a + * {@code ValidationResult.ok()} or {@code ValidationResult.error()} with + * the given error message depending on the result. + * + * @param errorMessage + * the error message to return if the check fails + * @param value + * the value to validate + * @param pattern + * the pattern to match + * @return {@code ValidationResult.ok()} if the value matches the pattern, + * {@code ValidationResult.error()} otherwise + */ + public static ValidationResult validatePatternConstraint( + String errorMessage, String value, String pattern) { + boolean isError = value != null && !value.isEmpty() && pattern != null + && !pattern.isEmpty() && !value.matches(pattern); + return isError ? ValidationResult.error(errorMessage) + : ValidationResult.ok(); + } } diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationPage.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationPage.java index 4474e679dfb..ec29b91573d 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationPage.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationPage.java @@ -27,9 +27,20 @@ public class TextFieldBasicValidationPage public static final String MIN_LENGTH_INPUT = "min-length-input"; public static final String MAX_LENGTH_INPUT = "max-length-input"; + public static final String REQUIRED_ERROR_MESSAGE = "Field is required"; + public static final String MIN_LENGTH_ERROR_MESSAGE = "Value is too short"; + public static final String MAX_LENGTH_ERROR_MESSAGE = "Value is too long"; + public static final String PATTERN_ERROR_MESSAGE = "Value does not match the pattern"; + public TextFieldBasicValidationPage() { super(); + testField.setI18n(new TextField.TextFieldI18n() + .setRequiredErrorMessage(REQUIRED_ERROR_MESSAGE) + .setMinLengthErrorMessage(MIN_LENGTH_ERROR_MESSAGE) + .setMaxLengthErrorMessage(MAX_LENGTH_ERROR_MESSAGE) + .setPatternErrorMessage(PATTERN_ERROR_MESSAGE)); + add(createButton(REQUIRED_BUTTON, "Enable required", event -> { testField.setRequired(true); })); diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationPage.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationPage.java index 442e71fbbb9..ee883462a1d 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationPage.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/main/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationPage.java @@ -28,8 +28,11 @@ public class TextFieldBinderValidationPage public static final String MAX_LENGTH_INPUT = "max-length-input"; public static final String EXPECTED_VALUE_INPUT = "expected-value-input"; - public static final String REQUIRED_ERROR_MESSAGE = "The field is required"; - public static final String UNEXPECTED_VALUE_ERROR_MESSAGE = "The field doesn't match the expected value"; + public static final String REQUIRED_ERROR_MESSAGE = "Field is required"; + public static final String MIN_LENGTH_ERROR_MESSAGE = "Value is too short"; + public static final String MAX_LENGTH_ERROR_MESSAGE = "Value is too long"; + public static final String PATTERN_ERROR_MESSAGE = "Value does not match the pattern"; + public static final String UNEXPECTED_VALUE_ERROR_MESSAGE = "Value does not match the expected value"; public static class Bean { private String property; @@ -50,6 +53,11 @@ public void setProperty(String property) { public TextFieldBinderValidationPage() { super(); + testField.setI18n(new TextField.TextFieldI18n() + .setMinLengthErrorMessage(MIN_LENGTH_ERROR_MESSAGE) + .setMaxLengthErrorMessage(MAX_LENGTH_ERROR_MESSAGE) + .setPatternErrorMessage(PATTERN_ERROR_MESSAGE)); + binder = new Binder<>(Bean.class); binder.forField(testField).asRequired(REQUIRED_ERROR_MESSAGE) .withValidator(value -> value.equals(expectedValue), diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationIT.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationIT.java index a15e90f8966..df3a8051c31 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationIT.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBasicValidationIT.java @@ -24,8 +24,12 @@ import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.MIN_LENGTH_INPUT; import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.MAX_LENGTH_INPUT; +import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.MIN_LENGTH_ERROR_MESSAGE; +import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.MAX_LENGTH_ERROR_MESSAGE; +import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.PATTERN_ERROR_MESSAGE; import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.PATTERN_INPUT; import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.REQUIRED_BUTTON; +import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBasicValidationPage.REQUIRED_ERROR_MESSAGE; @TestPath("vaadin-text-field/validation/basic") public class TextFieldBasicValidationIT @@ -34,6 +38,7 @@ public class TextFieldBasicValidationIT public void fieldIsInitiallyValid() { assertClientValid(); assertServerValid(); + assertErrorMessage(null); } @Test @@ -42,6 +47,7 @@ public void triggerBlur_assertValidity() { assertValidationCount(0); assertServerValid(); assertClientValid(); + assertErrorMessage(null); } @Test @@ -52,6 +58,7 @@ public void required_triggerBlur_assertValidity() { assertValidationCount(0); assertServerValid(); assertClientValid(); + assertErrorMessage(null); } @Test @@ -62,11 +69,13 @@ public void required_changeValue_assertValidity() { assertValidationCount(1); assertServerValid(); assertClientValid(); + assertErrorMessage(""); testField.setValue(""); assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); + assertErrorMessage(REQUIRED_ERROR_MESSAGE); } @Test @@ -77,16 +86,19 @@ public void minLength_changeValue_assertValidity() { assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); + assertErrorMessage(MIN_LENGTH_ERROR_MESSAGE); testField.setValue("AA"); assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); testField.setValue("AAA"); assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); } @Test @@ -97,16 +109,19 @@ public void maxLength_changeValue_assertValidity() { assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); + assertErrorMessage(MAX_LENGTH_ERROR_MESSAGE); testField.setValue("AA"); assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); testField.setValue("A"); assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); } @Test @@ -117,11 +132,13 @@ public void pattern_changeValue_assertValidity() { assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); + assertErrorMessage(PATTERN_ERROR_MESSAGE); testField.setValue("1234"); assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); } @Test diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java index fb32abd0b7b..c53fab8bfe4 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java @@ -26,6 +26,9 @@ import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.MIN_LENGTH_INPUT; import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.MAX_LENGTH_INPUT; import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.EXPECTED_VALUE_INPUT; +import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.MIN_LENGTH_ERROR_MESSAGE; +import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.MAX_LENGTH_ERROR_MESSAGE; +import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.PATTERN_ERROR_MESSAGE; import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.REQUIRED_ERROR_MESSAGE; import static com.vaadin.flow.component.textfield.tests.validation.TextFieldBinderValidationPage.UNEXPECTED_VALUE_ERROR_MESSAGE; @@ -45,6 +48,7 @@ public void required_triggerBlur_assertValidity() { assertValidationCount(0); assertServerValid(); assertClientValid(); + assertErrorMessage(null); } @Test @@ -55,6 +59,7 @@ public void required_changeValue_assertValidity() { assertValidationCount(1); assertServerValid(); assertClientValid(); + assertErrorMessage(""); testField.setValue(""); assertValidationCount(1); @@ -73,7 +78,7 @@ public void minLength_changeValue_assertValidity() { assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); - assertErrorMessage(""); + assertErrorMessage(MIN_LENGTH_ERROR_MESSAGE); // Binder validation fails: testField.setValue("AA"); @@ -87,6 +92,7 @@ public void minLength_changeValue_assertValidity() { assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); } @Test @@ -99,7 +105,7 @@ public void maxLength_changeValue_assertValidity() { assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); - assertErrorMessage(""); + assertErrorMessage(MAX_LENGTH_ERROR_MESSAGE); // Binder validation fails: testField.setValue("AA"); @@ -113,6 +119,7 @@ public void maxLength_changeValue_assertValidity() { assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); } @Test @@ -125,7 +132,7 @@ public void pattern_changeValue_assertValidity() { assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); - assertErrorMessage(""); + assertErrorMessage(PATTERN_ERROR_MESSAGE); // Binder validation fails: testField.setValue("12"); @@ -139,6 +146,7 @@ public void pattern_changeValue_assertValidity() { assertValidationCount(1); assertClientValid(); assertServerValid(); + assertErrorMessage(""); } @Override diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java index 28a5886a28b..cb0dcf56612 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java @@ -15,6 +15,10 @@ */ package com.vaadin.flow.component.textfield; +import java.io.Serializable; +import java.util.Objects; +import java.util.Optional; + import com.vaadin.flow.component.AttachEvent; import com.vaadin.flow.component.Tag; import com.vaadin.flow.component.dependency.JsModule; @@ -22,7 +26,9 @@ import com.vaadin.flow.component.shared.ClientValidationUtil; import com.vaadin.flow.component.shared.HasAllowedCharPattern; import com.vaadin.flow.component.shared.HasThemeVariant; +import com.vaadin.flow.component.shared.ValidationUtil; import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.data.binder.ValidationResult; import com.vaadin.flow.data.binder.Validator; import com.vaadin.flow.data.value.ValueChangeMode; @@ -40,9 +46,9 @@ public class TextField extends TextFieldBase implements HasAllowedCharPattern, HasThemeVariant { - private boolean isConnectorAttached; + private TextFieldI18n i18n; - private TextFieldValidationSupport validationSupport; + private boolean isConnectorAttached; private boolean manualValidationEnabled = false; @@ -175,13 +181,6 @@ public TextField(String label, String initialValue, addValueChangeListener(listener); } - private TextFieldValidationSupport getValidationSupport() { - if (validationSupport == null) { - validationSupport = new TextFieldValidationSupport(this); - } - return validationSupport; - } - /** * Maximum number of characters (in Unicode code points) that the user can * enter. @@ -191,7 +190,6 @@ private TextFieldValidationSupport getValidationSupport() { */ public void setMaxLength(int maxLength) { getElement().setProperty("maxlength", maxLength); - getValidationSupport().setMaxLength(maxLength); } /** @@ -204,6 +202,10 @@ public int getMaxLength() { return (int) getElement().getProperty("maxlength", 0.0); } + private boolean hasMaxLength() { + return getElement().getProperty("maxlength") != null; + } + /** * Minimum number of characters (in Unicode code points) that the user can * enter. @@ -213,7 +215,6 @@ public int getMaxLength() { */ public void setMinLength(int minLength) { getElement().setProperty("minlength", minLength); - getValidationSupport().setMinLength(minLength); } /** @@ -226,22 +227,6 @@ public int getMinLength() { return (int) getElement().getProperty("minlength", 0.0); } - /** - *

- * Specifies that the user must fill in a value. - *

- * NOTE: The required indicator will not be visible, if there is no - * {@code label} property set for the textfield. - * - * @param required - * the boolean value to set - */ - @Override - public void setRequired(boolean required) { - super.setRequired(required); - getValidationSupport().setRequired(required); - } - /** * Sets a regular expression for the value to pass on the client-side. The * pattern must be a valid JavaScript Regular Expression that matches the @@ -259,7 +244,6 @@ public void setRequired(boolean required) { */ public void setPattern(String pattern) { getElement().setProperty("pattern", pattern == null ? "" : pattern); - getValidationSupport().setPattern(pattern); } /** @@ -304,15 +288,9 @@ public String getValue() { return super.getValue(); } - @Override - public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { - super.setRequiredIndicatorVisible(requiredIndicatorVisible); - getValidationSupport().setRequired(requiredIndicatorVisible); - } - @Override public Validator getDefaultValidator() { - return (value, context) -> getValidationSupport().checkValidity(value); + return (value, context) -> checkValidity(value, false); } @Override @@ -320,15 +298,61 @@ public void setManualValidation(boolean enabled) { this.manualValidationEnabled = enabled; } + private ValidationResult checkValidity(String value, + boolean withRequiredValidator) { + if (withRequiredValidator) { + ValidationResult requiredResult = ValidationUtil + .validateRequiredConstraint(getRequiredErrorMessage(), + isRequiredIndicatorVisible(), value, + getEmptyValue()); + if (requiredResult.isError()) { + return requiredResult; + } + } + + ValidationResult maxLengthResult = ValidationUtil + .validateMaxLengthConstraint(getMaxLengthErrorMessage(), value, + hasMaxLength() ? getMaxLength() : null); + if (maxLengthResult.isError()) { + return maxLengthResult; + } + + ValidationResult minLengthResult = ValidationUtil + .validateMinLengthConstraint(getMinLengthErrorMessage(), value, + getMinLength()); + if (minLengthResult.isError()) { + return minLengthResult; + } + + ValidationResult patternResult = ValidationUtil + .validatePatternConstraint(getPatternErrorMessage(), value, + getPattern()); + if (patternResult.isError()) { + return patternResult; + } + + return ValidationResult.ok(); + } + /** - * Performs server-side validation of the current value and the validation - * constraints of the field, such as {@link #setPattern(String)}. This is - * needed because it is possible to circumvent the client-side validation - * constraints using browser development tools. + * Validates the current value against the constraints and sets the + * {@code invalid} property and the {@code errorMessage} property using the + * error messages defined in the i18n object. + *

+ * The method does nothing if the manual validation mode is enabled. */ protected void validate() { - if (!this.manualValidationEnabled) { - setInvalid(getValidationSupport().isInvalid(getValue())); + if (this.manualValidationEnabled) { + return; + } + + ValidationResult result = checkValidity(getValue(), true); + if (result.isError()) { + setInvalid(true); + setErrorMessage(result.getErrorMessage()); + } else { + setInvalid(false); + setErrorMessage(null); } } @@ -337,4 +361,168 @@ protected void onAttach(AttachEvent attachEvent) { super.onAttach(attachEvent); ClientValidationUtil.preventWebComponentFromModifyingInvalidState(this); } + + /** + * Gets the internationalization object previously set for this component. + *

+ * NOTE: Updating the instance that is returned from this method will not + * update the component if not set again using + * {@link TextField#setI18n(TextFieldI18n)} + * + * @return the i18n object. It will be {@code null}, If the i18n properties + * weren't set. + */ + public TextFieldI18n getI18n() { + return i18n; + } + + /** + * Sets the internationalization properties for this component. + * + * @param i18n + * the internationalized properties, not {@code null} + */ + public void setI18n(TextFieldI18n i18n) { + this.i18n = Objects.requireNonNull(i18n, + "The i18n properties object should not be null"); + } + + private String getRequiredErrorMessage() { + return Optional.ofNullable(i18n) + .map(TextFieldI18n::getRequiredErrorMessage).orElse(""); + } + + private String getMinLengthErrorMessage() { + return Optional.ofNullable(i18n) + .map(TextFieldI18n::getMinLengthErrorMessage).orElse(""); + } + + private String getMaxLengthErrorMessage() { + return Optional.ofNullable(i18n) + .map(TextFieldI18n::getMaxLengthErrorMessage).orElse(""); + } + + private String getPatternErrorMessage() { + return Optional.ofNullable(i18n) + .map(TextFieldI18n::getPatternErrorMessage).orElse(""); + } + + /** + * The internationalization properties for {@link TextField}. + */ + public static class TextFieldI18n implements Serializable { + + private String requiredErrorMessage; + private String minLengthErrorMessage; + private String maxLengthErrorMessage; + private String patternErrorMessage; + + /** + * Gets the error message displayed when the field is required but + * empty. + * + * @return the error message or {@code null} if not set + * @see TextField#isRequiredIndicatorVisible() + * @see TextField#setRequiredIndicatorVisible(boolean) + */ + public String getRequiredErrorMessage() { + return requiredErrorMessage; + } + + /** + * Sets the error message to display when the field is required but + * empty. + * + * @param errorMessage + * the error message or {@code null} to clear it + * @return this instance for method chaining + * @see TextField#isRequiredIndicatorVisible() + * @see TextField#setRequiredIndicatorVisible(boolean) + */ + public TextFieldI18n setRequiredErrorMessage(String errorMessage) { + requiredErrorMessage = errorMessage; + return this; + } + + /** + * Gets the error message displayed when the field value is shorter than + * the minimum length. + * + * @return the error message or {@code null} if not set + * @see TextField#getMinLength() + * @see TextField#setMinLength(int) + */ + public String getMinLengthErrorMessage() { + return minLengthErrorMessage; + } + + /** + * Sets the error message to display when the field value is shorter + * than the minimum length. + * + * @param errorMessage + * the error message or {@code null} to clear it + * @return this instance for method chaining + * @see TextField#getMinLength() + * @see TextField#setMinLength(int) + */ + public TextFieldI18n setMinLengthErrorMessage(String errorMessage) { + minLengthErrorMessage = errorMessage; + return this; + } + + /** + * Gets the error message displayed when the field value is longer than + * the maximum length. + * + * @return the error message or {@code null} if not set + * @see TextField#getMaxLength() + * @see TextField#setMaxLength(int) + */ + public String getMaxLengthErrorMessage() { + return maxLengthErrorMessage; + } + + /** + * Sets the error message to display when the field value is longer than + * the maximum length. + * + * @param errorMessage + * the error message or {@code null} to clear it + * @return this instance for method chaining + * @see TextField#getMaxLength() + * @see TextField#setMaxLength(int) + */ + public TextFieldI18n setMaxLengthErrorMessage(String errorMessage) { + maxLengthErrorMessage = errorMessage; + return this; + } + + /** + * Gets the error message displayed when the field value does not match + * the pattern. + * + * @return the error message or {@code null} if not set + * @see TextField#getPattern() + * @see TextField#setPattern(String) + */ + public String getPatternErrorMessage() { + return patternErrorMessage; + } + + /** + * Sets the error message to display when the field value does not match + * the pattern. + * + * @param errorMessage + * the error message or {@code null} to clear it + * @return this instance for method chaining + * @see TextField#getPattern() + * @see TextField#setPattern(String) + */ + public TextFieldI18n setPatternErrorMessage(String errorMessage) { + patternErrorMessage = errorMessage; + return this; + } + } } diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextFieldBase.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextFieldBase.java index 5c6cb4be978..c42437655bb 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextFieldBase.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextFieldBase.java @@ -159,6 +159,8 @@ public void setAutoselect(boolean autoselect) { /** * Specifies that the user must fill in a value. + *

+ * NOTE: The required indicator is only visible when the field has a label. * * @param required * the boolean value to set diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/tests/TextFieldTest.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/tests/TextFieldTest.java index 80b9de715b7..7edee4280b7 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/tests/TextFieldTest.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/tests/TextFieldTest.java @@ -207,4 +207,16 @@ public void implementsInputField() { Assert.assertTrue( field instanceof InputField, String>); } + + @Test + public void setI18n_getI18n() { + TextField textField = new TextField(); + TextField.TextFieldI18n i18n = new TextField.TextFieldI18n() + .setRequiredErrorMessage("Required error") + .setMinLengthErrorMessage("Min length error") + .setMaxLengthErrorMessage("Max length error") + .setPatternErrorMessage("Pattern error"); + textField.setI18n(i18n); + Assert.assertEquals(i18n, textField.getI18n()); + } } diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java index ffc23784bc3..d0f93e0a6fe 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java @@ -15,11 +15,82 @@ */ package com.vaadin.flow.component.textfield.validation; +import org.junit.Assert; +import org.junit.Test; + import com.vaadin.flow.component.textfield.TextField; import com.vaadin.tests.validation.AbstractBasicValidationTest; public class TextFieldBasicValidationTest extends AbstractBasicValidationTest { + @Test + public void required_validate_emptyErrorMessageDisplayed() { + testField.setRequiredIndicatorVisible(true); + testField.setValue("AAA"); + testField.setValue(""); + Assert.assertEquals("", testField.getErrorMessage()); + } + + @Test + public void required_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + testField.setRequiredIndicatorVisible(true); + testField.setI18n(new TextField.TextFieldI18n() + .setRequiredErrorMessage("Field is required")); + testField.setValue("AAA"); + testField.setValue(""); + Assert.assertEquals("Field is required", testField.getErrorMessage()); + } + + @Test + public void minLength_validate_emptyErrorMessageDisplayed() { + testField.setMinLength(3); + testField.setValue("AA"); + Assert.assertEquals("", testField.getErrorMessage()); + } + + @Test + public void minLength_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + testField.setMinLength(3); + testField.setI18n(new TextField.TextFieldI18n() + .setMinLengthErrorMessage("Value is too short")); + testField.setValue("AA"); + Assert.assertEquals("Value is too short", testField.getErrorMessage()); + } + + @Test + public void maxLength_validate_emptyErrorMessageDisplayed() { + testField.setMaxLength(3); + testField.setValue("AAAA"); + Assert.assertEquals("", testField.getErrorMessage()); + } + + @Test + public void maxLength_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + testField.setMaxLength(3); + testField.setI18n(new TextField.TextFieldI18n() + .setMaxLengthErrorMessage("Value is too long")); + testField.setValue("AAAA"); + Assert.assertEquals("Value is too long", testField.getErrorMessage()); + } + + @Test + public void pattern_validate_emptyErrorMessageDisplayed() { + testField.setPattern("\\d+"); + testField.setValue("AAAA"); + Assert.assertEquals("", testField.getErrorMessage()); + } + + @Test + public void pattern_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { + testField.setPattern("\\d+"); + testField.setI18n(new TextField.TextFieldI18n() + .setPatternErrorMessage("Value does not match the pattern")); + testField.setValue("AAAA"); + Assert.assertEquals("Value does not match the pattern", + testField.getErrorMessage()); + } + + @Override protected TextField createTestField() { return new TextField(); } From 51842742307a0ea3428ef83997376fc79660b373 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Mon, 10 Jun 2024 11:56:17 +0300 Subject: [PATCH 2/9] align JavaDoc wording --- .../com/vaadin/flow/component/textfield/TextField.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java index cb0dcf56612..012125efd75 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java @@ -446,7 +446,7 @@ public TextFieldI18n setRequiredErrorMessage(String errorMessage) { /** * Gets the error message displayed when the field value is shorter than - * the minimum length. + * the minimum allowed length. * * @return the error message or {@code null} if not set * @see TextField#getMinLength() @@ -458,7 +458,7 @@ public String getMinLengthErrorMessage() { /** * Sets the error message to display when the field value is shorter - * than the minimum length. + * than the minimum allowed length. * * @param errorMessage * the error message or {@code null} to clear it @@ -473,7 +473,7 @@ public TextFieldI18n setMinLengthErrorMessage(String errorMessage) { /** * Gets the error message displayed when the field value is longer than - * the maximum length. + * the maximum allowed length. * * @return the error message or {@code null} if not set * @see TextField#getMaxLength() @@ -485,7 +485,7 @@ public String getMaxLengthErrorMessage() { /** * Sets the error message to display when the field value is longer than - * the maximum length. + * the maximum allowed length. * * @param errorMessage * the error message or {@code null} to clear it From fb112d67be45b1ed3b35a8f6e7733966654d7655 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Mon, 10 Jun 2024 15:48:49 +0300 Subject: [PATCH 3/9] fix typos in JavaDoc --- .../com/vaadin/flow/component/shared/ValidationUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java index 67aa4b58764..506622cef9d 100644 --- a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java +++ b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/ValidationUtil.java @@ -182,7 +182,7 @@ public static > ValidationResult validateMinConstraint( * the value to validate * @param minLength * the minimum allowed length - * @return {@code ValidationResult.ok()} if the value is shorter than or + * @return {@code ValidationResult.ok()} if the value is longer than or * equal to the minimum length, {@code ValidationResult.error()} * otherwise */ @@ -205,8 +205,8 @@ public static ValidationResult validateMinLengthConstraint( * the value to validate * @param maxLength * the maximum allowed length - * @return {@code ValidationResult.ok()} if the value is longer than or - * equal to the minimum length, {@code ValidationResult.error()} + * @return {@code ValidationResult.ok()} if the value is shorter than or + * equal to the maximum length, {@code ValidationResult.error()} * otherwise */ public static ValidationResult validateMaxLengthConstraint( From 9b255bafce44592db091655521b85ecfb6222eaa Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Thu, 11 Jul 2024 15:38:48 +0400 Subject: [PATCH 4/9] update JavaDoc --- .../com/vaadin/flow/component/textfield/TextField.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java index 012125efd75..ee8c063476e 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java @@ -367,20 +367,19 @@ protected void onAttach(AttachEvent attachEvent) { *

* NOTE: Updating the instance that is returned from this method will not * update the component if not set again using - * {@link TextField#setI18n(TextFieldI18n)} + * {@link #setI18n(TextFieldI18n)} * - * @return the i18n object. It will be {@code null}, If the i18n properties - * weren't set. + * @return the i18n object or {@code null} if no i18n object has been set */ public TextFieldI18n getI18n() { return i18n; } /** - * Sets the internationalization properties for this component. + * Sets the internationalization object for this component. * * @param i18n - * the internationalized properties, not {@code null} + * the i18n object, not {@code null} */ public void setI18n(TextFieldI18n i18n) { this.i18n = Objects.requireNonNull(i18n, From 22222b7a6a09dc3866d86c350604d2f996a216b6 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Mon, 15 Jul 2024 12:48:22 +0400 Subject: [PATCH 5/9] replace multiple helpers with one --- .../flow/component/textfield/TextField.java | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java index ee8c063476e..3be0d04b896 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java @@ -18,6 +18,7 @@ import java.io.Serializable; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import com.vaadin.flow.component.AttachEvent; import com.vaadin.flow.component.Tag; @@ -302,7 +303,9 @@ private ValidationResult checkValidity(String value, boolean withRequiredValidator) { if (withRequiredValidator) { ValidationResult requiredResult = ValidationUtil - .validateRequiredConstraint(getRequiredErrorMessage(), + .validateRequiredConstraint( + getI18nErrorMessage( + TextFieldI18n::getRequiredErrorMessage), isRequiredIndicatorVisible(), value, getEmptyValue()); if (requiredResult.isError()) { @@ -311,22 +314,28 @@ private ValidationResult checkValidity(String value, } ValidationResult maxLengthResult = ValidationUtil - .validateMaxLengthConstraint(getMaxLengthErrorMessage(), value, - hasMaxLength() ? getMaxLength() : null); + .validateMaxLengthConstraint( + getI18nErrorMessage( + TextFieldI18n::getMaxLengthErrorMessage), + value, hasMaxLength() ? getMaxLength() : null); if (maxLengthResult.isError()) { return maxLengthResult; } ValidationResult minLengthResult = ValidationUtil - .validateMinLengthConstraint(getMinLengthErrorMessage(), value, - getMinLength()); + .validateMinLengthConstraint( + getI18nErrorMessage( + TextFieldI18n::getMinLengthErrorMessage), + value, getMinLength()); if (minLengthResult.isError()) { return minLengthResult; } ValidationResult patternResult = ValidationUtil - .validatePatternConstraint(getPatternErrorMessage(), value, - getPattern()); + .validatePatternConstraint( + getI18nErrorMessage( + TextFieldI18n::getPatternErrorMessage), + value, getPattern()); if (patternResult.isError()) { return patternResult; } @@ -386,24 +395,8 @@ public void setI18n(TextFieldI18n i18n) { "The i18n properties object should not be null"); } - private String getRequiredErrorMessage() { - return Optional.ofNullable(i18n) - .map(TextFieldI18n::getRequiredErrorMessage).orElse(""); - } - - private String getMinLengthErrorMessage() { - return Optional.ofNullable(i18n) - .map(TextFieldI18n::getMinLengthErrorMessage).orElse(""); - } - - private String getMaxLengthErrorMessage() { - return Optional.ofNullable(i18n) - .map(TextFieldI18n::getMaxLengthErrorMessage).orElse(""); - } - - private String getPatternErrorMessage() { - return Optional.ofNullable(i18n) - .map(TextFieldI18n::getPatternErrorMessage).orElse(""); + private String getI18nErrorMessage(Function getter) { + return Optional.ofNullable(i18n).map(getter).orElse(""); } /** From 1a4c180044d76946bb55daaeff0ffe370e416b11 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Wed, 17 Jul 2024 17:11:01 +0400 Subject: [PATCH 6/9] do not override custom error messages with i18n ones --- .../TextFieldBinderValidationIT.java | 4 -- .../flow/component/textfield/TextField.java | 63 +++++++++++++++++-- .../TextFieldBasicValidationTest.java | 43 ++++++++++--- 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java index c53fab8bfe4..706ad95ef39 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow-integration-tests/src/test/java/com/vaadin/flow/component/textfield/tests/validation/TextFieldBinderValidationIT.java @@ -59,7 +59,6 @@ public void required_changeValue_assertValidity() { assertValidationCount(1); assertServerValid(); assertClientValid(); - assertErrorMessage(""); testField.setValue(""); assertValidationCount(1); @@ -92,7 +91,6 @@ public void minLength_changeValue_assertValidity() { assertValidationCount(1); assertClientValid(); assertServerValid(); - assertErrorMessage(""); } @Test @@ -119,7 +117,6 @@ public void maxLength_changeValue_assertValidity() { assertValidationCount(1); assertClientValid(); assertServerValid(); - assertErrorMessage(""); } @Test @@ -146,7 +143,6 @@ public void pattern_changeValue_assertValidity() { assertValidationCount(1); assertClientValid(); assertServerValid(); - assertErrorMessage(""); } @Override diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java index 3be0d04b896..ab7d2bc2797 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java @@ -49,10 +49,11 @@ public class TextField extends TextFieldBase private TextFieldI18n i18n; - private boolean isConnectorAttached; - private boolean manualValidationEnabled = false; + private String customErrorMessage; + private String constraintErrorMessage; + /** * Constructs an empty {@code TextField}. */ @@ -182,6 +183,44 @@ public TextField(String label, String initialValue, addValueChangeListener(listener); } + /** + * Sets an error message to display for all constraint violations. + *

+ * This error message takes priority over i18n error messages when both are + * set. + * + * @param errorMessage + * the error message to set, or {@code null} to clear + */ + @Override + public void setErrorMessage(String errorMessage) { + customErrorMessage = errorMessage; + updateErrorMessage(); + } + + /** + * Gets the error message displayed for all constraint violations. + * + * @return the error message + */ + @Override + public String getErrorMessage() { + return customErrorMessage; + } + + private void setConstraintErrorMessage(String errorMessage) { + constraintErrorMessage = errorMessage; + updateErrorMessage(); + } + + private void updateErrorMessage() { + String errorMessage = constraintErrorMessage; + if (customErrorMessage != null && !customErrorMessage.isEmpty()) { + errorMessage = customErrorMessage; + } + getElement().setProperty("errorMessage", errorMessage); + } + /** * Maximum number of characters (in Unicode code points) that the user can * enter. @@ -358,10 +397,10 @@ protected void validate() { ValidationResult result = checkValidity(getValue(), true); if (result.isError()) { setInvalid(true); - setErrorMessage(result.getErrorMessage()); + setConstraintErrorMessage(result.getErrorMessage()); } else { setInvalid(false); - setErrorMessage(null); + setConstraintErrorMessage(null); } } @@ -424,6 +463,10 @@ public String getRequiredErrorMessage() { /** * Sets the error message to display when the field is required but * empty. + *

+ * Note, custom error messages set with + * {@link TextField#setErrorMessage(String)} take priority over i18n + * error messages. * * @param errorMessage * the error message or {@code null} to clear it @@ -451,6 +494,10 @@ public String getMinLengthErrorMessage() { /** * Sets the error message to display when the field value is shorter * than the minimum allowed length. + *

+ * Note, custom error messages set with + * {@link TextField#setErrorMessage(String)} take priority over i18n + * error messages. * * @param errorMessage * the error message or {@code null} to clear it @@ -478,6 +525,10 @@ public String getMaxLengthErrorMessage() { /** * Sets the error message to display when the field value is longer than * the maximum allowed length. + *

+ * Note, custom error messages set with + * {@link TextField#setErrorMessage(String)} take priority over i18n + * error messages. * * @param errorMessage * the error message or {@code null} to clear it @@ -505,6 +556,10 @@ public String getPatternErrorMessage() { /** * Sets the error message to display when the field value does not match * the pattern. + *

+ * Note, custom error messages set with + * {@link TextField#setErrorMessage(String)} take priority over i18n + * error messages. * * @param errorMessage * the error message or {@code null} to clear it diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java index d0f93e0a6fe..d3beafd3457 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/TextFieldBasicValidationTest.java @@ -28,7 +28,7 @@ public void required_validate_emptyErrorMessageDisplayed() { testField.setRequiredIndicatorVisible(true); testField.setValue("AAA"); testField.setValue(""); - Assert.assertEquals("", testField.getErrorMessage()); + Assert.assertEquals("", getErrorMessageProperty()); } @Test @@ -38,14 +38,14 @@ public void required_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { .setRequiredErrorMessage("Field is required")); testField.setValue("AAA"); testField.setValue(""); - Assert.assertEquals("Field is required", testField.getErrorMessage()); + Assert.assertEquals("Field is required", getErrorMessageProperty()); } @Test public void minLength_validate_emptyErrorMessageDisplayed() { testField.setMinLength(3); testField.setValue("AA"); - Assert.assertEquals("", testField.getErrorMessage()); + Assert.assertEquals("", getErrorMessageProperty()); } @Test @@ -54,14 +54,14 @@ public void minLength_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { testField.setI18n(new TextField.TextFieldI18n() .setMinLengthErrorMessage("Value is too short")); testField.setValue("AA"); - Assert.assertEquals("Value is too short", testField.getErrorMessage()); + Assert.assertEquals("Value is too short", getErrorMessageProperty()); } @Test public void maxLength_validate_emptyErrorMessageDisplayed() { testField.setMaxLength(3); testField.setValue("AAAA"); - Assert.assertEquals("", testField.getErrorMessage()); + Assert.assertEquals("", getErrorMessageProperty()); } @Test @@ -70,14 +70,14 @@ public void maxLength_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { testField.setI18n(new TextField.TextFieldI18n() .setMaxLengthErrorMessage("Value is too long")); testField.setValue("AAAA"); - Assert.assertEquals("Value is too long", testField.getErrorMessage()); + Assert.assertEquals("Value is too long", getErrorMessageProperty()); } @Test public void pattern_validate_emptyErrorMessageDisplayed() { testField.setPattern("\\d+"); testField.setValue("AAAA"); - Assert.assertEquals("", testField.getErrorMessage()); + Assert.assertEquals("", getErrorMessageProperty()); } @Test @@ -87,11 +87,38 @@ public void pattern_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() { .setPatternErrorMessage("Value does not match the pattern")); testField.setValue("AAAA"); Assert.assertEquals("Value does not match the pattern", - testField.getErrorMessage()); + getErrorMessageProperty()); + } + + @Test + public void setI18nAndCustomErrorMessage_validate_customErrorMessageDisplayed() { + testField.setRequiredIndicatorVisible(true); + testField.setI18n(new TextField.TextFieldI18n() + .setRequiredErrorMessage("Field is required")); + testField.setErrorMessage("Custom error message"); + testField.setValue("AAAA"); + testField.setValue(""); + Assert.assertEquals("Custom error message", getErrorMessageProperty()); + } + + @Test + public void setI18nAndCustomErrorMessage_validate_removeCustomErrorMessage_i18nErrorMessageDisplayed() { + testField.setRequiredIndicatorVisible(true); + testField.setI18n(new TextField.TextFieldI18n() + .setRequiredErrorMessage("Field is required")); + testField.setErrorMessage("Custom error message"); + testField.setValue("AAAA"); + testField.setValue(""); + testField.setErrorMessage(""); + Assert.assertEquals("Field is required", getErrorMessageProperty()); } @Override protected TextField createTestField() { return new TextField(); } + + private String getErrorMessageProperty() { + return testField.getElement().getProperty("errorMessage"); + } } From 5f3d1cd85b29e79d06b32355fa84bfd07a3509f6 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Wed, 17 Jul 2024 17:30:36 +0400 Subject: [PATCH 7/9] set an empty error message when valid --- .../java/com/vaadin/flow/component/textfield/TextField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java index 56ce5aab549..1068c551000 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java @@ -400,7 +400,7 @@ protected void validate() { setConstraintErrorMessage(result.getErrorMessage()); } else { setInvalid(false); - setConstraintErrorMessage(null); + setConstraintErrorMessage(""); } } From 58ccd041e0c0a5ac57a29e52f0ca640119cda43e Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Thu, 18 Jul 2024 12:45:05 +0400 Subject: [PATCH 8/9] remove no longer relevant note from HasValidationProperties --- .../flow/component/shared/HasValidationProperties.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java index 2bd08ffc19c..9ac40b5ce4d 100644 --- a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java +++ b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/HasValidationProperties.java @@ -28,11 +28,6 @@ public interface HasValidationProperties extends HasElement, HasValidation { /** * Sets the error message to show to the user when the component is invalid. - *

- * NOTE: If you need to manually control error messages, consider enabling - * manual validation mode with {@link #setManualValidation(boolean)} to - * avoid conflicts between your custom validation and the component's - * built-in validation. * * @param errorMessage * the error message or {@code null} to clear it @@ -58,8 +53,8 @@ default String getErrorMessage() { *

* NOTE: If you need to manually control the invalid state, consider * enabling manual validation mode with - * {@link #setManualValidation(boolean)} to avoid conflicts between your - * custom validation and the component's built-in validation. + * {@link #setManualValidation(boolean)} to avoid potential conflicts + * between your custom validation and the component's built-in validation. * * @param invalid * {@code true} for invalid, {@code false} for valid From ca09fe0bb224654887470030cb45f65031b85a13 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Thu, 18 Jul 2024 16:21:38 +0400 Subject: [PATCH 9/9] improve JavaDoc for validate() method --- .../java/com/vaadin/flow/component/textfield/TextField.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java index 1068c551000..a7d0c5eeae1 100644 --- a/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java +++ b/vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/TextField.java @@ -384,8 +384,10 @@ private ValidationResult checkValidity(String value, /** * Validates the current value against the constraints and sets the - * {@code invalid} property and the {@code errorMessage} property using the - * error messages defined in the i18n object. + * {@code invalid} property and the {@code errorMessage} property based on + * the result. If a custom error message is provided with + * {@link #setErrorMessage(String)}, it is used. Otherwise, + * the error message defined in the i18n object is used. *

* The method does nothing if the manual validation mode is enabled. */