/* * 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.Serializable; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.vaadin.data.Binder.Binding; import com.vaadin.data.Binder.BindingBuilder; import com.vaadin.data.converter.StringToIntegerConverter; import com.vaadin.data.validator.NotEmptyValidator; import com.vaadin.server.AbstractErrorMessage; import com.vaadin.server.ErrorMessage; import com.vaadin.server.SerializablePredicate; import com.vaadin.server.UserError; import com.vaadin.tests.data.bean.Person; import com.vaadin.ui.Label; import com.vaadin.ui.TextField; public class BinderConverterValidatorTest extends BinderTestBase<Binder<Person>, Person> { private static class StatusBean implements Serializable { private String status; public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } } @Before public void setUp() { binder = new Binder<>(); item = new Person(); item.setFirstName("Johannes"); item.setAge(32); } @Test public void validate_notBound_noErrors() { BinderValidationStatus<Person> status = binder.validate(); assertTrue(status.isOk()); } @Test public void bound_validatorsAreOK_noErrors() { BindingBuilder<Person, String> binding = binder.forField(nameField); binding.withValidator(Validator.alwaysPass()).bind(Person::getFirstName, Person::setFirstName); nameField.setComponentError(new UserError("")); BinderValidationStatus<Person> status = binder.validate(); assertTrue(status.isOk()); assertNull(nameField.getComponentError()); } @SuppressWarnings("serial") @Test public void bound_validatorsFail_errors() { BindingBuilder<Person, String> binding = binder.forField(nameField); binding.withValidator(Validator.alwaysPass()); String msg1 = "foo"; String msg2 = "bar"; binding.withValidator((String value, ValueContext context) -> ValidationResult.error(msg1)); binding.withValidator(value -> false, msg2); binding.bind(Person::getFirstName, Person::setFirstName); BinderValidationStatus<Person> status = binder.validate(); List<BindingValidationStatus<?>> errors = status .getFieldValidationErrors(); assertEquals(1, errors.size()); BindingValidationStatus<?> validationStatus = errors.stream() .findFirst().get(); String msg = validationStatus.getMessage().get(); assertEquals(msg1, msg); HasValue<?> field = validationStatus.getField(); assertEquals(nameField, field); ErrorMessage componentError = nameField.getComponentError(); assertNotNull(componentError); assertEquals("foo", ((AbstractErrorMessage) componentError).getMessage()); } @Test public void validatorForSuperTypeCanBeUsed() { // Validates that a validator for a super type can be used, e.g. // validator for Number can be used on a Double TextField salaryField = new TextField(); Validator<Number> positiveNumberValidator = (value, context) -> { if (value.doubleValue() >= 0) { return ValidationResult.ok(); } else { return ValidationResult.error(NEGATIVE_ERROR_MESSAGE); } }; binder.forField(salaryField) .withConverter(Double::valueOf, String::valueOf) .withValidator(positiveNumberValidator) .bind(Person::getSalaryDouble, Person::setSalaryDouble); Person person = new Person(); binder.setBean(person); salaryField.setValue("10"); assertEquals(10, person.getSalaryDouble(), 0); salaryField.setValue("-1"); // Does not pass validator assertEquals(10, person.getSalaryDouble(), 0); } @Test public void convertInitialValue() { bindAgeWithValidatorConverterValidator(); assertEquals("32", ageField.getValue()); } @Test public void convertToModelValidAge() { bindAgeWithValidatorConverterValidator(); ageField.setValue("33"); assertEquals(33, item.getAge()); } @Test public void convertToModelNegativeAgeFailsOnFirstValidator() { bindAgeWithValidatorConverterValidator(); ageField.setValue(""); assertEquals(32, item.getAge()); assertValidationErrors(binder.validate(), EMPTY_ERROR_MESSAGE); } private void assertValidationErrors( List<BindingValidationStatus<?>> validationErrors, String... errorMessages) { assertEquals(errorMessages.length, validationErrors.size()); for (int i = 0; i < errorMessages.length; i++) { assertEquals(errorMessages[i], validationErrors.get(i).getMessage().get()); } } private void assertValidationErrors(BinderValidationStatus<Person> status, String... errorMessages) { assertValidationErrors(status.getFieldValidationErrors(), errorMessages); } @Test public void convertToModelConversionFails() { bindAgeWithValidatorConverterValidator(); ageField.setValue("abc"); assertEquals(32, item.getAge()); assertValidationErrors(binder.validate(), NOT_NUMBER_ERROR_MESSAGE); } @Test public void convertToModelNegativeAgeFailsOnIntegerValidator() { bindAgeWithValidatorConverterValidator(); ageField.setValue("-5"); assertEquals(32, item.getAge()); assertValidationErrors(binder.validate(), NEGATIVE_ERROR_MESSAGE); } @Test public void convertDataToField() { bindAgeWithValidatorConverterValidator(); binder.getBean().setAge(12); binder.readBean(binder.getBean()); assertEquals("12", ageField.getValue()); } @Test public void convertNotValidatableDataToField() { bindAgeWithValidatorConverterValidator(); binder.getBean().setAge(-12); binder.readBean(binder.getBean()); assertEquals("-12", ageField.getValue()); } @Test(expected = IllegalArgumentException.class) public void convertInvalidDataToField() { TextField field = new TextField(); StatusBean bean = new StatusBean(); bean.setStatus("1"); Binder<StatusBean> binder = new Binder<>(); BindingBuilder<StatusBean, String> binding = binder.forField(field) .withConverter(presentation -> { if (presentation.equals("OK")) { return "1"; } else if (presentation.equals("NOTOK")) { return "2"; } throw new IllegalArgumentException( "Value must be OK or NOTOK"); }, model -> { if (model.equals("1")) { return "OK"; } else if (model.equals("2")) { return "NOTOK"; } else { throw new IllegalArgumentException( "Value in model must be 1 or 2"); } }); binding.bind(StatusBean::getStatus, StatusBean::setStatus); binder.setBean(bean); bean.setStatus("3"); binder.readBean(bean); } @Test public void validate_failedBeanValidatorWithoutFieldValidators() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); String msg = "foo"; binder.withValidator(Validator.from(bean -> false, msg)); Person person = new Person(); binder.setBean(person); List<BindingValidationStatus<?>> errors = binder.validate() .getFieldValidationErrors(); assertEquals(0, errors.size()); } @Test public void validate_failedBeanValidatorWithFieldValidator() { String msg = "foo"; BindingBuilder<Person, String> binding = binder.forField(nameField) .withValidator(new NotEmptyValidator<>(msg)); binding.bind(Person::getFirstName, Person::setFirstName); binder.withValidator(Validator.from(bean -> false, msg)); Person person = new Person(); binder.setBean(person); List<BindingValidationStatus<?>> errors = binder.validate() .getFieldValidationErrors(); assertEquals(1, errors.size()); BindingValidationStatus<?> error = errors.get(0); assertEquals(msg, error.getMessage().get()); assertEquals(nameField, error.getField()); } @Test public void validate_failedBothBeanValidatorAndFieldValidator() { String msg1 = "foo"; BindingBuilder<Person, String> binding = binder.forField(nameField) .withValidator(new NotEmptyValidator<>(msg1)); binding.bind(Person::getFirstName, Person::setFirstName); String msg2 = "bar"; binder.withValidator(Validator.from(bean -> false, msg2)); Person person = new Person(); binder.setBean(person); List<BindingValidationStatus<?>> errors = binder.validate() .getFieldValidationErrors(); assertEquals(1, errors.size()); BindingValidationStatus<?> error = errors.get(0); assertEquals(msg1, error.getMessage().get()); assertEquals(nameField, error.getField()); } @Test public void validate_okBeanValidatorWithoutFieldValidators() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); String msg = "foo"; binder.withValidator(Validator.from(bean -> true, msg)); Person person = new Person(); binder.setBean(person); assertFalse(binder.validate().hasErrors()); assertTrue(binder.validate().isOk()); } @Test public void binder_saveIfValid() { String msg1 = "foo"; BindingBuilder<Person, String> binding = binder.forField(nameField) .withValidator(new NotEmptyValidator<>(msg1)); binding.bind(Person::getFirstName, Person::setFirstName); String beanValidatorErrorMessage = "bar"; binder.withValidator( Validator.from(bean -> false, beanValidatorErrorMessage)); Person person = new Person(); String firstName = "first name"; person.setFirstName(firstName); binder.readBean(person); nameField.setValue(""); assertFalse(binder.writeBeanIfValid(person)); // check that field level-validation failed and bean is not updated assertEquals(firstName, person.getFirstName()); nameField.setValue("new name"); assertFalse(binder.writeBeanIfValid(person)); // Bean is updated but reverted assertEquals(firstName, person.getFirstName()); } @Test public void updateBoundField_bindingValdationFails_beanLevelValidationIsNotRun() { bindAgeWithValidatorConverterValidator(); bindName(); AtomicBoolean beanLevelValidationRun = new AtomicBoolean(); binder.withValidator(Validator .from(bean -> beanLevelValidationRun.getAndSet(true), "")); ageField.setValue("not a number"); assertFalse(beanLevelValidationRun.get()); nameField.setValue("foo"); assertFalse(beanLevelValidationRun.get()); } @Test public void updateBoundField_bindingValdationSuccess_beanLevelValidationIsRun() { bindAgeWithValidatorConverterValidator(); bindName(); AtomicBoolean beanLevelValidationRun = new AtomicBoolean(); binder.withValidator(Validator .from(bean -> beanLevelValidationRun.getAndSet(true), "")); ageField.setValue(String.valueOf(12)); assertTrue(beanLevelValidationRun.get()); } @Test public void binderHasChanges() throws ValidationException { binder.forField(nameField) .withValidator(Validator.from(name -> !"".equals(name), "Name can't be empty")) .bind(Person::getFirstName, Person::setFirstName); assertFalse(binder.hasChanges()); binder.setBean(item); assertFalse(binder.hasChanges()); // Bound binder + valid user changes: hasChanges == false nameField.setValue("foo"); assertFalse(binder.hasChanges()); nameField.setValue("bar"); binder.writeBeanIfValid(new Person()); assertFalse(binder.hasChanges()); // Bound binder + invalid user changes: hasChanges() == true nameField.setValue(""); binder.writeBeanIfValid(new Person()); assertTrue(binder.hasChanges()); // Read bean resets hasChanges binder.readBean(item); assertFalse(binder.hasChanges()); // Removing a bound bean resets hasChanges nameField.setValue(""); assertTrue(binder.hasChanges()); binder.removeBean(); assertFalse(binder.hasChanges()); // Unbound binder + valid user changes: hasChanges() == true nameField.setValue("foo"); assertTrue(binder.hasChanges()); // successful writeBean resets hasChanges to false binder.writeBeanIfValid(new Person()); assertFalse(binder.hasChanges()); // Unbound binder + invalid user changes: hasChanges() == true nameField.setValue(""); assertTrue(binder.hasChanges()); // unsuccessful writeBean doesn't affect hasChanges nameField.setValue(""); binder.writeBeanIfValid(new Person()); assertTrue(binder.hasChanges()); } @Test(expected = ValidationException.class) public void save_fieldValidationErrors() throws ValidationException { Binder<Person> binder = new Binder<>(); String msg = "foo"; binder.forField(nameField).withValidator(new NotEmptyValidator<>(msg)) .bind(Person::getFirstName, Person::setFirstName); Person person = new Person(); String firstName = "foo"; person.setFirstName(firstName); nameField.setValue(""); try { binder.writeBean(person); } finally { // Bean should not have been updated Assert.assertEquals(firstName, person.getFirstName()); } } @Test(expected = ValidationException.class) public void save_beanValidationErrors() throws ValidationException { Binder<Person> binder = new Binder<>(); binder.forField(nameField).withValidator(new NotEmptyValidator<>("a")) .bind(Person::getFirstName, Person::setFirstName); binder.withValidator(Validator.from(person -> false, "b")); Person person = new Person(); nameField.setValue("foo"); try { binder.writeBean(person); } finally { // Bean should have been updated for item validation but reverted Assert.assertNull(person.getFirstName()); } } @Test public void save_fieldsAndBeanLevelValidation() throws ValidationException { binder.forField(nameField).withValidator(new NotEmptyValidator<>("a")) .bind(Person::getFirstName, Person::setFirstName); binder.withValidator( Validator.from(person -> person.getLastName() != null, "b")); Person person = new Person(); person.setLastName("bar"); nameField.setValue("foo"); binder.writeBean(person); Assert.assertEquals(nameField.getValue(), person.getFirstName()); Assert.assertEquals("bar", person.getLastName()); } @Test public void saveIfValid_fieldValidationErrors() { String msg = "foo"; binder.forField(nameField).withValidator(new NotEmptyValidator<>(msg)) .bind(Person::getFirstName, Person::setFirstName); Person person = new Person(); person.setFirstName("foo"); nameField.setValue(""); Assert.assertFalse(binder.writeBeanIfValid(person)); Assert.assertEquals("foo", person.getFirstName()); } @Test public void saveIfValid_noValidationErrors() { String msg = "foo"; binder.forField(nameField).withValidator(new NotEmptyValidator<>(msg)) .bind(Person::getFirstName, Person::setFirstName); Person person = new Person(); person.setFirstName("foo"); nameField.setValue("bar"); Assert.assertTrue(binder.writeBeanIfValid(person)); Assert.assertEquals("bar", person.getFirstName()); } @Test public void saveIfValid_beanValidationErrors() { Binder<Person> binder = new Binder<>(); binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); String msg = "foo"; binder.withValidator(Validator.from( prsn -> prsn.getAddress() != null || prsn.getEmail() != null, msg)); Person person = new Person(); person.setFirstName("foo"); nameField.setValue(""); Assert.assertFalse(binder.writeBeanIfValid(person)); Assert.assertEquals("foo", person.getFirstName()); } @Test public void save_null_beanIsUpdated() throws ValidationException { Binder<Person> binder = new Binder<>(); binder.forField(nameField).withConverter(fieldValue -> { if ("null".equals(fieldValue)) { return null; } else { return fieldValue; } }, model -> { return model; }).bind(Person::getFirstName, Person::setFirstName); Person person = new Person(); person.setFirstName("foo"); nameField.setValue("null"); binder.writeBean(person); Assert.assertNull(person.getFirstName()); } @Test public void save_validationErrors_exceptionContainsErrors() throws ValidationException { String msg = "foo"; BindingBuilder<Person, String> nameBinding = binder.forField(nameField) .withValidator(new NotEmptyValidator<>(msg)); nameBinding.bind(Person::getFirstName, Person::setFirstName); BindingBuilder<Person, Integer> ageBinding = binder.forField(ageField) .withConverter(stringToInteger).withValidator(notNegative); ageBinding.bind(Person::getAge, Person::setAge); Person person = new Person(); nameField.setValue(""); ageField.setValue("-1"); try { binder.writeBean(person); Assert.fail(); } catch (ValidationException exception) { List<BindingValidationStatus<?>> validationErrors = exception .getFieldValidationErrors(); Assert.assertEquals(2, validationErrors.size()); BindingValidationStatus<?> error = validationErrors.get(0); Assert.assertEquals(nameField, error.getField()); Assert.assertEquals(msg, error.getMessage().get()); error = validationErrors.get(1); Assert.assertEquals(ageField, error.getField()); Assert.assertEquals(NEGATIVE_ERROR_MESSAGE, error.getMessage().get()); } } @Test public void binderBindAndLoad_clearsErrors() { BindingBuilder<Person, String> binding = binder.forField(nameField) .withValidator(notEmpty); binding.bind(Person::getFirstName, Person::setFirstName); binder.withValidator(bean -> !bean.getFirstName().contains("error"), "error"); Person person = new Person(); person.setFirstName(""); binder.setBean(person); // initial value is invalid but no error Assert.assertNull(nameField.getComponentError()); // make error show nameField.setValue("foo"); nameField.setValue(""); Assert.assertNotNull(nameField.getComponentError()); // bind to another person to see that error is cleared person = new Person(); person.setFirstName(""); binder.setBean(person); // error has been cleared Assert.assertNull(nameField.getComponentError()); // make show error nameField.setValue("foo"); nameField.setValue(""); Assert.assertNotNull(nameField.getComponentError()); // load should also clear error binder.readBean(person); Assert.assertNull(nameField.getComponentError()); // bind a new field that has invalid value in bean TextField lastNameField = new TextField(); person.setLastName(""); BindingBuilder<Person, String> binding2 = binder.forField(lastNameField) .withValidator(notEmpty); binding2.bind(Person::getLastName, Person::setLastName); // should not have error shown Assert.assertNull(lastNameField.getComponentError()); // add status label to show bean level error Label statusLabel = new Label(); binder.setStatusLabel(statusLabel); nameField.setValue("error"); // no error shown yet because second field validation doesn't pass Assert.assertEquals("", statusLabel.getValue()); // make second field validation pass to get bean validation error lastNameField.setValue("foo"); Assert.assertEquals("error", statusLabel.getValue()); // reload bean to clear error binder.readBean(person); Assert.assertEquals("", statusLabel.getValue()); // reset() should clear all errors and status label nameField.setValue(""); lastNameField.setValue(""); Assert.assertNotNull(nameField.getComponentError()); Assert.assertNotNull(lastNameField.getComponentError()); statusLabel.setComponentError(new UserError("ERROR")); binder.removeBean(); Assert.assertNull(nameField.getComponentError()); Assert.assertNull(lastNameField.getComponentError()); Assert.assertEquals("", statusLabel.getValue()); } @Test public void binderLoad_withCrossFieldValidation_clearsErrors() { TextField lastNameField = new TextField(); final SerializablePredicate<String> lengthPredicate = v -> v .length() > 2; BindingBuilder<Person, String> firstNameBinding = binder .forField(nameField).withValidator(lengthPredicate, "length"); firstNameBinding.bind(Person::getFirstName, Person::setFirstName); Binding<Person, String> lastNameBinding = binder.forField(lastNameField) .withValidator(v -> !nameField.getValue().isEmpty() || lengthPredicate.test(v), "err") .withValidator(lengthPredicate, "length") .bind(Person::getLastName, Person::setLastName); // this will be triggered as a new bean is bound with binder.bind(), // causing a validation error to be visible until reset is done nameField.addValueChangeListener(v -> lastNameBinding.validate()); Person person = new Person(); binder.setBean(person); Assert.assertNull(nameField.getComponentError()); Assert.assertNull(lastNameField.getComponentError()); nameField.setValue("x"); Assert.assertNotNull(nameField.getComponentError()); Assert.assertNotNull(lastNameField.getComponentError()); binder.setBean(person); Assert.assertNull(nameField.getComponentError()); Assert.assertNull(lastNameField.getComponentError()); } protected void bindName() { binder.bind(nameField, Person::getFirstName, Person::setFirstName); binder.setBean(item); } protected void bindAgeWithValidatorConverterValidator() { binder.forField(ageField).withValidator(notEmpty) .withConverter(stringToInteger).withValidator(notNegative) .bind(Person::getAge, Person::setAge); binder.setBean(item); } @Test(expected = ValidationException.class) public void save_beanValidationErrorsWithConverter() throws ValidationException { Binder<Person> binder = new Binder<>(); binder.forField(ageField) .withConverter(new StringToIntegerConverter("Can't convert")) .bind(Person::getAge, Person::setAge); binder.withValidator(Validator.from(person -> false, "b")); Person person = new Person(); ageField.setValue("1"); try { binder.writeBean(person); } finally { // Bean should have been updated for item validation but reverted Assert.assertEquals(0, person.getAge()); } } }