From 41516b54350bdfb597d6f60961266d3c2c57b880 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Johannes=20Dahlstr=C3=B6m?= Date: Tue, 13 Sep 2016 16:14:51 +0300 Subject: [PATCH] Split Binder tests roughly by category Change-Id: I7c1a55bd68097819e8091533df7232271af689c3 --- .../java/com/vaadin/data/BeanBinderTest.java | 46 +- .../data/BinderConverterValidatorTest.java | 687 ++++++++++ .../test/java/com/vaadin/data/BinderTest.java | 1200 +---------------- .../java/com/vaadin/data/BinderTestBase.java | 57 + .../data/BinderValidationStatusTest.java | 507 +++++++ 5 files changed, 1303 insertions(+), 1194 deletions(-) create mode 100644 server/src/test/java/com/vaadin/data/BinderConverterValidatorTest.java create mode 100644 server/src/test/java/com/vaadin/data/BinderTestBase.java create mode 100644 server/src/test/java/com/vaadin/data/BinderValidationStatusTest.java diff --git a/server/src/test/java/com/vaadin/data/BeanBinderTest.java b/server/src/test/java/com/vaadin/data/BeanBinderTest.java index 84a414edf1..1d775a2a6d 100644 --- a/server/src/test/java/com/vaadin/data/BeanBinderTest.java +++ b/server/src/test/java/com/vaadin/data/BeanBinderTest.java @@ -9,37 +9,29 @@ import org.junit.Before; import org.junit.Test; import com.vaadin.tests.data.bean.BeanToValidate; -import com.vaadin.ui.TextField; -public class BeanBinderTest { - - private BeanBinder binder; - - private TextField nameField; - private TextField ageField; - - private BeanToValidate p = new BeanToValidate(); +public class BeanBinderTest extends + BinderTestBase, BeanToValidate> { @Before public void setUp() { binder = new BeanBinder<>(BeanToValidate.class); - p.setFirstname("Johannes"); - p.setAge(32); - nameField = new TextField(); - ageField = new TextField(); + item = new BeanToValidate(); + item.setFirstname("Johannes"); + item.setAge(32); } @Test public void fieldBound_bindBean_fieldValueUpdated() { binder.bind(nameField, "firstname"); - binder.bind(p); + binder.bind(item); assertEquals("Johannes", nameField.getValue()); } @Test public void beanBound_bindField_fieldValueUpdated() { - binder.bind(p); + binder.bind(item); binder.bind(nameField, "firstname"); assertEquals("Johannes", nameField.getValue()); @@ -62,33 +54,33 @@ public class BeanBinderTest { @Test public void beanBound_setValidFieldValue_propertyValueChanged() { - binder.bind(p); + binder.bind(item); binder.bind(nameField, "firstname"); nameField.setValue("Henri"); - assertEquals("Henri", p.getFirstname()); + assertEquals("Henri", item.getFirstname()); } @Test public void readOnlyPropertyBound_setFieldValue_ignored() { binder.bind(nameField, "readOnlyProperty"); - binder.bind(p); + binder.bind(item); - String propertyValue = p.getReadOnlyProperty(); + String propertyValue = item.getReadOnlyProperty(); nameField.setValue("Foo"); - assertEquals(propertyValue, p.getReadOnlyProperty()); + assertEquals(propertyValue, item.getReadOnlyProperty()); } @Test public void beanBound_setInvalidFieldValue_validationError() { - binder.bind(p); + binder.bind(item); binder.bind(nameField, "firstname"); nameField.setValue("H"); // too short - assertEquals("Johannes", p.getFirstname()); + assertEquals("Johannes", item.getFirstname()); assertInvalid(nameField, "size must be between 3 and 16"); } @@ -124,13 +116,13 @@ public class BeanBinderTest { @Test(expected = ClassCastException.class) public void fieldWithIncompatibleTypeBound_bindBean_throws() { binder.bind(ageField, "age"); - binder.bind(p); + binder.bind(item); } @Test(expected = ClassCastException.class) public void fieldWithIncompatibleTypeBound_loadBean_throws() { binder.bind(ageField, "age"); - binder.load(p); + binder.load(item); } @Test(expected = ClassCastException.class) @@ -138,7 +130,7 @@ public class BeanBinderTest { throws Throwable { try { binder.bind(ageField, "age"); - binder.save(p); + binder.save(item); } catch (RuntimeException e) { throw e.getCause(); } @@ -148,7 +140,7 @@ public class BeanBinderTest { public void fieldWithConverterBound_bindBean_fieldValueUpdated() { binder.forField(ageField) .withConverter(Integer::valueOf, String::valueOf).bind("age"); - binder.bind(p); + binder.bind(item); assertEquals("32", ageField.getValue()); } @@ -157,7 +149,7 @@ public class BeanBinderTest { public void fieldWithInvalidConverterBound_bindBean_fieldValueUpdated() { binder.forField(ageField).withConverter(Float::valueOf, String::valueOf) .bind("age"); - binder.bind(p); + binder.bind(item); assertEquals("32", ageField.getValue()); } diff --git a/server/src/test/java/com/vaadin/data/BinderConverterValidatorTest.java b/server/src/test/java/com/vaadin/data/BinderConverterValidatorTest.java new file mode 100644 index 0000000000..d5959eb459 --- /dev/null +++ b/server/src/test/java/com/vaadin/data/BinderConverterValidatorTest.java @@ -0,0 +1,687 @@ +/* + * 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.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.Binder.Binding; +import com.vaadin.data.validator.NotEmptyValidator; +import com.vaadin.server.AbstractErrorMessage; +import com.vaadin.server.ErrorMessage; +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, Person> { + + private static class StatusBean { + 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 status = binder.validate(); + + assertTrue(status.isOk()); + } + + @Test + public void bound_validatorsAreOK_noErrors() { + Binding binding = binder.forField(nameField); + binding.withValidator(Validator.alwaysPass()).bind(Person::getFirstName, + Person::setFirstName); + + nameField.setComponentError(new UserError("")); + BinderValidationStatus status = binder.validate(); + + assertTrue(status.isOk()); + assertNull(nameField.getComponentError()); + } + + @SuppressWarnings("serial") + @Test + public void bound_validatorsFail_errors() { + Binding binding = binder.forField(nameField); + binding.withValidator(Validator.alwaysPass()); + String msg1 = "foo"; + String msg2 = "bar"; + binding.withValidator(new Validator() { + @Override + public Result apply(String value) { + return new SimpleResult<>(null, msg1); + } + }); + binding.withValidator(value -> false, msg2); + binding.bind(Person::getFirstName, Person::setFirstName); + + BinderValidationStatus status = binder.validate(); + List> errors = status.getFieldValidationErrors(); + + assertEquals(1, errors.size()); + + ValidationStatus 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 positiveNumberValidator = value -> { + if (value.doubleValue() >= 0) { + return Result.ok(value); + } else { + return Result.error(NEGATIVE_ERROR_MESSAGE); + } + }; + binder.forField(salaryField) + .withConverter(Double::valueOf, String::valueOf) + .withValidator(positiveNumberValidator) + .bind(Person::getSalaryDouble, Person::setSalaryDouble); + + Person person = new Person(); + binder.bind(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> 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 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().get().setAge(12); + binder.load(binder.getBean().get()); + assertEquals("12", ageField.getValue()); + } + + @Test + public void convertNotValidatableDataToField() { + bindAgeWithValidatorConverterValidator(); + binder.getBean().get().setAge(-12); + binder.load(binder.getBean().get()); + assertEquals("-12", ageField.getValue()); + } + + @Test(expected = IllegalArgumentException.class) + public void convertInvalidDataToField() { + TextField field = new TextField(); + StatusBean bean = new StatusBean(); + bean.setStatus("1"); + Binder binder = new Binder<>(); + + Binding 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.bind(bean); + + bean.setStatus("3"); + binder.load(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.bind(person); + + List> errors = binder.validate() + .getFieldValidationErrors(); + assertEquals(0, errors.size()); + } + + @Test + public void validate_failedBeanValidatorWithFieldValidator() { + String msg = "foo"; + + Binding 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.bind(person); + + List> errors = binder.validate() + .getFieldValidationErrors(); + assertEquals(1, errors.size()); + ValidationStatus error = errors.get(0); + assertEquals(msg, error.getMessage().get()); + assertEquals(nameField, error.getField()); + } + + @Test + public void validate_failedBothBeanValidatorAndFieldValidator() { + String msg1 = "foo"; + + Binding 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.bind(person); + + List> errors = binder.validate() + .getFieldValidationErrors(); + assertEquals(1, errors.size()); + + ValidationStatus 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.bind(person); + + assertFalse(binder.validate().hasErrors()); + assertTrue(binder.validate().isOk()); + } + + @Test + public void binder_saveIfValid() { + String msg1 = "foo"; + Binding 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.load(person); + + nameField.setValue(""); + assertFalse(binder.saveIfValid(person)); + // check that field level-validation failed and bean is not updated + assertEquals(firstName, person.getFirstName()); + + nameField.setValue("new name"); + + assertFalse(binder.saveIfValid(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.bind(item); + assertFalse(binder.hasChanges()); + + nameField.setValue("foo"); + assertTrue(binder.hasChanges()); + binder.load(item); + assertFalse(binder.hasChanges()); + + nameField.setValue("bar"); + binder.saveIfValid(new Person()); + assertFalse(binder.hasChanges()); + + nameField.setValue("baz"); + binder.save(new Person()); + assertFalse(binder.hasChanges()); + + nameField.setValue(""); + binder.saveIfValid(new Person()); + assertTrue(binder.hasChanges()); + } + + @Test(expected = ValidationException.class) + public void save_fieldValidationErrors() throws ValidationException { + Binder 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.save(person); + } finally { + // Bean should not have been updated + Assert.assertEquals(firstName, person.getFirstName()); + } + } + + @Test(expected = ValidationException.class) + public void save_beanValidationErrors() throws ValidationException { + Binder 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.save(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.save(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.saveIfValid(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.saveIfValid(person)); + Assert.assertEquals("bar", person.getFirstName()); + } + + @Test + public void saveIfValid_beanValidationErrors() { + Binder 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.saveIfValid(person)); + + Assert.assertEquals("foo", person.getFirstName()); + } + + @Test + public void save_null_beanIsUpdated() throws ValidationException { + Binder 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.save(person); + + Assert.assertNull(person.getFirstName()); + } + + @Test + public void save_validationErrors_exceptionContainsErrors() + throws ValidationException { + String msg = "foo"; + Binding nameBinding = binder.forField(nameField) + .withValidator(new NotEmptyValidator<>(msg)); + nameBinding.bind(Person::getFirstName, Person::setFirstName); + + Binding 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.save(person); + Assert.fail(); + } catch (ValidationException exception) { + List> validationErrors = exception + .getFieldValidationErrors(); + Assert.assertEquals(2, validationErrors.size()); + ValidationStatus 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() { + Binding binding = binder.forField(nameField) + .withValidator(notEmpty); + binding.bind(Person::getFirstName, Person::setFirstName); + binder.withValidator(bean -> bean.getFirstName().contains("error") + ? Result.error("error") : Result.ok(bean)); + Person person = new Person(); + person.setFirstName(""); + binder.bind(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.bind(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.load(person); + Assert.assertNull(nameField.getComponentError()); + + // bind a new field that has invalid value in bean + TextField lastNameField = new TextField(); + person.setLastName(""); + Binding 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.load(person); + Assert.assertEquals("", statusLabel.getValue()); + + // unbind() 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.unbind(); + Assert.assertNull(nameField.getComponentError()); + Assert.assertNull(lastNameField.getComponentError()); + Assert.assertEquals("", statusLabel.getValue()); + } + + @Test + public void binderLoad_withCrossFieldValidation_clearsErrors() { + TextField lastNameField = new TextField(); + final Predicate lengthPredicate = v -> v.length() > 2; + + Binding firstNameBinding = binder + .forField(nameField).withValidator(lengthPredicate, "length"); + firstNameBinding.bind(Person::getFirstName, Person::setFirstName); + + Binding lastNameBinding = binder + .forField(lastNameField) + .withValidator(v -> !nameField.getValue().isEmpty() + || lengthPredicate.test(v), "err") + .withValidator(lengthPredicate, "length"); + lastNameBinding.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.bind(person); + + Assert.assertNull(nameField.getComponentError()); + Assert.assertNull(lastNameField.getComponentError()); + + nameField.setValue("x"); + + Assert.assertNotNull(nameField.getComponentError()); + Assert.assertNotNull(lastNameField.getComponentError()); + + binder.bind(person); + + Assert.assertNull(nameField.getComponentError()); + Assert.assertNull(lastNameField.getComponentError()); + } + + protected void bindName() { + binder.bind(nameField, Person::getFirstName, Person::setFirstName); + binder.bind(item); + } + + protected void bindAgeWithValidatorConverterValidator() { + binder.forField(ageField).withValidator(notEmpty) + .withConverter(stringToInteger).withValidator(notNegative) + .bind(Person::getAge, Person::setAge); + binder.bind(item); + } +} diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java index 16ad4b70e3..b5e84cddd4 100644 --- a/server/src/test/java/com/vaadin/data/BinderTest.java +++ b/server/src/test/java/com/vaadin/data/BinderTest.java @@ -4,140 +4,101 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; - import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.vaadin.data.Binder.Binding; -import com.vaadin.data.ValidationStatus.Status; -import com.vaadin.data.util.converter.Converter; -import com.vaadin.data.validator.NotEmptyValidator; -import com.vaadin.server.AbstractErrorMessage; -import com.vaadin.server.ErrorMessage; -import com.vaadin.server.UserError; import com.vaadin.tests.data.bean.Person; -import com.vaadin.ui.Label; -import com.vaadin.ui.TextField; - -public class BinderTest { - - private static final String EMPTY_ERROR_MESSAGE = "Value cannot be empty"; - - private static class StatusBean { - private String status; - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - } - - Binder binder; - - TextField nameField; - TextField ageField; - - Person p = new Person(); - - Validator notEmpty = Validator.from(val -> !val.isEmpty(), - EMPTY_ERROR_MESSAGE); - Converter stringToInteger = Converter.from( - Integer::valueOf, String::valueOf, e -> "Value must be a number"); - Validator notNegative = Validator.from(x -> x >= 0, - "Value must be positive"); +public class BinderTest extends BinderTestBase, Person> { @Before public void setUp() { binder = new Binder<>(); - p.setFirstName("Johannes"); - p.setAge(32); - nameField = new TextField(); - ageField = new TextField(); + item = new Person(); + item.setFirstName("Johannes"); + item.setAge(32); } @Test(expected = NullPointerException.class) - public void bindingNullBeanThrows() { + public void bindNullBean_throws() { binder.bind(null); } @Test(expected = NullPointerException.class) - public void bindingNullFieldThrows() { + public void bindNullField_throws() { binder.forField(null); } @Test(expected = NullPointerException.class) - public void bindingNullGetterThrows() { + public void bindNullGetter_throws() { binder.bind(nameField, null, Person::setFirstName); } @Test - public void fieldValueUpdatedOnBeanBind() { + public void fieldBound_bindItem_fieldValueUpdated() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); - binder.bind(p); + binder.bind(item); assertEquals("Johannes", nameField.getValue()); } @Test - public void fieldValueUpdatedWithShortcutBind() { + public void fieldBoundWithShortcut_bindBean_fieldValueUpdated() { bindName(); assertEquals("Johannes", nameField.getValue()); } @Test - public void fieldValueUpdatedIfBeanAlreadyBound() { - binder.bind(p); + public void beanBound_updateFieldValue_beanValueUpdated() { + binder.bind(item); binder.bind(nameField, Person::getFirstName, Person::setFirstName); assertEquals("Johannes", nameField.getValue()); nameField.setValue("Artur"); - assertEquals("Artur", p.getFirstName()); + assertEquals("Artur", item.getFirstName()); } @Test - public void getBeanReturnsBoundBeanOrNothing() { + public void bound_getBean_returnsBoundBean() { assertFalse(binder.getBean().isPresent()); - binder.bind(p); - assertSame(p, binder.getBean().get()); + binder.bind(item); + assertSame(item, binder.getBean().get()); + } + + @Test + public void unbound_getBean_returnsNothing() { + binder.bind(item); binder.unbind(); assertFalse(binder.getBean().isPresent()); } @Test - public void fieldValueSavedToPropertyOnChange() { + public void bound_changeFieldValue_beanValueUpdated() { bindName(); nameField.setValue("Henri"); - assertEquals("Henri", p.getFirstName()); + assertEquals("Henri", item.getFirstName()); } @Test - public void fieldValueNotSavedAfterUnbind() { + public void unbound_changeFieldValue_beanValueNotUpdated() { bindName(); nameField.setValue("Henri"); binder.unbind(); nameField.setValue("Aleksi"); - assertEquals("Henri", p.getFirstName()); + assertEquals("Henri", item.getFirstName()); } @Test - public void bindNullSetterIgnoresValueChange() { + public void bindNullSetter_valueChangesIgnored() { binder.bind(nameField, Person::getFirstName, null); - binder.bind(p); + binder.bind(item); nameField.setValue("Artur"); - assertEquals(p.getFirstName(), "Johannes"); + assertEquals(item.getFirstName(), "Johannes"); } @Test - public void bindToAnotherBeanStopsUpdatingOriginalBean() { + public void bound_bindToAnotherBean_stopsUpdatingOriginal() { bindName(); nameField.setValue("Leif"); @@ -145,12 +106,12 @@ public class BinderTest { p2.setFirstName("Marlon"); binder.bind(p2); assertEquals("Marlon", nameField.getValue()); - assertEquals("Leif", p.getFirstName()); + assertEquals("Leif", item.getFirstName()); assertSame(p2, binder.getBean().get()); nameField.setValue("Ilia"); assertEquals("Ilia", p2.getFirstName()); - assertEquals("Leif", p.getFirstName()); + assertEquals("Leif", item.getFirstName()); } @Test @@ -183,168 +144,8 @@ public class BinderTest { Assert.assertEquals(fieldValue, person.getFirstName()); } - @Test - public void save_null_beanIsUpdated() throws ValidationException { - Binder 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.save(person); - - Assert.assertNull(person.getFirstName()); - } - - @Test(expected = ValidationException.class) - public void save_fieldValidationErrors() throws ValidationException { - Binder 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.save(person); - } finally { - // Bean should not have been updated - Assert.assertEquals(firstName, person.getFirstName()); - } - } - - @Test(expected = ValidationException.class) - public void save_beanValidationErrors() throws ValidationException { - Binder 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.save(person); - } finally { - // Bean should have been updated for item validation but reverted - Assert.assertNull(person.getFirstName()); - } - } - - @Test - public void save_fieldsAndBeanLevelValidation() throws ValidationException { - Binder binder = new Binder<>(); - 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.save(person); - Assert.assertEquals(nameField.getValue(), person.getFirstName()); - Assert.assertEquals("bar", person.getLastName()); - } - - @Test - public void saveIfValid_fieldValidationErrors() { - Binder binder = new Binder<>(); - 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.saveIfValid(person)); - Assert.assertEquals("foo", person.getFirstName()); - } - - @Test - public void saveIfValid_noValidationErrors() { - Binder binder = new Binder<>(); - 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.saveIfValid(person)); - Assert.assertEquals("bar", person.getFirstName()); - } - - @Test - public void saveIfValid_beanValidationErrors() { - Binder 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.saveIfValid(person)); - - Assert.assertEquals("foo", person.getFirstName()); - } - - @Test - public void save_validationErrors_exceptionContainsErrors() - throws ValidationException { - Binder binder = new Binder<>(); - String msg = "foo"; - Binding nameBinding = binder.forField(nameField) - .withValidator(new NotEmptyValidator<>(msg)); - nameBinding.bind(Person::getFirstName, Person::setFirstName); - - Binding 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.save(person); - Assert.fail(); - } catch (ValidationException exception) { - List> validationErrors = exception - .getFieldValidationErrors(); - Assert.assertEquals(2, validationErrors.size()); - ValidationStatus 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("Value must be positive", - error.getMessage().get()); - } - } - @Test public void load_bound_fieldValueIsUpdated() { - Binder binder = new Binder<>(); binder.bind(nameField, Person::getFirstName, Person::setFirstName); Person person = new Person(); @@ -358,8 +159,6 @@ public class BinderTest { @Test public void load_unbound_noChanges() { - Binder binder = new Binder<>(); - nameField.setValue(""); Person person = new Person(); @@ -371,941 +170,8 @@ public class BinderTest { Assert.assertEquals("", nameField.getValue()); } - @Test - public void validate_notBound_noErrors() { - Binder binder = new Binder<>(); - - BinderValidationStatus status = binder.validate(); - - Assert.assertTrue(status.isOk()); - } - - @Test - public void bound_validatorsAreOK_noErrors() { - Binder binder = new Binder<>(); - Binding binding = binder.forField(nameField); - binding.withValidator(Validator.alwaysPass()).bind(Person::getFirstName, - Person::setFirstName); - - nameField.setComponentError(new UserError("")); - BinderValidationStatus status = binder.validate(); - - Assert.assertTrue(status.isOk()); - Assert.assertNull(nameField.getComponentError()); - } - - @SuppressWarnings("serial") - @Test - public void bound_validatorsFail_errors() { - Binder binder = new Binder<>(); - Binding binding = binder.forField(nameField); - binding.withValidator(Validator.alwaysPass()); - String msg1 = "foo"; - String msg2 = "bar"; - binding.withValidator(new Validator() { - @Override - public Result apply(String value) { - return new SimpleResult<>(null, msg1); - } - }); - binding.withValidator(value -> false, msg2); - binding.bind(Person::getFirstName, Person::setFirstName); - - BinderValidationStatus status = binder.validate(); - List> errors = status.getFieldValidationErrors(); - - Assert.assertEquals(1, errors.size()); - - ValidationStatus validationStatus = errors.stream().findFirst() - .get(); - String msg = validationStatus.getMessage().get(); - Assert.assertEquals(msg1, msg); - - HasValue field = validationStatus.getField(); - - Assert.assertEquals(nameField, field); - - ErrorMessage componentError = nameField.getComponentError(); - Assert.assertNotNull(componentError); - Assert.assertEquals("foo", - ((AbstractErrorMessage) componentError).getMessage()); - } - - private void bindName() { + protected void bindName() { binder.bind(nameField, Person::getFirstName, Person::setFirstName); - binder.bind(p); - } - - private void bindAgeWithValidatorConverterValidator() { - binder.forField(ageField).withValidator(notEmpty) - .withConverter(stringToInteger).withValidator(notNegative) - .bind(Person::getAge, Person::setAge); - binder.bind(p); - } - - @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(); - Binder binder = new Binder<>(); - Validator positiveNumberValidator = value -> { - if (value.doubleValue() >= 0) { - return Result.ok(value); - } else { - return Result.error("Number must be positive"); - } - }; - binder.forField(salaryField) - .withConverter(Double::valueOf, String::valueOf) - .withValidator(positiveNumberValidator) - .bind(Person::getSalaryDouble, Person::setSalaryDouble); - - Person person = new Person(); - binder.bind(person); - salaryField.setValue("10"); - Assert.assertEquals(10, person.getSalaryDouble(), 0); - salaryField.setValue("-1"); // Does not pass validator - Assert.assertEquals(10, person.getSalaryDouble(), 0); - } - - @Test - public void convertInitialValue() { - bindAgeWithValidatorConverterValidator(); - assertEquals("32", ageField.getValue()); - } - - @Test - public void convertToModelValidAge() { - bindAgeWithValidatorConverterValidator(); - - ageField.setValue("33"); - assertEquals(33, p.getAge()); - } - - @Test - public void convertToModelNegativeAgeFailsOnFirstValidator() { - bindAgeWithValidatorConverterValidator(); - - ageField.setValue(""); - assertEquals(32, p.getAge()); - assertValidationErrors(binder.validate(), EMPTY_ERROR_MESSAGE); - } - - private void assertValidationErrors( - List> validationErrors, - String... errorMessages) { - Assert.assertEquals(errorMessages.length, validationErrors.size()); - for (int i = 0; i < errorMessages.length; i++) { - Assert.assertEquals(errorMessages[i], - validationErrors.get(i).getMessage().get()); - } - } - - private void assertValidationErrors(BinderValidationStatus status, - String... errorMessages) { - assertValidationErrors(status.getFieldValidationErrors(), - errorMessages); - } - - @Test - public void convertToModelConversionFails() { - bindAgeWithValidatorConverterValidator(); - ageField.setValue("abc"); - assertEquals(32, p.getAge()); - assertValidationErrors(binder.validate(), "Value must be a number"); - } - - @Test - public void convertToModelNegativeAgeFailsOnIntegerValidator() { - bindAgeWithValidatorConverterValidator(); - - ageField.setValue("-5"); - assertEquals(32, p.getAge()); - assertValidationErrors(binder.validate(), "Value must be positive"); - } - - @Test - public void convertDataToField() { - bindAgeWithValidatorConverterValidator(); - binder.getBean().get().setAge(12); - binder.load(binder.getBean().get()); - Assert.assertEquals("12", ageField.getValue()); - } - - @Test - public void convertNotValidatableDataToField() { - bindAgeWithValidatorConverterValidator(); - binder.getBean().get().setAge(-12); - binder.load(binder.getBean().get()); - Assert.assertEquals("-12", ageField.getValue()); - } - - @Test(expected = IllegalArgumentException.class) - public void convertInvalidDataToField() { - TextField field = new TextField(); - StatusBean bean = new StatusBean(); - bean.setStatus("1"); - Binder binder = new Binder<>(); - - Binding 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.bind(bean); - - bean.setStatus("3"); - binder.load(bean); - } - - @Test - public void bindingWithStatusHandler_handlerGetsEvents() { - AtomicReference> statusCapture = new AtomicReference<>(); - Binding binding = binder.forField(nameField) - .withValidator(notEmpty).withValidationStatusHandler(evt -> { - Assert.assertNull(statusCapture.get()); - statusCapture.set(evt); - }); - binding.bind(Person::getFirstName, Person::setFirstName); - - nameField.setValue(""); - - // First validation fails => should be event with ERROR status and - // message - binder.validate(); - - Assert.assertNotNull(statusCapture.get()); - ValidationStatus evt = statusCapture.get(); - Assert.assertEquals(Status.ERROR, evt.getStatus()); - Assert.assertEquals(EMPTY_ERROR_MESSAGE, evt.getMessage().get()); - Assert.assertEquals(nameField, evt.getField()); - - nameField.setValue("foo"); - - statusCapture.set(null); - // Second validation succeeds => should be event with OK status and - // no message - binder.validate(); - - evt = statusCapture.get(); - Assert.assertNotNull(evt); - Assert.assertEquals(Status.OK, evt.getStatus()); - Assert.assertFalse(evt.getMessage().isPresent()); - Assert.assertEquals(nameField, evt.getField()); - } - - @Test - public void bindingWithStatusHandler_defaultStatusHandlerIsReplaced() { - Binding binding = binder.forField(nameField) - .withValidator(notEmpty).withValidationStatusHandler(evt -> { - }); - binding.bind(Person::getFirstName, Person::setFirstName); - - Assert.assertNull(nameField.getComponentError()); - - nameField.setValue(""); - - // First validation fails => should be event with ERROR status and - // message - binding.validate(); - - // default behavior should update component error for the nameField - Assert.assertNull(nameField.getComponentError()); - } - - @Test - public void bindingWithStatusLabel_labelIsUpdatedAccordingStatus() { - Label label = new Label(); - - Binding binding = binder.forField(nameField) - .withValidator(notEmpty).withStatusLabel(label); - binding.bind(Person::getFirstName, Person::setFirstName); - - nameField.setValue(""); - - // First validation fails => should be event with ERROR status and - // message - binding.validate(); - - Assert.assertTrue(label.isVisible()); - Assert.assertEquals(EMPTY_ERROR_MESSAGE, label.getValue()); - - nameField.setValue("foo"); - - // Second validation succeeds => should be event with OK status and - // no message - binding.validate(); - - Assert.assertFalse(label.isVisible()); - Assert.assertEquals("", label.getValue()); - } - - @Test - public void bindingWithStatusLabel_defaultStatusHandlerIsReplaced() { - Label label = new Label(); - - Binding binding = binder.forField(nameField) - .withValidator(notEmpty).withStatusLabel(label); - binding.bind(Person::getFirstName, Person::setFirstName); - - Assert.assertNull(nameField.getComponentError()); - - nameField.setValue(""); - - // First validation fails => should be event with ERROR status and - // message - binding.validate(); - - // default behavior should update component error for the nameField - Assert.assertNull(nameField.getComponentError()); - } - - @Test(expected = IllegalStateException.class) - public void bindingWithStatusHandler_addAfterBound() { - Binding binding = binder.forField(nameField) - .withValidator(notEmpty); - binding.bind(Person::getFirstName, Person::setFirstName); - - binding.withValidationStatusHandler(evt -> Assert.fail()); - } - - @Test(expected = IllegalStateException.class) - public void bindingWithStatusLabel_addAfterBound() { - Label label = new Label(); - - Binding binding = binder.forField(nameField) - .withValidator(notEmpty); - binding.bind(Person::getFirstName, Person::setFirstName); - - binding.withStatusLabel(label); - } - - @Test(expected = IllegalStateException.class) - public void bindingWithStatusLabel_setAfterHandler() { - Label label = new Label(); - - Binding binding = binder.forField(nameField); - - binding.withValidationStatusHandler(event -> { - }); - - binding.withStatusLabel(label); - } - - @Test(expected = IllegalStateException.class) - public void bindingWithStatusHandler_setAfterLabel() { - Label label = new Label(); - - Binding binding = binder.forField(nameField); - - binding.withStatusLabel(label); - - binding.withValidationStatusHandler(event -> { - }); - } - - @Test(expected = IllegalStateException.class) - public void bingingWithStatusHandler_setAfterOtherHandler() { - - Binding binding = binder.forField(nameField); - - binding.withValidationStatusHandler(event -> { - }); - - binding.withValidationStatusHandler(event -> { - }); - } - - @Test - public void validate_failedBeanValidatorWithoutFieldValidators() { - Binder binder = new Binder<>(); - binder.forField(nameField).bind(Person::getFirstName, - Person::setFirstName); - - String msg = "foo"; - binder.withValidator(Validator.from(bean -> false, msg)); - Person person = new Person(); - binder.bind(person); - - List> errors = binder.validate() - .getFieldValidationErrors(); - Assert.assertEquals(0, errors.size()); - } - - @Test - public void validate_failedBeanValidatorWithFieldValidator() { - String msg = "foo"; - - Binder binder = new Binder<>(); - Binding 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.bind(person); - - List> errors = binder.validate() - .getFieldValidationErrors(); - Assert.assertEquals(1, errors.size()); - ValidationStatus error = errors.get(0); - Assert.assertEquals(msg, error.getMessage().get()); - Assert.assertEquals(nameField, error.getField()); - } - - @Test - public void validate_failedBothBeanValidatorAndFieldValidator() { - String msg1 = "foo"; - - Binder binder = new Binder<>(); - Binding 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.bind(person); - - List> errors = binder.validate() - .getFieldValidationErrors(); - Assert.assertEquals(1, errors.size()); - - ValidationStatus error = errors.get(0); - - Assert.assertEquals(msg1, error.getMessage().get()); - Assert.assertEquals(nameField, error.getField()); - } - - @Test - public void validate_okBeanValidatorWithoutFieldValidators() { - Binder binder = new Binder<>(); - binder.forField(nameField).bind(Person::getFirstName, - Person::setFirstName); - - String msg = "foo"; - binder.withValidator(Validator.from(bean -> true, msg)); - Person person = new Person(); - binder.bind(person); - - Assert.assertFalse(binder.validate().hasErrors()); - Assert.assertTrue(binder.validate().isOk()); - } - - @Test - public void binder_saveIfValid() { - String msg1 = "foo"; - Binder binder = new Binder<>(); - Binding 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.load(person); - - nameField.setValue(""); - Assert.assertFalse(binder.saveIfValid(person)); - // check that field level-validation failed and bean is not updated - Assert.assertEquals(firstName, person.getFirstName()); - - nameField.setValue("new name"); - - Assert.assertFalse(binder.saveIfValid(person)); - // Bean is updated but reverted - Assert.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"); - - Assert.assertFalse(beanLevelValidationRun.get()); - - nameField.setValue("foo"); - Assert.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)); - - Assert.assertTrue(beanLevelValidationRun.get()); - } - - @Test - public void binderWithStatusHandler_fieldValidationNoBeanValidation_handlerGetsStatusUpdates() { - AtomicReference> statusCapture = new AtomicReference<>(); - binder.forField(nameField).withValidator(notEmpty) - .withValidationStatusHandler(evt -> { - Assert.fail( - "Using a custom status change handler so no change should end up here"); - }).bind(Person::getFirstName, Person::setFirstName); - binder.forField(ageField).withConverter(stringToInteger) - .withValidator(notNegative).withValidationStatusHandler(evt -> { - Assert.fail( - "Using a custom status change handler so no change should end up here"); - }).bind(Person::getAge, Person::setAge); - - binder.setValidationStatusHandler(r -> { - statusCapture.set(r); - }); - binder.bind(p); - Assert.assertNull(nameField.getComponentError()); - - nameField.setValue(""); - ageField.setValue("5"); - - // First binding validation fails => should be result with ERROR status - // and message - BinderValidationStatus status2 = binder.validate(); - BinderValidationStatus status = statusCapture.get(); - Assert.assertSame(status2, status); - - Assert.assertNull(nameField.getComponentError()); - - List> bindingStatuses = status - .getFieldValidationStatuses(); - Assert.assertNotNull(bindingStatuses); - Assert.assertEquals(1, status.getFieldValidationErrors().size()); - Assert.assertEquals(2, bindingStatuses.size()); - - ValidationStatus r = bindingStatuses.get(0); - Assert.assertTrue(r.isError()); - Assert.assertEquals(EMPTY_ERROR_MESSAGE, r.getMessage().get()); - Assert.assertEquals(nameField, r.getField()); - - r = bindingStatuses.get(1); - Assert.assertFalse(r.isError()); - Assert.assertFalse(r.getMessage().isPresent()); - Assert.assertEquals(ageField, r.getField()); - - Assert.assertEquals(0, status.getBeanValidationResults().size()); - Assert.assertEquals(0, status.getBeanValidationErrors().size()); - - nameField.setValue("foo"); - ageField.setValue(""); - - statusCapture.set(null); - // Second validation succeeds => should be result with OK status and - // no message, and error result for age - binder.validate(); - - status = statusCapture.get(); - bindingStatuses = status.getFieldValidationStatuses(); - Assert.assertEquals(1, status.getFieldValidationErrors().size()); - Assert.assertEquals(2, bindingStatuses.size()); - - r = bindingStatuses.get(0); - Assert.assertFalse(r.isError()); - Assert.assertFalse(r.getMessage().isPresent()); - Assert.assertEquals(nameField, r.getField()); - - r = bindingStatuses.get(1); - Assert.assertTrue(r.isError()); - Assert.assertEquals("Value must be a number", r.getMessage().get()); - Assert.assertEquals(ageField, r.getField()); - - Assert.assertEquals(0, status.getBeanValidationResults().size()); - Assert.assertEquals(0, status.getBeanValidationErrors().size()); - - statusCapture.set(null); - // binding validations pass, binder validation fails - ageField.setValue("0"); - binder.validate(); - - status = statusCapture.get(); - bindingStatuses = status.getFieldValidationStatuses(); - Assert.assertEquals(0, status.getFieldValidationErrors().size()); - Assert.assertEquals(2, bindingStatuses.size()); - - Assert.assertEquals(0, status.getBeanValidationResults().size()); - Assert.assertEquals(0, status.getBeanValidationErrors().size()); - } - - @Test - public void binderWithStatusHandler_fieldAndBeanLevelValidation_handlerGetsStatusUpdates() { - AtomicReference> statusCapture = new AtomicReference<>(); - binder.forField(nameField).withValidator(notEmpty) - .withValidationStatusHandler(evt -> { - Assert.fail( - "Using a custom status change handler so no change should end up here"); - }).bind(Person::getFirstName, Person::setFirstName); - binder.forField(ageField).withConverter(stringToInteger) - .withValidator(notNegative).withValidationStatusHandler(evt -> { - Assert.fail( - "Using a custom status change handler so no change should end up here"); - }).bind(Person::getAge, Person::setAge); - binder.withValidator( - bean -> !bean.getFirstName().isEmpty() && bean.getAge() > 0 - ? Result.ok(bean) - : Result.error("Need first name and age")); - - binder.setValidationStatusHandler(r -> { - statusCapture.set(r); - }); - binder.bind(p); - Assert.assertNull(nameField.getComponentError()); - - nameField.setValue(""); - ageField.setValue("5"); - - // First binding validation fails => should be result with ERROR status - // and message - BinderValidationStatus status2 = binder.validate(); - BinderValidationStatus status = statusCapture.get(); - Assert.assertSame(status2, status); - - Assert.assertNull(nameField.getComponentError()); - - List> bindingStatuses = status - .getFieldValidationStatuses(); - Assert.assertNotNull(bindingStatuses); - Assert.assertEquals(1, status.getFieldValidationErrors().size()); - Assert.assertEquals(2, bindingStatuses.size()); - - ValidationStatus r = bindingStatuses.get(0); - Assert.assertTrue(r.isError()); - Assert.assertEquals(EMPTY_ERROR_MESSAGE, r.getMessage().get()); - Assert.assertEquals(nameField, r.getField()); - - r = bindingStatuses.get(1); - Assert.assertFalse(r.isError()); - Assert.assertFalse(r.getMessage().isPresent()); - Assert.assertEquals(ageField, r.getField()); - - Assert.assertEquals(0, status.getBeanValidationResults().size()); - Assert.assertEquals(0, status.getBeanValidationErrors().size()); - - nameField.setValue("foo"); - ageField.setValue(""); - - statusCapture.set(null); - // Second validation succeeds => should be result with OK status and - // no message, and error result for age - binder.validate(); - - status = statusCapture.get(); - bindingStatuses = status.getFieldValidationStatuses(); - Assert.assertEquals(1, status.getFieldValidationErrors().size()); - Assert.assertEquals(2, bindingStatuses.size()); - - r = bindingStatuses.get(0); - Assert.assertFalse(r.isError()); - Assert.assertFalse(r.getMessage().isPresent()); - Assert.assertEquals(nameField, r.getField()); - - r = bindingStatuses.get(1); - Assert.assertTrue(r.isError()); - Assert.assertEquals("Value must be a number", r.getMessage().get()); - Assert.assertEquals(ageField, r.getField()); - - Assert.assertEquals(0, status.getBeanValidationResults().size()); - Assert.assertEquals(0, status.getBeanValidationErrors().size()); - - statusCapture.set(null); - // binding validations pass, binder validation fails - ageField.setValue("0"); - binder.validate(); - - status = statusCapture.get(); - bindingStatuses = status.getFieldValidationStatuses(); - Assert.assertEquals(0, status.getFieldValidationErrors().size()); - Assert.assertEquals(2, bindingStatuses.size()); - - Assert.assertEquals(1, status.getBeanValidationResults().size()); - Assert.assertEquals(1, status.getBeanValidationErrors().size()); - - Assert.assertEquals("Need first name and age", - status.getBeanValidationErrors().get(0).getMessage().get()); - } - - @Test - public void binderWithStatusHandler_defaultStatusHandlerIsReplaced() { - Binding binding = binder.forField(nameField) - .withValidator(notEmpty).withValidationStatusHandler(evt -> { - }); - binding.bind(Person::getFirstName, Person::setFirstName); - - Assert.assertNull(nameField.getComponentError()); - - nameField.setValue(""); - - // First validation fails => should be event with ERROR status and - // message - binding.validate(); - - // no component error since default handler is replaced - Assert.assertNull(nameField.getComponentError()); - } - - @Test - public void binderWithStatusLabel_defaultStatusHandlerIsReplaced() { - Label label = new Label(); - - Binding binding = binder.forField(nameField) - .withValidator(notEmpty).withStatusLabel(label); - binding.bind(Person::getFirstName, Person::setFirstName); - - Assert.assertNull(nameField.getComponentError()); - - nameField.setValue(""); - - // First validation fails => should be event with ERROR status and - // message - binding.validate(); - - // default behavior should update component error for the nameField - Assert.assertNull(nameField.getComponentError()); - } - - @Test(expected = IllegalStateException.class) - public void binderWithStatusHandler_addAfterBound() { - Binding binding = binder.forField(nameField) - .withValidator(notEmpty); - binding.bind(Person::getFirstName, Person::setFirstName); - - binding.withValidationStatusHandler(evt -> Assert.fail()); - } - - @Test(expected = IllegalStateException.class) - public void binderWithStatusLabel_addAfterBound() { - Label label = new Label(); - - Binding binding = binder.forField(nameField) - .withValidator(notEmpty); - binding.bind(Person::getFirstName, Person::setFirstName); - - binding.withStatusLabel(label); - } - - @Test(expected = IllegalStateException.class) - public void binderWithStatusLabel_setAfterHandler() { - Label label = new Label(); - - Binding binding = binder.forField(nameField); - binding.bind(Person::getFirstName, Person::setFirstName); - - binder.setValidationStatusHandler(event -> { - }); - - binder.setStatusLabel(label); - } - - @Test(expected = IllegalStateException.class) - public void binderWithStatusHandler_setAfterLabel() { - Label label = new Label(); - - Binding binding = binder.forField(nameField); - binding.bind(Person::getFirstName, Person::setFirstName); - - binder.setStatusLabel(label); - - binder.setValidationStatusHandler(event -> { - }); - } - - @Test(expected = NullPointerException.class) - public void binderWithNullStatusHandler_throws() { - binder.setValidationStatusHandler(null); - } - - @Test - public void binderWithStatusHandler_replaceHandler() { - AtomicReference> capture = new AtomicReference<>(); - - Binding binding = binder.forField(nameField); - binding.bind(Person::getFirstName, Person::setFirstName); - - binder.setValidationStatusHandler(results -> { - Assert.fail(); - }); - - binder.setValidationStatusHandler(results -> { - capture.set(results); - }); - - nameField.setValue("foo"); - binder.validate(); - - List> results = capture.get() - .getFieldValidationStatuses(); - Assert.assertNotNull(results); - Assert.assertEquals(1, results.size()); - Assert.assertFalse(results.get(0).isError()); - } - - @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); - Assert.assertFalse(binder.hasChanges()); - binder.bind(p); - Assert.assertFalse(binder.hasChanges()); - - nameField.setValue("foo"); - Assert.assertTrue(binder.hasChanges()); - binder.load(p); - Assert.assertFalse(binder.hasChanges()); - - nameField.setValue("bar"); - binder.saveIfValid(new Person()); - Assert.assertFalse(binder.hasChanges()); - - nameField.setValue("baz"); - binder.save(new Person()); - Assert.assertFalse(binder.hasChanges()); - - nameField.setValue(""); - binder.saveIfValid(new Person()); - Assert.assertTrue(binder.hasChanges()); - } - - @Test - public void binderBindAndLoad_clearsErrors() { - Binding binding = binder.forField(nameField) - .withValidator(notEmpty); - binding.bind(Person::getFirstName, Person::setFirstName); - binder.withValidator(bean -> bean.getFirstName().contains("error") - ? Result.error("error") : Result.ok(bean)); - Person person = new Person(); - person.setFirstName(""); - binder.bind(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.bind(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.load(person); - Assert.assertNull(nameField.getComponentError()); - - // bind a new field that has invalid value in bean - TextField lastNameField = new TextField(); - person.setLastName(""); - Binding 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.load(person); - Assert.assertEquals("", statusLabel.getValue()); - - // unbind() 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.unbind(); - Assert.assertNull(nameField.getComponentError()); - Assert.assertNull(lastNameField.getComponentError()); - Assert.assertEquals("", statusLabel.getValue()); - } - - @Test - public void binderLoad_withCrossFieldValidation_clearsErrors() { - TextField lastNameField = new TextField(); - final Predicate lengthPredicate = v -> v.length() > 2; - - Binding firstNameBinding = binder - .forField(nameField).withValidator(lengthPredicate, "length"); - firstNameBinding.bind(Person::getFirstName, Person::setFirstName); - - Binding lastNameBinding = binder - .forField(lastNameField) - .withValidator(v -> !nameField.getValue().isEmpty() - || lengthPredicate.test(v), "err") - .withValidator(lengthPredicate, "length"); - lastNameBinding.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.bind(person); - - Assert.assertNull(nameField.getComponentError()); - Assert.assertNull(lastNameField.getComponentError()); - - nameField.setValue("x"); - - Assert.assertNotNull(nameField.getComponentError()); - Assert.assertNotNull(lastNameField.getComponentError()); - - binder.bind(person); - - Assert.assertNull(nameField.getComponentError()); - Assert.assertNull(lastNameField.getComponentError()); + binder.bind(item); } } diff --git a/server/src/test/java/com/vaadin/data/BinderTestBase.java b/server/src/test/java/com/vaadin/data/BinderTestBase.java new file mode 100644 index 0000000000..978475cebb --- /dev/null +++ b/server/src/test/java/com/vaadin/data/BinderTestBase.java @@ -0,0 +1,57 @@ +/* + * 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 org.junit.Before; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.ui.TextField; + +/** + * A base class for {@code Binder} unit tests. + * + * @author Vaadin Ltd. + * + * @since + */ +public abstract class BinderTestBase, ITEM> { + + protected static final String NEGATIVE_ERROR_MESSAGE = "Value must be non-negative"; + + protected static final String NOT_NUMBER_ERROR_MESSAGE = "Value must be a number"; + + protected static final String EMPTY_ERROR_MESSAGE = "Value cannot be empty"; + + protected BINDER binder; + + protected ITEM item; + + protected TextField nameField; + protected TextField ageField; + + protected Validator notEmpty = Validator.from(val -> !val.isEmpty(), + EMPTY_ERROR_MESSAGE); + protected Converter stringToInteger = Converter.from( + Integer::valueOf, String::valueOf, e -> NOT_NUMBER_ERROR_MESSAGE); + protected Validator notNegative = Validator.from(x -> x >= 0, + NEGATIVE_ERROR_MESSAGE); + + @Before + public void setUpBase() { + nameField = new TextField(); + ageField = new TextField(); + } +} diff --git a/server/src/test/java/com/vaadin/data/BinderValidationStatusTest.java b/server/src/test/java/com/vaadin/data/BinderValidationStatusTest.java new file mode 100644 index 0000000000..25edfaf347 --- /dev/null +++ b/server/src/test/java/com/vaadin/data/BinderValidationStatusTest.java @@ -0,0 +1,507 @@ +/* + * 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.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.Binder.Binding; +import com.vaadin.data.ValidationStatus.Status; +import com.vaadin.tests.data.bean.Person; +import com.vaadin.ui.Label; + +public class BinderValidationStatusTest extends + BinderTestBase, Person> { + + protected final static ValidationStatusHandler NOOP = event -> { + }; + + @Before + public void setUp() { + binder = new Binder<>(); + item = new Person(); + item.setFirstName("Johannes"); + item.setAge(32); + } + + // + // Binding-level status handler + // + + @Test + public void bindingWithStatusHandler_handlerGetsEvents() { + AtomicReference> statusCapture = new AtomicReference<>(); + Binding binding = binder.forField(nameField) + .withValidator(notEmpty).withValidationStatusHandler(evt -> { + Assert.assertNull(statusCapture.get()); + statusCapture.set(evt); + }); + binding.bind(Person::getFirstName, Person::setFirstName); + + nameField.setValue(""); + + // First validation fails => should be event with ERROR status and + // message + binder.validate(); + + Assert.assertNotNull(statusCapture.get()); + ValidationStatus evt = statusCapture.get(); + Assert.assertEquals(Status.ERROR, evt.getStatus()); + Assert.assertEquals(EMPTY_ERROR_MESSAGE, evt.getMessage().get()); + Assert.assertEquals(nameField, evt.getField()); + + nameField.setValue("foo"); + + statusCapture.set(null); + // Second validation succeeds => should be event with OK status and + // no message + binder.validate(); + + evt = statusCapture.get(); + Assert.assertNotNull(evt); + Assert.assertEquals(Status.OK, evt.getStatus()); + Assert.assertFalse(evt.getMessage().isPresent()); + Assert.assertEquals(nameField, evt.getField()); + } + + @Test + public void bindingWithStatusHandler_defaultStatusHandlerIsReplaced() { + Binding binding = binder.forField(nameField) + .withValidator(notEmpty).withValidationStatusHandler(evt -> { + }); + binding.bind(Person::getFirstName, Person::setFirstName); + + Assert.assertNull(nameField.getComponentError()); + + nameField.setValue(""); + + // First validation fails => should be event with ERROR status and + // message + binding.validate(); + + // default behavior should update component error for the nameField + Assert.assertNull(nameField.getComponentError()); + } + + @Test + public void bindingWithStatusLabel_labelIsUpdatedAccordingStatus() { + Label label = new Label(); + + Binding binding = binder.forField(nameField) + .withValidator(notEmpty).withStatusLabel(label); + binding.bind(Person::getFirstName, Person::setFirstName); + + nameField.setValue(""); + + // First validation fails => should be event with ERROR status and + // message + binding.validate(); + + Assert.assertTrue(label.isVisible()); + Assert.assertEquals(EMPTY_ERROR_MESSAGE, label.getValue()); + + nameField.setValue("foo"); + + // Second validation succeeds => should be event with OK status and + // no message + binding.validate(); + + Assert.assertFalse(label.isVisible()); + Assert.assertEquals("", label.getValue()); + } + + @Test + public void bindingWithStatusLabel_defaultStatusHandlerIsReplaced() { + Label label = new Label(); + + Binding binding = binder.forField(nameField) + .withValidator(notEmpty).withStatusLabel(label); + binding.bind(Person::getFirstName, Person::setFirstName); + + Assert.assertNull(nameField.getComponentError()); + + nameField.setValue(""); + + // First validation fails => should be event with ERROR status and + // message + binding.validate(); + + // default behavior should update component error for the nameField + Assert.assertNull(nameField.getComponentError()); + } + + @Test(expected = IllegalStateException.class) + public void bindingWithStatusHandler_addAfterBound() { + Binding binding = binder.forField(nameField) + .withValidator(notEmpty); + binding.bind(Person::getFirstName, Person::setFirstName); + + binding.withValidationStatusHandler(evt -> Assert.fail()); + } + + @Test(expected = IllegalStateException.class) + public void bindingWithStatusLabel_addAfterBound() { + Label label = new Label(); + + Binding binding = binder.forField(nameField) + .withValidator(notEmpty); + binding.bind(Person::getFirstName, Person::setFirstName); + + binding.withStatusLabel(label); + } + + @Test(expected = IllegalStateException.class) + public void bindingWithStatusLabel_setAfterHandler() { + Label label = new Label(); + + Binding binding = binder.forField(nameField); + + binding.withValidationStatusHandler(NOOP); + + binding.withStatusLabel(label); + } + + @Test(expected = IllegalStateException.class) + public void bindingWithStatusHandler_setAfterLabel() { + Label label = new Label(); + + Binding binding = binder.forField(nameField); + + binding.withStatusLabel(label); + + binding.withValidationStatusHandler(NOOP); + } + + @Test(expected = IllegalStateException.class) + public void bindingWithStatusHandler_setAfterOtherHandler() { + + Binding binding = binder.forField(nameField); + + binding.withValidationStatusHandler(NOOP); + + binding.withValidationStatusHandler(NOOP); + } + + // + // Binder-level status handler + // + + @Test + public void binderWithStatusHandler_fieldValidationNoBeanValidation_handlerGetsStatusUpdates() { + AtomicReference> statusCapture = new AtomicReference<>(); + binder.forField(nameField).withValidator(notEmpty) + .withValidationStatusHandler(evt -> { + Assert.fail( + "Using a custom status change handler so no change should end up here"); + }).bind(Person::getFirstName, Person::setFirstName); + binder.forField(ageField).withConverter(stringToInteger) + .withValidator(notNegative).withValidationStatusHandler(evt -> { + Assert.fail( + "Using a custom status change handler so no change should end up here"); + }).bind(Person::getAge, Person::setAge); + + binder.setValidationStatusHandler(r -> { + statusCapture.set(r); + }); + binder.bind(item); + Assert.assertNull(nameField.getComponentError()); + + nameField.setValue(""); + ageField.setValue("5"); + + // First binding validation fails => should be result with ERROR status + // and message + BinderValidationStatus status2 = binder.validate(); + BinderValidationStatus status = statusCapture.get(); + Assert.assertSame(status2, status); + + Assert.assertNull(nameField.getComponentError()); + + List> bindingStatuses = status + .getFieldValidationStatuses(); + Assert.assertNotNull(bindingStatuses); + Assert.assertEquals(1, status.getFieldValidationErrors().size()); + Assert.assertEquals(2, bindingStatuses.size()); + + ValidationStatus r = bindingStatuses.get(0); + Assert.assertTrue(r.isError()); + Assert.assertEquals(EMPTY_ERROR_MESSAGE, r.getMessage().get()); + Assert.assertEquals(nameField, r.getField()); + + r = bindingStatuses.get(1); + Assert.assertFalse(r.isError()); + Assert.assertFalse(r.getMessage().isPresent()); + Assert.assertEquals(ageField, r.getField()); + + Assert.assertEquals(0, status.getBeanValidationResults().size()); + Assert.assertEquals(0, status.getBeanValidationErrors().size()); + + nameField.setValue("foo"); + ageField.setValue(""); + + statusCapture.set(null); + // Second validation succeeds => should be result with OK status and + // no message, and error result for age + binder.validate(); + + status = statusCapture.get(); + bindingStatuses = status.getFieldValidationStatuses(); + Assert.assertEquals(1, status.getFieldValidationErrors().size()); + Assert.assertEquals(2, bindingStatuses.size()); + + r = bindingStatuses.get(0); + Assert.assertFalse(r.isError()); + Assert.assertFalse(r.getMessage().isPresent()); + Assert.assertEquals(nameField, r.getField()); + + r = bindingStatuses.get(1); + Assert.assertTrue(r.isError()); + Assert.assertEquals("Value must be a number", r.getMessage().get()); + Assert.assertEquals(ageField, r.getField()); + + Assert.assertEquals(0, status.getBeanValidationResults().size()); + Assert.assertEquals(0, status.getBeanValidationErrors().size()); + + statusCapture.set(null); + // binding validations pass, binder validation fails + ageField.setValue("0"); + binder.validate(); + + status = statusCapture.get(); + bindingStatuses = status.getFieldValidationStatuses(); + Assert.assertEquals(0, status.getFieldValidationErrors().size()); + Assert.assertEquals(2, bindingStatuses.size()); + + Assert.assertEquals(0, status.getBeanValidationResults().size()); + Assert.assertEquals(0, status.getBeanValidationErrors().size()); + } + + @Test + public void binderWithStatusHandler_fieldAndBeanLevelValidation_handlerGetsStatusUpdates() { + AtomicReference> statusCapture = new AtomicReference<>(); + binder.forField(nameField).withValidator(notEmpty) + .withValidationStatusHandler(evt -> { + Assert.fail( + "Using a custom status change handler so no change should end up here"); + }).bind(Person::getFirstName, Person::setFirstName); + binder.forField(ageField).withConverter(stringToInteger) + .withValidator(notNegative).withValidationStatusHandler(evt -> { + Assert.fail( + "Using a custom status change handler so no change should end up here"); + }).bind(Person::getAge, Person::setAge); + binder.withValidator( + bean -> !bean.getFirstName().isEmpty() && bean.getAge() > 0 + ? Result.ok(bean) + : Result.error("Need first name and age")); + + binder.setValidationStatusHandler(r -> { + statusCapture.set(r); + }); + binder.bind(item); + Assert.assertNull(nameField.getComponentError()); + + nameField.setValue(""); + ageField.setValue("5"); + + // First binding validation fails => should be result with ERROR status + // and message + BinderValidationStatus status2 = binder.validate(); + BinderValidationStatus status = statusCapture.get(); + Assert.assertSame(status2, status); + + Assert.assertNull(nameField.getComponentError()); + + List> bindingStatuses = status + .getFieldValidationStatuses(); + Assert.assertNotNull(bindingStatuses); + Assert.assertEquals(1, status.getFieldValidationErrors().size()); + Assert.assertEquals(2, bindingStatuses.size()); + + ValidationStatus r = bindingStatuses.get(0); + Assert.assertTrue(r.isError()); + Assert.assertEquals(EMPTY_ERROR_MESSAGE, r.getMessage().get()); + Assert.assertEquals(nameField, r.getField()); + + r = bindingStatuses.get(1); + Assert.assertFalse(r.isError()); + Assert.assertFalse(r.getMessage().isPresent()); + Assert.assertEquals(ageField, r.getField()); + + Assert.assertEquals(0, status.getBeanValidationResults().size()); + Assert.assertEquals(0, status.getBeanValidationErrors().size()); + + nameField.setValue("foo"); + ageField.setValue(""); + + statusCapture.set(null); + // Second validation succeeds => should be result with OK status and + // no message, and error result for age + binder.validate(); + + status = statusCapture.get(); + bindingStatuses = status.getFieldValidationStatuses(); + Assert.assertEquals(1, status.getFieldValidationErrors().size()); + Assert.assertEquals(2, bindingStatuses.size()); + + r = bindingStatuses.get(0); + Assert.assertFalse(r.isError()); + Assert.assertFalse(r.getMessage().isPresent()); + Assert.assertEquals(nameField, r.getField()); + + r = bindingStatuses.get(1); + Assert.assertTrue(r.isError()); + Assert.assertEquals("Value must be a number", r.getMessage().get()); + Assert.assertEquals(ageField, r.getField()); + + Assert.assertEquals(0, status.getBeanValidationResults().size()); + Assert.assertEquals(0, status.getBeanValidationErrors().size()); + + statusCapture.set(null); + // binding validations pass, binder validation fails + ageField.setValue("0"); + binder.validate(); + + status = statusCapture.get(); + bindingStatuses = status.getFieldValidationStatuses(); + Assert.assertEquals(0, status.getFieldValidationErrors().size()); + Assert.assertEquals(2, bindingStatuses.size()); + + Assert.assertEquals(1, status.getBeanValidationResults().size()); + Assert.assertEquals(1, status.getBeanValidationErrors().size()); + + Assert.assertEquals("Need first name and age", + status.getBeanValidationErrors().get(0).getMessage().get()); + } + + @Test + public void binderWithStatusHandler_defaultStatusHandlerIsReplaced() { + Binding binding = binder.forField(nameField) + .withValidator(notEmpty).withValidationStatusHandler(evt -> { + }); + binding.bind(Person::getFirstName, Person::setFirstName); + + Assert.assertNull(nameField.getComponentError()); + + nameField.setValue(""); + + // First validation fails => should be event with ERROR status and + // message + binding.validate(); + + // no component error since default handler is replaced + Assert.assertNull(nameField.getComponentError()); + } + + @Test + public void binderWithStatusLabel_defaultStatusHandlerIsReplaced() { + Label label = new Label(); + + Binding binding = binder.forField(nameField) + .withValidator(notEmpty).withStatusLabel(label); + binding.bind(Person::getFirstName, Person::setFirstName); + + Assert.assertNull(nameField.getComponentError()); + + nameField.setValue(""); + + // First validation fails => should be event with ERROR status and + // message + binding.validate(); + + // default behavior should update component error for the nameField + Assert.assertNull(nameField.getComponentError()); + } + + @Test(expected = IllegalStateException.class) + public void binderWithStatusHandler_addAfterBound() { + Binding binding = binder.forField(nameField) + .withValidator(notEmpty); + binding.bind(Person::getFirstName, Person::setFirstName); + + binding.withValidationStatusHandler(evt -> Assert.fail()); + } + + @Test(expected = IllegalStateException.class) + public void binderWithStatusLabel_addAfterBound() { + Label label = new Label(); + + Binding binding = binder.forField(nameField) + .withValidator(notEmpty); + binding.bind(Person::getFirstName, Person::setFirstName); + + binding.withStatusLabel(label); + } + + @Test(expected = IllegalStateException.class) + public void binderWithStatusLabel_setAfterHandler() { + Label label = new Label(); + + Binding binding = binder.forField(nameField); + binding.bind(Person::getFirstName, Person::setFirstName); + + binder.setValidationStatusHandler(event -> { + }); + + binder.setStatusLabel(label); + } + + @Test(expected = IllegalStateException.class) + public void binderWithStatusHandler_setAfterLabel() { + Label label = new Label(); + + Binding binding = binder.forField(nameField); + binding.bind(Person::getFirstName, Person::setFirstName); + + binder.setStatusLabel(label); + + binder.setValidationStatusHandler(event -> { + }); + } + + @Test(expected = NullPointerException.class) + public void binderWithNullStatusHandler_throws() { + binder.setValidationStatusHandler(null); + } + + @Test + public void binderWithStatusHandler_replaceHandler() { + AtomicReference> capture = new AtomicReference<>(); + + Binding binding = binder.forField(nameField); + binding.bind(Person::getFirstName, Person::setFirstName); + + binder.setValidationStatusHandler(results -> { + Assert.fail(); + }); + + binder.setValidationStatusHandler(results -> { + capture.set(results); + }); + + nameField.setValue("foo"); + binder.validate(); + + List> results = capture.get() + .getFieldValidationStatuses(); + Assert.assertNotNull(results); + Assert.assertEquals(1, results.size()); + Assert.assertFalse(results.get(0).isError()); + } +} -- 2.39.5