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.assertSame; import static org.junit.Assert.assertTrue; import java.util.Locale; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.vaadin.data.Binder.BindingBuilder; import com.vaadin.data.converter.StringToIntegerConverter; import com.vaadin.data.validator.NotEmptyValidator; import com.vaadin.server.ErrorMessage; import com.vaadin.tests.data.bean.Person; import com.vaadin.tests.data.bean.Sex; import com.vaadin.ui.TextField; public class BinderTest extends BinderTestBase<Binder<Person>, Person> { @Before public void setUp() { binder = new Binder<>(); item = new Person(); item.setFirstName("Johannes"); item.setAge(32); } @Test public void bindNullBean_noBeanPresent() { binder.setBean(item); assertNotNull(binder.getBean()); binder.setBean(null); assertNull(binder.getBean()); } @Test public void bindNullBean_FieldsAreCleared() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.forField(ageField) .withConverter(new StringToIntegerConverter("")) .bind(Person::getAge, Person::setAge); binder.setBean(item); assertEquals("No name field value","Johannes", nameField.getValue()); assertEquals("No age field value", "32", ageField.getValue()); binder.setBean(null); assertEquals("Name field not empty", "", nameField.getValue()); assertEquals("Age field not empty", "", ageField.getValue()); } @Test public void clearForReadBean_boundFieldsAreCleared() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.forField(ageField) .withConverter(new StringToIntegerConverter("")) .bind(Person::getAge, Person::setAge); binder.readBean(item); assertEquals("No name field value","Johannes", nameField.getValue()); assertEquals("No age field value", "32", ageField.getValue()); binder.readBean(null); assertEquals("Name field not empty", "", nameField.getValue()); assertEquals("Age field not empty", "", ageField.getValue()); } @Test public void clearReadOnlyField_shouldClearField() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); // Make name field read only nameField.setReadOnly(true); binder.setBean(item); assertEquals("No name field value", "Johannes", nameField.getValue()); binder.setBean(null); assertEquals("ReadOnly field not empty","", nameField.getValue()); } @Test public void clearBean_setsHasChangesToFalse() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); // Make name field read only nameField.setReadOnly(true); binder.readBean(item); assertEquals("No name field value", "Johannes", nameField.getValue()); nameField.setValue("James"); assertTrue("Binder did not have value changes", binder.hasChanges()); binder.readBean(null); assertFalse("Binder has changes after clearing all fields", binder.hasChanges()); } @Test public void clearReadOnlyBinder_shouldClearFields() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.forField(ageField) .withConverter(new StringToIntegerConverter("")) .bind(Person::getAge, Person::setAge); binder.setReadOnly(true); binder.setBean(item); binder.setBean(null); assertEquals("ReadOnly name field not empty", "", nameField.getValue()); assertEquals("ReadOnly age field not empty", "", ageField.getValue()); } @Test(expected = NullPointerException.class) public void bindNullField_throws() { binder.forField(null); } @Test(expected = NullPointerException.class) public void bindNullGetter_throws() { binder.bind(nameField, null, Person::setFirstName); } @Test public void fieldBound_bindItem_fieldValueUpdated() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.setBean(item); assertEquals("Johannes", nameField.getValue()); } @Test public void fieldBoundWithShortcut_bindBean_fieldValueUpdated() { bindName(); assertEquals("Johannes", nameField.getValue()); } @Test public void beanBound_updateFieldValue_beanValueUpdated() { binder.setBean(item); binder.bind(nameField, Person::getFirstName, Person::setFirstName); assertEquals("Johannes", nameField.getValue()); nameField.setValue("Artur"); assertEquals("Artur", item.getFirstName()); } @Test public void bound_getBean_returnsBoundBean() { assertNull(binder.getBean()); binder.setBean(item); assertSame(item, binder.getBean()); } @Test public void unbound_getBean_returnsNothing() { binder.setBean(item); binder.removeBean(); assertNull(binder.getBean()); } @Test public void bound_changeFieldValue_beanValueUpdated() { bindName(); nameField.setValue("Henri"); assertEquals("Henri", item.getFirstName()); } @Test public void unbound_changeFieldValue_beanValueNotUpdated() { bindName(); nameField.setValue("Henri"); binder.removeBean(); nameField.setValue("Aleksi"); assertEquals("Henri", item.getFirstName()); } @Test public void bindNullSetter_valueChangesIgnored() { binder.bind(nameField, Person::getFirstName, null); binder.setBean(item); nameField.setValue("Artur"); assertEquals(item.getFirstName(), "Johannes"); } @Test public void bound_bindToAnotherBean_stopsUpdatingOriginal() { bindName(); nameField.setValue("Leif"); Person p2 = new Person(); p2.setFirstName("Marlon"); binder.setBean(p2); assertEquals("Marlon", nameField.getValue()); assertEquals("Leif", item.getFirstName()); assertSame(p2, binder.getBean()); nameField.setValue("Ilia"); assertEquals("Ilia", p2.getFirstName()); assertEquals("Leif", item.getFirstName()); } @Test public void save_unbound_noChanges() throws ValidationException { Binder<Person> binder = new Binder<>(); Person person = new Person(); int age = 10; person.setAge(age); binder.writeBean(person); Assert.assertEquals(age, person.getAge()); } @Test public void save_bound_beanIsUpdated() throws ValidationException { Binder<Person> binder = new Binder<>(); binder.bind(nameField, Person::getFirstName, Person::setFirstName); Person person = new Person(); String fieldValue = "bar"; nameField.setValue(fieldValue); person.setFirstName("foo"); binder.writeBean(person); Assert.assertEquals(fieldValue, person.getFirstName()); } @Test public void load_bound_fieldValueIsUpdated() { binder.bind(nameField, Person::getFirstName, Person::setFirstName); Person person = new Person(); String name = "bar"; person.setFirstName(name); binder.readBean(person); Assert.assertEquals(name, nameField.getValue()); } @Test public void load_unbound_noChanges() { nameField.setValue(""); Person person = new Person(); String name = "bar"; person.setFirstName(name); binder.readBean(person); Assert.assertEquals("", nameField.getValue()); } protected void bindName() { binder.bind(nameField, Person::getFirstName, Person::setFirstName); binder.setBean(item); } @Test public void binding_with_null_representation() { String nullRepresentation = "Some arbitrary text"; String realName = "John"; Person namelessPerson = new Person(null, "Doe", "", 25, Sex.UNKNOWN, null); binder.forField(nameField).withNullRepresentation(nullRepresentation) .bind(Person::getFirstName, Person::setFirstName); // Bind a person with null value and check that null representation is // used binder.setBean(namelessPerson); Assert.assertEquals( "Null value from bean was not converted to explicit null representation", nullRepresentation, nameField.getValue()); // Verify that changes are applied to bean nameField.setValue(realName); Assert.assertEquals( "Bean was not correctly updated from a change in the field", realName, namelessPerson.getFirstName()); // Verify conversion back to null nameField.setValue(nullRepresentation); Assert.assertEquals( "Two-way null representation did not change value back to null", null, namelessPerson.getFirstName()); } @Test public void binding_with_default_null_representation() { TextField nullTextField = new TextField() { @Override public String getEmptyValue() { return "null"; } }; Person namelessPerson = new Person(null, "Doe", "", 25, Sex.UNKNOWN, null); binder.bind(nullTextField, Person::getFirstName, Person::setFirstName); binder.setBean(namelessPerson); assertTrue(nullTextField.isEmpty()); Assert.assertEquals(null, namelessPerson.getFirstName()); // Change value, see that textfield is not empty and bean is updated. nullTextField.setValue(""); assertFalse(nullTextField.isEmpty()); Assert.assertEquals("First name of person was not properly updated", "", namelessPerson.getFirstName()); // Verify that default null representation does not map back to null nullTextField.setValue("null"); assertTrue(nullTextField.isEmpty()); Assert.assertEquals("Default one-way null representation failed.", "null", namelessPerson.getFirstName()); } @Test public void binding_with_null_representation_value_not_null() { String nullRepresentation = "Some arbitrary text"; binder.forField(nameField).withNullRepresentation(nullRepresentation) .bind(Person::getFirstName, Person::setFirstName); assertFalse("First name in item should not be null", Objects.isNull(item.getFirstName())); binder.setBean(item); Assert.assertEquals("Field value was not set correctly", item.getFirstName(), nameField.getValue()); } @Test public void withConverter_disablesDefaulNullRepresentation() { Integer customNullConverter = 0; binder.forField(ageField).withNullRepresentation("foo") .withConverter(new StringToIntegerConverter("")) .withConverter(age -> age, age -> age == null ? customNullConverter : age) .bind(Person::getSalary, Person::setSalary); binder.setBean(item); Assert.assertEquals(customNullConverter.toString(), ageField.getValue()); Integer salary = 11; ageField.setValue(salary.toString()); Assert.assertEquals(11, salary.intValue()); } @Test public void beanBinder_nullRepresentationIsNotDisabled() { Binder<Person> binder = new Binder<>(Person.class); binder.forField(nameField).bind("firstName"); Person person = new Person(); binder.setBean(person); Assert.assertEquals("", nameField.getValue()); } @Test public void beanBinder_withConverter_nullRepresentationIsNotDisabled() { String customNullPointerRepresentation = "foo"; Binder<Person> binder = new Binder<>(Person.class); binder.forField(nameField) .withConverter(value -> value, value -> value == null ? customNullPointerRepresentation : value) .bind("firstName"); Person person = new Person(); binder.setBean(person); Assert.assertEquals(customNullPointerRepresentation, nameField.getValue()); } @Test public void withValidator_doesNotDisablesDefaulNullRepresentation() { String nullRepresentation = "foo"; binder.forField(nameField).withNullRepresentation(nullRepresentation) .withValidator(new NotEmptyValidator<>("")) .bind(Person::getFirstName, Person::setFirstName); item.setFirstName(null); binder.setBean(item); Assert.assertEquals(nullRepresentation, nameField.getValue()); String newValue = "bar"; nameField.setValue(newValue); Assert.assertEquals(newValue, item.getFirstName()); } @Test public void setRequired_withErrorMessage_fieldGetsRequiredIndicatorAndValidator() { TextField textField = new TextField(); assertFalse(textField.isRequiredIndicatorVisible()); BindingBuilder<Person, String> binding = binder.forField(textField); assertFalse(textField.isRequiredIndicatorVisible()); binding.asRequired("foobar"); assertTrue(textField.isRequiredIndicatorVisible()); binding.bind(Person::getFirstName, Person::setFirstName); binder.setBean(item); Assert.assertNull(textField.getErrorMessage()); textField.setValue(textField.getEmptyValue()); ErrorMessage errorMessage = textField.getErrorMessage(); Assert.assertNotNull(errorMessage); Assert.assertEquals("foobar", errorMessage.getFormattedHtmlMessage()); textField.setValue("value"); Assert.assertNull(textField.getErrorMessage()); assertTrue(textField.isRequiredIndicatorVisible()); } @Test public void setRequired_withErrorMessageProvider_fieldGetsRequiredIndicatorAndValidator() { TextField textField = new TextField(); textField.setLocale(Locale.CANADA); assertFalse(textField.isRequiredIndicatorVisible()); BindingBuilder<Person, String> binding = binder.forField(textField); assertFalse(textField.isRequiredIndicatorVisible()); AtomicInteger invokes = new AtomicInteger(); binding.asRequired(context -> { invokes.incrementAndGet(); Assert.assertSame(Locale.CANADA, context.getLocale().get()); return "foobar"; }); assertTrue(textField.isRequiredIndicatorVisible()); binding.bind(Person::getFirstName, Person::setFirstName); binder.setBean(item); Assert.assertNull(textField.getErrorMessage()); Assert.assertEquals(0, invokes.get()); textField.setValue(textField.getEmptyValue()); ErrorMessage errorMessage = textField.getErrorMessage(); Assert.assertNotNull(errorMessage); Assert.assertEquals("foobar", errorMessage.getFormattedHtmlMessage()); // validation is run twice, once for the field, then for all the fields // for cross field validation... Assert.assertEquals(2, invokes.get()); textField.setValue("value"); Assert.assertNull(textField.getErrorMessage()); assertTrue(textField.isRequiredIndicatorVisible()); } @Test public void validationStatusHandler_onlyRunForChangedField() { TextField firstNameField = new TextField(); TextField lastNameField = new TextField(); AtomicInteger invokes = new AtomicInteger(); binder.forField(firstNameField) .withValidator(new NotEmptyValidator<>("")) .withValidationStatusHandler( validationStatus -> invokes.addAndGet(1)) .bind(Person::getFirstName, Person::setFirstName); binder.forField(lastNameField) .withValidator(new NotEmptyValidator<>("")) .bind(Person::getLastName, Person::setLastName); binder.setBean(item); // setting the bean causes 2: Assert.assertEquals(2, invokes.get()); lastNameField.setValue(""); Assert.assertEquals(2, invokes.get()); firstNameField.setValue(""); Assert.assertEquals(3, invokes.get()); binder.removeBean(); Person person = new Person(); person.setFirstName("a"); person.setLastName("a"); binder.readBean(person); // reading from a bean causes 2: Assert.assertEquals(5, invokes.get()); lastNameField.setValue(""); Assert.assertEquals(5, invokes.get()); firstNameField.setValue(""); Assert.assertEquals(6, invokes.get()); } @Test(expected = IllegalStateException.class) public void noArgsConstructor_stringBind_throws() { binder.bind(new TextField(), "firstName"); } @Test public void setReadOnly_unboundBinder() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.forField(ageField); binder.setReadOnly(true); assertTrue(nameField.isReadOnly()); assertFalse(ageField.isReadOnly()); binder.setReadOnly(false); assertFalse(nameField.isReadOnly()); assertFalse(ageField.isReadOnly()); } @Test public void setReadOnly_boundBinder() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.forField(ageField) .withConverter(new StringToIntegerConverter("")) .bind(Person::getAge, Person::setAge); binder.setBean(new Person()); binder.setReadOnly(true); assertTrue(nameField.isReadOnly()); assertTrue(ageField.isReadOnly()); binder.setReadOnly(false); assertFalse(nameField.isReadOnly()); assertFalse(ageField.isReadOnly()); } @Test public void setReadOnly_binderLoadedByReadBean() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.forField(ageField) .withConverter(new StringToIntegerConverter("")) .bind(Person::getAge, Person::setAge); binder.readBean(new Person()); binder.setReadOnly(true); assertTrue(nameField.isReadOnly()); assertTrue(ageField.isReadOnly()); binder.setReadOnly(false); assertFalse(nameField.isReadOnly()); assertFalse(ageField.isReadOnly()); } @Test public void isValidTest_bound_binder() { binder.forField(nameField) .withValidator( Validator.from( name -> !name.equals("fail field validation"), "")) .bind(Person::getFirstName, Person::setFirstName); binder.withValidator( Validator.from(person -> !person.getFirstName() .equals("fail bean validation"), "")); binder.setBean(item); Assert.assertTrue(binder.isValid()); nameField.setValue("fail field validation"); Assert.assertFalse(binder.isValid()); nameField.setValue(""); Assert.assertTrue(binder.isValid()); nameField.setValue("fail bean validation"); Assert.assertFalse(binder.isValid()); } @Test public void isValidTest_unbound_binder() { binder.forField(nameField) .withValidator(Validator.from( name -> !name.equals("fail field validation"), "")) .bind(Person::getFirstName, Person::setFirstName); Assert.assertTrue(binder.isValid()); nameField.setValue("fail field validation"); Assert.assertFalse(binder.isValid()); nameField.setValue(""); Assert.assertTrue(binder.isValid()); } @Test(expected = IllegalStateException.class) public void isValidTest_unbound_binder_throws_with_bean_level_validation() { binder.forField(nameField).bind(Person::getFirstName, Person::setFirstName); binder.withValidator(Validator.from( person -> !person.getFirstName().equals("fail bean validation"), "")); binder.isValid(); } }