diff options
10 files changed, 491 insertions, 51 deletions
diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html index 20a7346182..f2c9a6523e 100644 --- a/all/src/main/templates/release-notes.html +++ b/all/src/main/templates/release-notes.html @@ -115,6 +115,7 @@ <li><tt>Button</tt> has a new constructor that may cause constructor calls with null as first parameter to be ambiguous.</li> <li><tt>DataCommunicator</tt> methods <tt>getDataProviderSize</tt> and <tt>fetchItemsWithRange</tt> are now <tt>public</tt>, not <tt>protected</tt>.</li> <li><tt>Binder</tt> method <tt>getBindings</tt> now returns a Collection, not a Set.</li> + <li><tt>Binder</tt> protected method <tt>handleError</tt> now takes a <tt>ValidationResult</tt>, not <tt>String</tt>.</li> <li><tt>BindingBuilder</tt> now works like a proper builder. Adding a converter will not mark Binding as <tt>bound</tt> allowing chaining to the same object.</li> <li><tt>ErrorLevel</tt> is removed from <tt>ErrorMessage</tt> and now <tt>com.vaadin.shared.ui.ErrorLevel</tt> should be used.</li> <li>Error indicators are now <tt><span class="v-errorindicator"></span></tt> elements.</li> diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index e28c191018..828130bd81 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -43,12 +43,14 @@ import com.vaadin.data.HasValue.ValueChangeListener; import com.vaadin.data.converter.StringToIntegerConverter; import com.vaadin.data.validator.BeanValidator; import com.vaadin.event.EventRouter; +import com.vaadin.server.AbstractErrorMessage.ContentMode; import com.vaadin.server.ErrorMessage; import com.vaadin.server.SerializableFunction; import com.vaadin.server.SerializablePredicate; import com.vaadin.server.Setter; import com.vaadin.server.UserError; import com.vaadin.shared.Registration; +import com.vaadin.shared.ui.ErrorLevel; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.Component; import com.vaadin.ui.Label; @@ -286,6 +288,7 @@ public class Binder<BEAN> implements Serializable { * failure, the property value is not updated. * * @see #withValidator(Validator) + * @see #withValidator(SerializablePredicate, String, ErrorLevel) * @see #withValidator(SerializablePredicate, ErrorMessageProvider) * @see Validator#from(SerializablePredicate, String) * @@ -305,6 +308,40 @@ public class Binder<BEAN> implements Serializable { /** * A convenience method to add a validator to this binding using the + * {@link Validator#from(SerializablePredicate, String, ErrorLevel)} + * factory method. + * <p> + * Validators are applied, in registration order, when the field value + * is written to the backing property. If any validator returns a + * failure, the property value is not updated. + * + * @see #withValidator(Validator) + * @see #withValidator(SerializablePredicate, String) + * @see #withValidator(SerializablePredicate, ErrorMessageProvider, + * ErrorLevel) + * @see Validator#from(SerializablePredicate, String) + * + * @param predicate + * the predicate performing validation, not null + * @param message + * the error message to report in case validation failure + * @param errorLevel + * the error level for failures from this validator, not null + * @return this binding, for chaining + * @throws IllegalStateException + * if {@code bind} has already been called + * + * @since 8.2 + */ + public default BindingBuilder<BEAN, TARGET> withValidator( + SerializablePredicate<? super TARGET> predicate, String message, + ErrorLevel errorLevel) { + return withValidator( + Validator.from(predicate, message, errorLevel)); + } + + /** + * A convenience method to add a validator to this binding using the * {@link Validator#from(SerializablePredicate, ErrorMessageProvider)} * factory method. * <p> @@ -314,6 +351,8 @@ public class Binder<BEAN> implements Serializable { * * @see #withValidator(Validator) * @see #withValidator(SerializablePredicate, String) + * @see #withValidator(SerializablePredicate, ErrorMessageProvider, + * ErrorLevel) * @see Validator#from(SerializablePredicate, ErrorMessageProvider) * * @param predicate @@ -332,6 +371,41 @@ public class Binder<BEAN> implements Serializable { } /** + * A convenience method to add a validator to this binding using the + * {@link Validator#from(SerializablePredicate, ErrorMessageProvider, ErrorLevel)} + * factory method. + * <p> + * Validators are applied, in registration order, when the field value + * is written to the backing property. If any validator returns a + * failure, the property value is not updated. + * + * @see #withValidator(Validator) + * @see #withValidator(SerializablePredicate, String, ErrorLevel) + * @see #withValidator(SerializablePredicate, ErrorMessageProvider) + * @see Validator#from(SerializablePredicate, ErrorMessageProvider, + * ErrorLevel) + * + * @param predicate + * the predicate performing validation, not null + * @param errorMessageProvider + * the provider to generate error messages, not null + * @param errorLevel + * the error level for failures from this validator, not null + * @return this binding, for chaining + * @throws IllegalStateException + * if {@code bind} has already been called + * + * @since 8.2 + */ + public default BindingBuilder<BEAN, TARGET> withValidator( + SerializablePredicate<? super TARGET> predicate, + ErrorMessageProvider errorMessageProvider, + ErrorLevel errorLevel) { + return withValidator(Validator.from(predicate, errorMessageProvider, + errorLevel)); + } + + /** * Maps the binding to another data type using the given * {@link Converter}. * <p> @@ -905,10 +979,7 @@ public class Binder<BEAN> implements Serializable { private BindingValidationStatus<TARGET> toValidationStatus( Result<TARGET> result) { - return new BindingValidationStatus<>(this, - result.isError() - ? ValidationResult.error(result.getMessage().get()) - : ValidationResult.ok()); + return new BindingValidationStatus<>(result, this); } /** @@ -1031,11 +1102,7 @@ public class Binder<BEAN> implements Serializable { @Override public Result<T> convertToModel(T value, ValueContext context) { ValidationResult validationResult = validator.apply(value, context); - if (validationResult.isError()) { - return Result.error(validationResult.getErrorMessage()); - } else { - return Result.ok(value); - } + return new ValidationResultWrap<>(value, validationResult); } @Override @@ -2012,14 +2079,18 @@ public class Binder<BEAN> implements Serializable { * * @param field * the field with the invalid value - * @param error - * the error message to set + * @param result + * the validation error result + * + * @since 8.2 */ - protected void handleError(HasValue<?> field, String error) { - if (field instanceof AbstractComponent) { - ((AbstractComponent) field).setComponentError(new UserError(error)); - } - + protected void handleError(HasValue<?> field, ValidationResult result) { + result.getErrorLevel().ifPresent(level -> { + if (field instanceof AbstractComponent) { + ((AbstractComponent) field).setComponentError(new UserError( + result.getErrorMessage(), ContentMode.TEXT, level)); + } + }); } /** @@ -2033,7 +2104,23 @@ public class Binder<BEAN> implements Serializable { HasValue<?> source = status.getField(); clearError(source); if (status.isError()) { - handleError(source, status.getMessage().get()); + Optional<ValidationResult> firstError = status + .getValidationResults().stream() + .filter(ValidationResult::isError).findFirst(); + if (firstError.isPresent()) { + // Failed with a Validation error + handleError(source, firstError.get()); + } else { + // Conversion error + status.getResult() + .ifPresent(result -> handleError(source, result)); + } + } else { + // Show first non-error ValidationResult message. + status.getValidationResults().stream() + .filter(result -> result.getErrorLevel().isPresent()) + .findFirst() + .ifPresent(result -> handleError(source, result)); } } diff --git a/server/src/main/java/com/vaadin/data/BindingValidationStatus.java b/server/src/main/java/com/vaadin/data/BindingValidationStatus.java index 5b5a066418..f6a7809970 100644 --- a/server/src/main/java/com/vaadin/data/BindingValidationStatus.java +++ b/server/src/main/java/com/vaadin/data/BindingValidationStatus.java @@ -16,6 +16,8 @@ package com.vaadin.data; import java.io.Serializable; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Optional; @@ -24,8 +26,8 @@ import com.vaadin.data.Binder.BindingBuilder; /** * Represents the status of field validation. Status can be {@code Status.OK}, - * {@code Status.ERROR} or {@code Status.RESET}. Status OK and ERROR are always - * associated with a ValidationResult {@link #getResult}. + * {@code Status.ERROR} or {@code Status.UNRESOLVED}. Status OK and ERROR are + * always associated with a ValidationResult {@link #getResult}. * <p> * Use * {@link BindingBuilder#withValidationStatusHandler(BindingValidationStatusHandler)} @@ -70,8 +72,9 @@ public class BindingValidationStatus<TARGET> implements Serializable { } private final Status status; - private final ValidationResult result; + private final List<ValidationResult> results; private final Binding<?, TARGET> binding; + private Result<TARGET> result; /** * Convenience method for creating a {@link Status#UNRESOLVED} validation @@ -86,7 +89,7 @@ public class BindingValidationStatus<TARGET> implements Serializable { */ public static <TARGET> BindingValidationStatus<TARGET> createUnresolvedStatus( Binding<?, TARGET> source) { - return new BindingValidationStatus<>(source, Status.UNRESOLVED, null); + return new BindingValidationStatus<TARGET>(null, source); } /** @@ -98,6 +101,7 @@ public class BindingValidationStatus<TARGET> implements Serializable { * @param result * the result of the validation */ + @Deprecated public BindingValidationStatus(Binding<?, TARGET> source, ValidationResult result) { this(source, result.isError() ? Status.ERROR : Status.OK, result); @@ -116,20 +120,43 @@ public class BindingValidationStatus<TARGET> implements Serializable { * @param result * the related result, may be {@code null} */ + @Deprecated public BindingValidationStatus(Binding<?, TARGET> source, Status status, ValidationResult result) { + this(result.isError() ? Result.error(result.getErrorMessage()) + : Result.ok(null), source); + } + + /** + * Creates a new status change event. + * <p> + * If {@code result} is {@code null}, the {@code status} is + * {@link Status#UNRESOLVED}. + * + * @param result + * the related result object, may be {@code null} + * @param source + * field whose status has changed, not {@code null} + * + * @since 8.2 + */ + public BindingValidationStatus(Result<TARGET> result, + Binding<?, TARGET> source) { Objects.requireNonNull(source, "Event source may not be null"); - Objects.requireNonNull(status, "Status may not be null"); - if (Objects.equals(status, Status.OK) && result.isError() - || Objects.equals(status, Status.ERROR) && !result.isError() - || Objects.equals(status, Status.UNRESOLVED) - && result != null) { - throw new IllegalStateException( - "Invalid validation status " + status + " for given result " - + (result == null ? "null" : result.toString())); - } + binding = source; - this.status = status; + if (result != null) { + this.status = result.isError() ? Status.ERROR : Status.OK; + if (result instanceof ValidationResultWrap) { + results = ((ValidationResultWrap<TARGET>) result) + .getValidationResults(); + } else { + results = Collections.emptyList(); + } + } else { + this.status = Status.UNRESOLVED; + results = Collections.emptyList(); + } this.result = result; } @@ -159,8 +186,10 @@ public class BindingValidationStatus<TARGET> implements Serializable { * status is not an error */ public Optional<String> getMessage() { - return Optional.ofNullable(result).filter(ValidationResult::isError) - .map(ValidationResult::getErrorMessage); + if (getStatus() == Status.OK || result == null) { + return Optional.empty(); + } + return result.getMessage(); } /** @@ -171,7 +200,24 @@ public class BindingValidationStatus<TARGET> implements Serializable { * @return the validation result */ public Optional<ValidationResult> getResult() { - return Optional.ofNullable(result); + if (result == null) { + return Optional.empty(); + } + return Optional.of(result.isError() + ? ValidationResult.error(result.getMessage().orElse("")) + : ValidationResult.ok()); + } + + /** + * Gets all the validation results related to this binding validation + * status. + * + * @return list of validation results + * + * @since 8.2 + */ + public List<ValidationResult> getValidationResults() { + return Collections.unmodifiableList(results); } /** diff --git a/server/src/main/java/com/vaadin/data/ValidationResult.java b/server/src/main/java/com/vaadin/data/ValidationResult.java index d458911ba1..33e10d6db2 100644 --- a/server/src/main/java/com/vaadin/data/ValidationResult.java +++ b/server/src/main/java/com/vaadin/data/ValidationResult.java @@ -17,6 +17,9 @@ package com.vaadin.data; import java.io.Serializable; import java.util.Objects; +import java.util.Optional; + +import com.vaadin.shared.ui.ErrorLevel; /** * Represents the result of a validation. A result may be either successful or @@ -35,26 +38,30 @@ public interface ValidationResult extends Serializable { class SimpleValidationResult implements ValidationResult { private final String error; + private final ErrorLevel errorLevel; - SimpleValidationResult(String error) { + SimpleValidationResult(String error, ErrorLevel errorLevel) { + if (error != null && errorLevel == null) { + throw new IllegalStateException("ValidationResult has an " + + "error message, but no ErrorLevel is provided."); + } this.error = error; + this.errorLevel = errorLevel; } @Override public String getErrorMessage() { - if (error == null) { + if (!getErrorLevel().isPresent()) { throw new IllegalStateException("The result is not an error. " + "It cannot contain error message"); } else { - return error; + return error != null ? error : ""; } } - @Override - public boolean isError() { - return error != null; + public Optional<ErrorLevel> getErrorLevel() { + return Optional.ofNullable(errorLevel); } - } /** @@ -69,12 +76,36 @@ public interface ValidationResult extends Serializable { String getErrorMessage(); /** + * Returns optional error level for this validation result. Error level is + * not present for successful validation results. + * <p> + * <strong>Note:</strong> By default {@link ErrorLevel#INFO} and + * {@link ErrorLevel#WARNING} are not considered to be blocking the + * validation and conversion chain. + * + * @see #isError() + * + * @return optional error level; error level is present for validation + * results that have not passed validation + * + * @since 8.2 + */ + Optional<ErrorLevel> getErrorLevel(); + + /** * Checks if the result denotes an error. + * <p> + * <strong>Note:</strong> By default {@link ErrorLevel#INFO} and + * {@link ErrorLevel#WARNING} are not considered to be errors. * * @return <code>true</code> if the result denotes an error, * <code>false</code> otherwise */ - boolean isError(); + default boolean isError() { + ErrorLevel errorLevel = getErrorLevel().orElse(null); + return errorLevel != null && errorLevel != ErrorLevel.INFO + && errorLevel != ErrorLevel.WARNING; + } /** * Returns a successful result. @@ -82,7 +113,7 @@ public interface ValidationResult extends Serializable { * @return the successful result */ public static ValidationResult ok() { - return new SimpleValidationResult(null); + return new SimpleValidationResult(null, null); } /** @@ -98,6 +129,32 @@ public interface ValidationResult extends Serializable { */ public static ValidationResult error(String errorMessage) { Objects.requireNonNull(errorMessage); - return new SimpleValidationResult(errorMessage); + return create(errorMessage, ErrorLevel.ERROR); + } + + /** + * Creates the validation result with the given {@code errorMessage} and + * {@code errorLevel}. Results with {@link ErrorLevel} of {@code INFO} or + * {@code WARNING} are not errors by default. + * + * @see #ok() + * @see #error(String) + * + * @param errorMessage + * error message, not {@code null} + * @param errorLevel + * error level, not {@code null} + * @return validation result with the given {@code errorMessage} and + * {@code errorLevel} + * @throws NullPointerException + * if {@code errorMessage} or {@code errorLevel} is {@code null} + * + * @since 8.2 + */ + public static ValidationResult create(String errorMessage, + ErrorLevel errorLevel) { + Objects.requireNonNull(errorMessage); + Objects.requireNonNull(errorLevel); + return new SimpleValidationResult(errorMessage, errorLevel); } } diff --git a/server/src/main/java/com/vaadin/data/ValidationResultWrap.java b/server/src/main/java/com/vaadin/data/ValidationResultWrap.java new file mode 100644 index 0000000000..71bb475686 --- /dev/null +++ b/server/src/main/java/com/vaadin/data/ValidationResultWrap.java @@ -0,0 +1,102 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import com.vaadin.server.SerializableConsumer; +import com.vaadin.server.SerializableFunction; + +/** + * Internal implementation of a {@code Result} that collects all possible + * ValidationResults into one list. This class intercepts the normal chaining of + * Converters and Validators, catching and collecting results. + * + * @param <R> + * the result data type + * + * @since 8.2 + */ +class ValidationResultWrap<R> implements Result<R> { + + private final List<ValidationResult> resultList; + private final Result<R> wrappedResult; + + ValidationResultWrap(Result<R> result, List<ValidationResult> resultList) { + this.resultList = resultList; + this.wrappedResult = result; + } + + ValidationResultWrap(R value, ValidationResult result) { + if (result.isError()) { + wrappedResult = new SimpleResult<>(null, result.getErrorMessage()); + } else { + wrappedResult = new SimpleResult<>(value, null); + } + this.resultList = new ArrayList<>(); + this.resultList.add(result); + } + + List<ValidationResult> getValidationResults() { + return Collections.unmodifiableList(resultList); + } + + Result<R> getWrappedResult() { + return wrappedResult; + } + + @Override + public <S> Result<S> flatMap(SerializableFunction<R, Result<S>> mapper) { + Result<S> result = wrappedResult.flatMap(mapper); + if (!(result instanceof ValidationResultWrap)) { + return new ValidationResultWrap<S>(result, resultList); + } + + List<ValidationResult> currentResults = new ArrayList<>(resultList); + ValidationResultWrap<S> resultWrap = (ValidationResultWrap<S>) result; + currentResults.addAll(resultWrap.getValidationResults()); + + return new ValidationResultWrap<>(resultWrap.getWrappedResult(), + currentResults); + } + + @Override + public void handle(SerializableConsumer<R> ifOk, + SerializableConsumer<String> ifError) { + wrappedResult.handle(ifOk, ifError); + } + + @Override + public boolean isError() { + return wrappedResult.isError(); + } + + @Override + public Optional<String> getMessage() { + return wrappedResult.getMessage(); + } + + @Override + public <X extends Throwable> R getOrThrow( + SerializableFunction<String, ? extends X> exceptionProvider) + throws X { + return wrappedResult.getOrThrow(exceptionProvider); + } + +} diff --git a/server/src/main/java/com/vaadin/data/Validator.java b/server/src/main/java/com/vaadin/data/Validator.java index 30d6976bde..4f8f4dc8ca 100644 --- a/server/src/main/java/com/vaadin/data/Validator.java +++ b/server/src/main/java/com/vaadin/data/Validator.java @@ -21,6 +21,7 @@ import java.util.Objects; import java.util.function.BiFunction; import com.vaadin.server.SerializablePredicate; +import com.vaadin.shared.ui.ErrorLevel; /** * A functional interface for validating user input or other potentially invalid @@ -80,8 +81,8 @@ public interface Validator<T> * Builds a validator out of a conditional function and an error message. If * the function returns true, the validator returns {@code Result.ok()}; if * it returns false or throws an exception, - * {@link ValidationResult#error(String)} is returned with the given - * message. + * {@link ValidationResult#error(String)} is returned with the given message + * and error level {@link ErrorLevel#ERROR}. * <p> * For instance, the following validator checks if a number is between 0 and * 10, inclusive: @@ -101,12 +102,44 @@ public interface Validator<T> */ public static <T> Validator<T> from(SerializablePredicate<T> guard, String errorMessage) { - Objects.requireNonNull(guard, "guard cannot be null"); Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); return from(guard, ctx -> errorMessage); } /** + * Builds a validator out of a conditional function and an error message. If + * the function returns true, the validator returns {@code Result.ok()}; if + * it returns false or throws an exception, + * {@link ValidationResult#error(String)} is returned with the given message + * and error level. + * <p> + * For instance, the following validator checks if a number is between 0 and + * 10, inclusive: + * + * <pre> + * Validator<Integer> v = Validator.from(num -> num >= 0 && num <= 10, + * "number must be between 0 and 10", ErrorLevel.ERROR); + * </pre> + * + * @param <T> + * the value type + * @param guard + * the function used to validate, not null + * @param errorMessage + * the message returned if validation fails, not null + * @param errorLevel + * the error level for failures from this validator, not null + * @return the new validator using the function + * + * @since 8.2 + */ + public static <T> Validator<T> from(SerializablePredicate<T> guard, + String errorMessage, ErrorLevel errorLevel) { + Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); + return from(guard, ctx -> errorMessage, errorLevel); + } + + /** * Builds a validator out of a conditional function and an error message * provider. If the function returns true, the validator returns * {@code Result.ok()}; if it returns false or throws an exception, @@ -122,20 +155,44 @@ public interface Validator<T> */ public static <T> Validator<T> from(SerializablePredicate<T> guard, ErrorMessageProvider errorMessageProvider) { + return from(guard, errorMessageProvider, ErrorLevel.ERROR); + } + + /** + * Builds a validator out of a conditional function and an error message + * provider. If the function returns true, the validator returns + * {@code Result.ok()}; if it returns false or throws an exception, + * {@code Result.error()} is returned with the message from the provider. + * + * @param <T> + * the value type + * @param guard + * the function used to validate, not null + * @param errorMessageProvider + * the provider to generate error messages, not null + * @param errorLevel + * the error level for failures from this validator, not null + * @return the new validator using the function + * + * @since 8.2 + */ + public static <T> Validator<T> from(SerializablePredicate<T> guard, + ErrorMessageProvider errorMessageProvider, ErrorLevel errorLevel) { Objects.requireNonNull(guard, "guard cannot be null"); Objects.requireNonNull(errorMessageProvider, "errorMessageProvider cannot be null"); + Objects.requireNonNull(errorLevel, "errorLevel cannot be null"); return (value, context) -> { try { if (guard.test(value)) { return ValidationResult.ok(); } else { - return ValidationResult - .error(errorMessageProvider.apply(context)); + return ValidationResult.create( + errorMessageProvider.apply(context), errorLevel); } } catch (Exception e) { - return ValidationResult - .error(errorMessageProvider.apply(context)); + return ValidationResult.create( + errorMessageProvider.apply(context), errorLevel); } }; } diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java index a44e3b3e06..032ce99580 100644 --- a/server/src/test/java/com/vaadin/data/BinderTest.java +++ b/server/src/test/java/com/vaadin/data/BinderTest.java @@ -14,6 +14,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -24,6 +25,7 @@ import com.vaadin.data.validator.IntegerRangeValidator; import com.vaadin.data.validator.NotEmptyValidator; import com.vaadin.data.validator.StringLengthValidator; import com.vaadin.server.ErrorMessage; +import com.vaadin.shared.ui.ErrorLevel; import com.vaadin.tests.data.bean.Person; import com.vaadin.tests.data.bean.Sex; import com.vaadin.ui.TextField; @@ -943,6 +945,24 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> { } @Test + public void info_validator_not_considered_error() { + String infoMessage = "Young"; + binder.forField(ageField) + .withConverter(new StringToIntegerConverter("Can't convert")) + .withValidator(i -> i > 5, infoMessage, ErrorLevel.INFO) + .bind(Person::getAge, Person::setAge); + + binder.setBean(item); + ageField.setValue("3"); + Assert.assertEquals(infoMessage, + ageField.getComponentError().getFormattedHtmlMessage()); + Assert.assertEquals(ErrorLevel.INFO, + ageField.getComponentError().getErrorLevel()); + + Assert.assertEquals(3, item.getAge()); + } + + @Test public void two_asRequired_fields_without_initial_values() { binder.forField(nameField).asRequired("Empty name").bind(p -> "", (p, s) -> { diff --git a/tests/screenshots b/tests/screenshots -Subproject 93a7b5d5a00209767b54a69395b1a7cc30ee970 +Subproject a20d0108ad315b1b5a4626619cd157e03cfa7b7 diff --git a/uitest/src/main/java/com/vaadin/tests/binder/BinderValidatorErrorLevel.java b/uitest/src/main/java/com/vaadin/tests/binder/BinderValidatorErrorLevel.java new file mode 100644 index 0000000000..03a20bbe1f --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/binder/BinderValidatorErrorLevel.java @@ -0,0 +1,29 @@ +package com.vaadin.tests.binder; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.data.Binder; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.ui.ErrorLevel; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.TextField; + +@Widgetset("com.vaadin.DefaultWidgetSet") +public class BinderValidatorErrorLevel extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + Binder<Object> binder = new Binder<>(); + + for (ErrorLevel l : ErrorLevel.values()) { + TextField field = new TextField(l.name()); + binder.forField(field) + .withValidator(s -> s.length() > 3, + "ErrorLevel: " + l.name(), l) + .bind(t -> "", (t, s) -> { + }); + addComponent(field); + } + binder.validate(); + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/binder/BinderValidatorErrorLevelTest.java b/uitest/src/test/java/com/vaadin/tests/binder/BinderValidatorErrorLevelTest.java new file mode 100644 index 0000000000..4be9f9a49e --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/binder/BinderValidatorErrorLevelTest.java @@ -0,0 +1,41 @@ +package com.vaadin.tests.binder; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.shared.ui.ErrorLevel; +import com.vaadin.testbench.By; +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class BinderValidatorErrorLevelTest extends SingleBrowserTest { + + @Test + public void testErrorLevelStyleNames() throws IOException { + openTestURL(); + + for (ErrorLevel l : ErrorLevel.values()) { + TextFieldElement textField = $(TextFieldElement.class) + .caption(l.name()).first(); + + // Screenshot the whole slot + compareScreen(textField.findElement(By.xpath("..")), + l.name().toLowerCase()); + + Assert.assertTrue("Error style for " + l.name() + " not present", + textField.getAttribute("class").contains( + "v-textfield-error-" + l.name().toLowerCase())); + textField.setValue("long enough text"); + Assert.assertFalse("Error style for " + l.name() + " still present", + textField.getAttribute("class").contains( + "v-textfield-error-" + l.name().toLowerCase())); + textField.setValue("foo"); + Assert.assertTrue( + "Error style for " + l.name() + " should be present again.", + textField.getAttribute("class").contains( + "v-textfield-error-" + l.name().toLowerCase())); + } + } +} |