You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BinderTest.java 53KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566
  1. package com.vaadin.data;
  2. import static org.junit.Assert.assertArrayEquals;
  3. import static org.junit.Assert.assertEquals;
  4. import static org.junit.Assert.assertFalse;
  5. import static org.junit.Assert.assertNotEquals;
  6. import static org.junit.Assert.assertNotNull;
  7. import static org.junit.Assert.assertNull;
  8. import static org.junit.Assert.assertSame;
  9. import static org.junit.Assert.assertTrue;
  10. import static org.junit.Assert.fail;
  11. import java.math.BigDecimal;
  12. import java.text.DecimalFormat;
  13. import java.text.NumberFormat;
  14. import java.util.Locale;
  15. import java.util.Objects;
  16. import java.util.concurrent.atomic.AtomicBoolean;
  17. import java.util.concurrent.atomic.AtomicInteger;
  18. import java.util.concurrent.atomic.AtomicReference;
  19. import java.util.stream.Stream;
  20. import org.junit.Assert;
  21. import org.junit.Before;
  22. import org.junit.Test;
  23. import org.junit.Rule;
  24. import org.junit.rules.ExpectedException;
  25. import com.vaadin.data.Binder.Binding;
  26. import com.vaadin.data.Binder.BindingBuilder;
  27. import com.vaadin.data.converter.StringToBigDecimalConverter;
  28. import com.vaadin.data.converter.StringToIntegerConverter;
  29. import com.vaadin.data.validator.IntegerRangeValidator;
  30. import com.vaadin.data.validator.NotEmptyValidator;
  31. import com.vaadin.data.validator.StringLengthValidator;
  32. import com.vaadin.server.ErrorMessage;
  33. import com.vaadin.shared.ui.ErrorLevel;
  34. import com.vaadin.tests.data.bean.Person;
  35. import com.vaadin.tests.data.bean.Sex;
  36. import com.vaadin.ui.TextField;
  37. import org.apache.commons.lang.StringUtils;
  38. import org.hamcrest.CoreMatchers;
  39. public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
  40. @Rule
  41. /*
  42. * transient to avoid interfering with serialization tests that capture a
  43. * test instance in a closure
  44. */
  45. public transient ExpectedException exceptionRule = ExpectedException.none();
  46. @Before
  47. public void setUp() {
  48. binder = new Binder<>();
  49. item = new Person();
  50. item.setFirstName("Johannes");
  51. item.setAge(32);
  52. }
  53. @Test
  54. public void bindNullBean_noBeanPresent() {
  55. binder.setBean(item);
  56. assertNotNull(binder.getBean());
  57. binder.setBean(null);
  58. assertNull(binder.getBean());
  59. }
  60. @Test
  61. public void bindNullBean_FieldsAreCleared() {
  62. binder.forField(nameField).bind(Person::getFirstName,
  63. Person::setFirstName);
  64. binder.forField(ageField)
  65. .withConverter(new StringToIntegerConverter(""))
  66. .bind(Person::getAge, Person::setAge);
  67. binder.setBean(item);
  68. assertEquals("No name field value", "Johannes", nameField.getValue());
  69. assertEquals("No age field value", "32", ageField.getValue());
  70. binder.setBean(null);
  71. assertEquals("Name field not empty", "", nameField.getValue());
  72. assertEquals("Age field not empty", "", ageField.getValue());
  73. }
  74. @Test
  75. public void clearForReadBean_boundFieldsAreCleared() {
  76. binder.forField(nameField).bind(Person::getFirstName,
  77. Person::setFirstName);
  78. binder.forField(ageField)
  79. .withConverter(new StringToIntegerConverter(""))
  80. .bind(Person::getAge, Person::setAge);
  81. binder.readBean(item);
  82. assertEquals("No name field value", "Johannes", nameField.getValue());
  83. assertEquals("No age field value", "32", ageField.getValue());
  84. binder.readBean(null);
  85. assertEquals("Name field not empty", "", nameField.getValue());
  86. assertEquals("Age field not empty", "", ageField.getValue());
  87. }
  88. @Test
  89. public void clearReadOnlyField_shouldClearField() {
  90. binder.forField(nameField).bind(Person::getFirstName,
  91. Person::setFirstName);
  92. // Make name field read only
  93. nameField.setReadOnly(true);
  94. binder.setBean(item);
  95. assertEquals("No name field value", "Johannes", nameField.getValue());
  96. binder.setBean(null);
  97. assertEquals("ReadOnly field not empty", "", nameField.getValue());
  98. }
  99. @Test
  100. public void clearBean_setsHasChangesToFalse() {
  101. binder.forField(nameField).bind(Person::getFirstName,
  102. Person::setFirstName);
  103. // Make name field read only
  104. nameField.setReadOnly(true);
  105. binder.readBean(item);
  106. assertEquals("No name field value", "Johannes", nameField.getValue());
  107. nameField.setValue("James");
  108. assertTrue("Binder did not have value changes", binder.hasChanges());
  109. binder.readBean(null);
  110. assertFalse("Binder has changes after clearing all fields",
  111. binder.hasChanges());
  112. }
  113. @Test
  114. public void clearReadOnlyBinder_shouldClearFields() {
  115. binder.forField(nameField).bind(Person::getFirstName,
  116. Person::setFirstName);
  117. binder.forField(ageField)
  118. .withConverter(new StringToIntegerConverter(""))
  119. .bind(Person::getAge, Person::setAge);
  120. binder.setReadOnly(true);
  121. binder.setBean(item);
  122. binder.setBean(null);
  123. assertEquals("ReadOnly name field not empty", "", nameField.getValue());
  124. assertEquals("ReadOnly age field not empty", "", ageField.getValue());
  125. }
  126. @Test(expected = NullPointerException.class)
  127. public void bindNullField_throws() {
  128. binder.forField(null);
  129. }
  130. @Test(expected = NullPointerException.class)
  131. public void bindNullGetter_throws() {
  132. binder.bind(nameField, null, Person::setFirstName);
  133. }
  134. @Test
  135. public void fieldBound_bindItem_fieldValueUpdated() {
  136. binder.forField(nameField).bind(Person::getFirstName,
  137. Person::setFirstName);
  138. binder.setBean(item);
  139. assertEquals("Johannes", nameField.getValue());
  140. }
  141. @Test
  142. public void fieldBoundWithShortcut_bindBean_fieldValueUpdated() {
  143. bindName();
  144. assertEquals("Johannes", nameField.getValue());
  145. }
  146. @Test
  147. public void beanBound_updateFieldValue_beanValueUpdated() {
  148. binder.setBean(item);
  149. binder.bind(nameField, Person::getFirstName, Person::setFirstName);
  150. assertEquals("Johannes", nameField.getValue());
  151. nameField.setValue("Artur");
  152. assertEquals("Artur", item.getFirstName());
  153. }
  154. @Test
  155. public void bound_getBean_returnsBoundBean() {
  156. assertNull(binder.getBean());
  157. binder.setBean(item);
  158. assertSame(item, binder.getBean());
  159. }
  160. @Test
  161. public void unbound_getBean_returnsNothing() {
  162. binder.setBean(item);
  163. binder.removeBean();
  164. assertNull(binder.getBean());
  165. }
  166. @Test
  167. public void bound_changeFieldValue_beanValueUpdated() {
  168. bindName();
  169. nameField.setValue("Henri");
  170. assertEquals("Henri", item.getFirstName());
  171. }
  172. @Test
  173. public void unbound_changeFieldValue_beanValueNotUpdated() {
  174. bindName();
  175. nameField.setValue("Henri");
  176. binder.removeBean();
  177. nameField.setValue("Aleksi");
  178. assertEquals("Henri", item.getFirstName());
  179. }
  180. @Test
  181. public void bindNullSetter_valueChangesIgnored() {
  182. binder.bind(nameField, Person::getFirstName, null);
  183. binder.setBean(item);
  184. nameField.setValue("Artur");
  185. assertEquals(item.getFirstName(), "Johannes");
  186. }
  187. @Test
  188. public void bound_bindToAnotherBean_stopsUpdatingOriginal() {
  189. bindName();
  190. nameField.setValue("Leif");
  191. Person p2 = new Person();
  192. p2.setFirstName("Marlon");
  193. binder.setBean(p2);
  194. assertEquals("Marlon", nameField.getValue());
  195. assertEquals("Leif", item.getFirstName());
  196. assertSame(p2, binder.getBean());
  197. nameField.setValue("Ilia");
  198. assertEquals("Ilia", p2.getFirstName());
  199. assertEquals("Leif", item.getFirstName());
  200. }
  201. @Test
  202. public void save_unbound_noChanges() throws ValidationException {
  203. Binder<Person> binder = new Binder<>();
  204. Person person = new Person();
  205. int age = 10;
  206. person.setAge(age);
  207. binder.writeBean(person);
  208. assertEquals(age, person.getAge());
  209. }
  210. @Test
  211. public void save_bound_beanIsUpdated() throws ValidationException {
  212. Binder<Person> binder = new Binder<>();
  213. binder.bind(nameField, Person::getFirstName, Person::setFirstName);
  214. Person person = new Person();
  215. String fieldValue = "bar";
  216. nameField.setValue(fieldValue);
  217. person.setFirstName("foo");
  218. binder.writeBean(person);
  219. assertEquals(fieldValue, person.getFirstName());
  220. }
  221. @Test
  222. public void save_bound_beanAsDraft() {
  223. Binder<Person> binder = new Binder<>();
  224. binder.forField(nameField)
  225. .withValidator((value,context) -> {
  226. if (value.equals("Mike")) {
  227. return ValidationResult.ok();
  228. } else {
  229. return ValidationResult.error("value must be Mike");
  230. }
  231. })
  232. .bind(Person::getFirstName, Person::setFirstName);
  233. binder.forField(ageField)
  234. .withConverter(new StringToIntegerConverter(""))
  235. .bind(Person::getAge, Person::setAge);
  236. Person person = new Person();
  237. String fieldValue = "John";
  238. nameField.setValue(fieldValue);
  239. int age = 10;
  240. ageField.setValue("10");
  241. person.setFirstName("Mark");
  242. binder.writeBeanAsDraft(person);
  243. // name is not written to draft as validation / conversion
  244. // does not pass
  245. assertNotEquals(fieldValue, person.getFirstName());
  246. // age is written to draft even if firstname validation
  247. // fails
  248. assertEquals(age, person.getAge());
  249. binder.writeBeanAsDraft(person,true);
  250. // name is now written despite validation as write was forced
  251. assertEquals(fieldValue, person.getFirstName());
  252. }
  253. @Test
  254. public void save_bound_bean_disable_validation_binding() throws ValidationException {
  255. Binder<Person> binder = new Binder<>();
  256. Binding<Person, String> nameBinding = binder.forField(nameField)
  257. .withValidator((value,context) -> {
  258. if (value.equals("Mike")) {
  259. return ValidationResult.ok();
  260. } else {
  261. return ValidationResult.error("value must be Mike");
  262. }
  263. })
  264. .bind(Person::getFirstName, Person::setFirstName);
  265. binder.forField(ageField)
  266. .withConverter(new StringToIntegerConverter(""))
  267. .bind(Person::getAge, Person::setAge);
  268. Person person = new Person();
  269. String fieldValue = "John";
  270. nameField.setValue(fieldValue);
  271. int age = 10;
  272. ageField.setValue("10");
  273. person.setFirstName("Mark");
  274. nameBinding.setValidatorsDisabled(true);
  275. binder.writeBean(person);
  276. // name is now written as validation was disabled
  277. assertEquals(fieldValue, person.getFirstName());
  278. assertEquals(age, person.getAge());
  279. }
  280. @Test
  281. public void save_bound_bean_disable_validation_binder() throws ValidationException {
  282. Binder<Person> binder = new Binder<>();
  283. binder.forField(nameField)
  284. .withValidator((value,context) -> {
  285. if (value.equals("Mike")) {
  286. return ValidationResult.ok();
  287. } else {
  288. return ValidationResult.error("value must be Mike");
  289. }
  290. })
  291. .bind(Person::getFirstName, Person::setFirstName);
  292. binder.forField(ageField)
  293. .withConverter(new StringToIntegerConverter(""))
  294. .bind(Person::getAge, Person::setAge);
  295. Person person = new Person();
  296. String fieldValue = "John";
  297. nameField.setValue(fieldValue);
  298. int age = 10;
  299. ageField.setValue("10");
  300. person.setFirstName("Mark");
  301. binder.setValidatorsDisabled(true);
  302. binder.writeBean(person);
  303. // name is now written as validation was disabled
  304. assertEquals(fieldValue, person.getFirstName());
  305. assertEquals(age, person.getAge());
  306. }
  307. @Test
  308. public void load_bound_fieldValueIsUpdated() {
  309. binder.bind(nameField, Person::getFirstName, Person::setFirstName);
  310. Person person = new Person();
  311. String name = "bar";
  312. person.setFirstName(name);
  313. binder.readBean(person);
  314. assertEquals(name, nameField.getValue());
  315. }
  316. @Test
  317. public void load_unbound_noChanges() {
  318. nameField.setValue("");
  319. Person person = new Person();
  320. String name = "bar";
  321. person.setFirstName(name);
  322. binder.readBean(person);
  323. assertEquals("", nameField.getValue());
  324. }
  325. protected void bindName() {
  326. binder.bind(nameField, Person::getFirstName, Person::setFirstName);
  327. binder.setBean(item);
  328. }
  329. @Test
  330. public void binding_with_null_representation() {
  331. String nullRepresentation = "Some arbitrary text";
  332. String realName = "John";
  333. Person namelessPerson = new Person(null, "Doe", "", 25, Sex.UNKNOWN,
  334. null);
  335. binder.forField(nameField).withNullRepresentation(nullRepresentation)
  336. .bind(Person::getFirstName, Person::setFirstName);
  337. // Bind a person with null value and check that null representation is
  338. // used
  339. binder.setBean(namelessPerson);
  340. assertEquals(
  341. "Null value from bean was not converted to explicit null representation",
  342. nullRepresentation, nameField.getValue());
  343. // Verify that changes are applied to bean
  344. nameField.setValue(realName);
  345. assertEquals(
  346. "Bean was not correctly updated from a change in the field",
  347. realName, namelessPerson.getFirstName());
  348. // Verify conversion back to null
  349. nameField.setValue(nullRepresentation);
  350. assertEquals(
  351. "Two-way null representation did not change value back to null",
  352. null, namelessPerson.getFirstName());
  353. }
  354. @Test
  355. public void binding_with_default_null_representation() {
  356. TextField nullTextField = new TextField() {
  357. @Override
  358. public String getEmptyValue() {
  359. return "null";
  360. }
  361. };
  362. Person namelessPerson = new Person(null, "Doe", "", 25, Sex.UNKNOWN,
  363. null);
  364. binder.bind(nullTextField, Person::getFirstName, Person::setFirstName);
  365. binder.setBean(namelessPerson);
  366. assertTrue(nullTextField.isEmpty());
  367. assertEquals("null", namelessPerson.getFirstName());
  368. // Change value, see that textfield is not empty and bean is updated.
  369. nullTextField.setValue("");
  370. assertFalse(nullTextField.isEmpty());
  371. assertEquals("First name of person was not properly updated", "",
  372. namelessPerson.getFirstName());
  373. // Verify that default null representation does not map back to null
  374. nullTextField.setValue("null");
  375. assertTrue(nullTextField.isEmpty());
  376. assertEquals("Default one-way null representation failed.", "null",
  377. namelessPerson.getFirstName());
  378. }
  379. @Test
  380. public void binding_with_null_representation_value_not_null() {
  381. String nullRepresentation = "Some arbitrary text";
  382. binder.forField(nameField).withNullRepresentation(nullRepresentation)
  383. .bind(Person::getFirstName, Person::setFirstName);
  384. assertFalse("First name in item should not be null",
  385. Objects.isNull(item.getFirstName()));
  386. binder.setBean(item);
  387. assertEquals("Field value was not set correctly", item.getFirstName(),
  388. nameField.getValue());
  389. }
  390. @Test
  391. public void withConverter_disablesDefaulNullRepresentation() {
  392. Integer customNullConverter = 0;
  393. binder.forField(ageField).withNullRepresentation("foo")
  394. .withConverter(new StringToIntegerConverter(""))
  395. .withConverter(age -> age,
  396. age -> age == null ? customNullConverter : age)
  397. .bind(Person::getSalary, Person::setSalary);
  398. binder.setBean(item);
  399. assertEquals(customNullConverter.toString(), ageField.getValue());
  400. Integer salary = 11;
  401. ageField.setValue(salary.toString());
  402. assertEquals(11, salary.intValue());
  403. }
  404. @Test
  405. public void withConverter_writeBackValue() {
  406. TextField rentField = new TextField();
  407. rentField.setValue("");
  408. binder.forField(rentField).withConverter(new EuroConverter(""))
  409. .withNullRepresentation(BigDecimal.valueOf(0d))
  410. .bind(Person::getRent, Person::setRent);
  411. binder.setBean(item);
  412. rentField.setValue("10");
  413. assertEquals("€ 10.00", rentField.getValue());
  414. }
  415. @Test
  416. public void withConverter_writeBackValueDisabled() {
  417. TextField rentField = new TextField();
  418. rentField.setValue("");
  419. Binding<Person, BigDecimal> binding = binder.forField(rentField)
  420. .withConverter(new EuroConverter(""))
  421. .withNullRepresentation(BigDecimal.valueOf(0d))
  422. .bind(Person::getRent, Person::setRent);
  423. binder.setBean(item);
  424. binding.setConvertBackToPresentation(false);
  425. rentField.setValue("10");
  426. assertNotEquals("€ 10.00", rentField.getValue());
  427. }
  428. @Test
  429. public void beanBinder_nullRepresentationIsNotDisabled() {
  430. Binder<Person> binder = new Binder<>(Person.class);
  431. binder.forField(nameField).bind("firstName");
  432. Person person = new Person();
  433. binder.setBean(person);
  434. assertEquals("", nameField.getValue());
  435. }
  436. @Test
  437. public void beanBinder_withConverter_nullRepresentationIsNotDisabled() {
  438. String customNullPointerRepresentation = "foo";
  439. Binder<Person> binder = new Binder<>(Person.class);
  440. binder.forField(nameField)
  441. .withConverter(value -> value,
  442. value -> value == null ? customNullPointerRepresentation
  443. : value)
  444. .bind("firstName");
  445. Person person = new Person();
  446. binder.setBean(person);
  447. assertEquals(customNullPointerRepresentation, nameField.getValue());
  448. }
  449. @Test
  450. public void withValidator_doesNotDisablesDefaulNullRepresentation() {
  451. String nullRepresentation = "foo";
  452. binder.forField(nameField).withNullRepresentation(nullRepresentation)
  453. .withValidator(new NotEmptyValidator<>(""))
  454. .bind(Person::getFirstName, Person::setFirstName);
  455. item.setFirstName(null);
  456. binder.setBean(item);
  457. assertEquals(nullRepresentation, nameField.getValue());
  458. String newValue = "bar";
  459. nameField.setValue(newValue);
  460. assertEquals(newValue, item.getFirstName());
  461. }
  462. @Test
  463. public void setRequired_withErrorMessage_fieldGetsRequiredIndicatorAndValidator() {
  464. TextField textField = new TextField();
  465. assertFalse(textField.isRequiredIndicatorVisible());
  466. BindingBuilder<Person, String> bindingBuilder = binder.forField(textField);
  467. assertFalse(textField.isRequiredIndicatorVisible());
  468. bindingBuilder.asRequired("foobar");
  469. assertTrue(textField.isRequiredIndicatorVisible());
  470. Binding<Person, String> binding = bindingBuilder.bind(Person::getFirstName, Person::setFirstName);
  471. binder.setBean(item);
  472. assertNull(textField.getErrorMessage());
  473. textField.setValue(textField.getEmptyValue());
  474. ErrorMessage errorMessage = textField.getErrorMessage();
  475. assertNotNull(errorMessage);
  476. assertEquals("foobar", errorMessage.getFormattedHtmlMessage());
  477. textField.setValue("value");
  478. assertNull(textField.getErrorMessage());
  479. assertTrue(textField.isRequiredIndicatorVisible());
  480. binding.setAsRequiredEnabled(false);
  481. assertFalse(textField.isRequiredIndicatorVisible());
  482. }
  483. @Test
  484. public void readNullBeanRemovesError() {
  485. TextField textField = new TextField();
  486. binder.forField(textField).asRequired("foobar")
  487. .bind(Person::getFirstName, Person::setFirstName);
  488. assertTrue(textField.isRequiredIndicatorVisible());
  489. assertNull(textField.getErrorMessage());
  490. binder.readBean(item);
  491. assertNull(textField.getErrorMessage());
  492. textField.setValue(textField.getEmptyValue());
  493. assertTrue(textField.isRequiredIndicatorVisible());
  494. assertNotNull(textField.getErrorMessage());
  495. binder.readBean(null);
  496. assertTrue(textField.isRequiredIndicatorVisible());
  497. assertNull(textField.getErrorMessage());
  498. }
  499. @Test
  500. public void setRequired_withErrorMessageProvider_fieldGetsRequiredIndicatorAndValidator() {
  501. TextField textField = new TextField();
  502. textField.setLocale(Locale.CANADA);
  503. assertFalse(textField.isRequiredIndicatorVisible());
  504. BindingBuilder<Person, String> binding = binder.forField(textField);
  505. assertFalse(textField.isRequiredIndicatorVisible());
  506. AtomicInteger invokes = new AtomicInteger();
  507. binding.asRequired(context -> {
  508. invokes.incrementAndGet();
  509. assertSame(Locale.CANADA, context.getLocale().get());
  510. return "foobar";
  511. });
  512. assertTrue(textField.isRequiredIndicatorVisible());
  513. binding.bind(Person::getFirstName, Person::setFirstName);
  514. binder.setBean(item);
  515. assertNull(textField.getErrorMessage());
  516. assertEquals(0, invokes.get());
  517. textField.setValue(textField.getEmptyValue());
  518. ErrorMessage errorMessage = textField.getErrorMessage();
  519. assertNotNull(errorMessage);
  520. assertEquals("foobar", errorMessage.getFormattedHtmlMessage());
  521. // validation is done for all changed bindings once.
  522. assertEquals(1, invokes.get());
  523. textField.setValue("value");
  524. assertNull(textField.getErrorMessage());
  525. assertTrue(textField.isRequiredIndicatorVisible());
  526. }
  527. @Test
  528. public void setRequired_withCustomValidator_fieldGetsRequiredIndicatorAndValidator() {
  529. TextField textField = new TextField();
  530. textField.setLocale(Locale.CANADA);
  531. assertFalse(textField.isRequiredIndicatorVisible());
  532. BindingBuilder<Person, String> binding = binder.forField(textField);
  533. assertFalse(textField.isRequiredIndicatorVisible());
  534. AtomicInteger invokes = new AtomicInteger();
  535. Validator<String> customRequiredValidator = (value, context) -> {
  536. invokes.incrementAndGet();
  537. if (StringUtils.isBlank(value)) {
  538. return ValidationResult.error("Input is required.");
  539. }
  540. return ValidationResult.ok();
  541. };
  542. binding.asRequired(customRequiredValidator);
  543. assertTrue(textField.isRequiredIndicatorVisible());
  544. binding.bind(Person::getFirstName, Person::setFirstName);
  545. binder.setBean(item);
  546. assertNull(textField.getErrorMessage());
  547. assertEquals(1, invokes.get());
  548. textField.setValue(" ");
  549. ErrorMessage errorMessage = textField.getErrorMessage();
  550. assertNotNull(errorMessage);
  551. assertEquals("Input&#32;is&#32;required&#46;",
  552. errorMessage.getFormattedHtmlMessage());
  553. // validation is done for all changed bindings once.
  554. assertEquals(2, invokes.get());
  555. textField.setValue("value");
  556. assertNull(textField.getErrorMessage());
  557. assertTrue(textField.isRequiredIndicatorVisible());
  558. }
  559. @Test
  560. public void setRequired_withCustomValidator_modelConverterBeforeValidator() {
  561. TextField textField = new TextField();
  562. textField.setLocale(Locale.CANADA);
  563. assertFalse(textField.isRequiredIndicatorVisible());
  564. Converter<String, String> stringBasicPreProcessingConverter = new Converter<String, String>() {
  565. @Override
  566. public Result<String> convertToModel(String value,
  567. ValueContext context) {
  568. if (StringUtils.isBlank(value)) {
  569. return Result.ok(null);
  570. }
  571. return Result.ok(StringUtils.trim(value));
  572. }
  573. @Override
  574. public String convertToPresentation(String value,
  575. ValueContext context) {
  576. if (value == null) {
  577. return "";
  578. }
  579. return value;
  580. }
  581. };
  582. AtomicInteger invokes = new AtomicInteger();
  583. Validator<String> customRequiredValidator = (value, context) -> {
  584. invokes.incrementAndGet();
  585. if (value == null) {
  586. return ValidationResult.error("Input required.");
  587. }
  588. return ValidationResult.ok();
  589. };
  590. binder.forField(textField)
  591. .withConverter(stringBasicPreProcessingConverter)
  592. .asRequired(customRequiredValidator)
  593. .bind(Person::getFirstName, Person::setFirstName);
  594. binder.setBean(item);
  595. assertNull(textField.getErrorMessage());
  596. assertEquals(1, invokes.get());
  597. textField.setValue(" ");
  598. ErrorMessage errorMessage = textField.getErrorMessage();
  599. assertNotNull(errorMessage);
  600. assertEquals("Input&#32;required&#46;",
  601. errorMessage.getFormattedHtmlMessage());
  602. // validation is done for all changed bindings once.
  603. assertEquals(2, invokes.get());
  604. textField.setValue("value");
  605. assertNull(textField.getErrorMessage());
  606. assertTrue(textField.isRequiredIndicatorVisible());
  607. }
  608. @Test
  609. public void validationStatusHandler_onlyRunForChangedField() {
  610. TextField firstNameField = new TextField();
  611. TextField lastNameField = new TextField();
  612. AtomicInteger invokes = new AtomicInteger();
  613. binder.forField(firstNameField)
  614. .withValidator(new NotEmptyValidator<>(""))
  615. .withValidationStatusHandler(
  616. validationStatus -> invokes.addAndGet(1))
  617. .bind(Person::getFirstName, Person::setFirstName);
  618. binder.forField(lastNameField)
  619. .withValidator(new NotEmptyValidator<>(""))
  620. .bind(Person::getLastName, Person::setLastName);
  621. binder.setBean(item);
  622. // setting the bean causes 2:
  623. assertEquals(2, invokes.get());
  624. lastNameField.setValue("");
  625. assertEquals(2, invokes.get());
  626. firstNameField.setValue("");
  627. assertEquals(3, invokes.get());
  628. binder.removeBean();
  629. Person person = new Person();
  630. person.setFirstName("a");
  631. person.setLastName("a");
  632. binder.readBean(person);
  633. // reading from a bean causes 2:
  634. assertEquals(5, invokes.get());
  635. lastNameField.setValue("");
  636. assertEquals(5, invokes.get());
  637. firstNameField.setValue("");
  638. assertEquals(6, invokes.get());
  639. }
  640. @Test(expected = IllegalStateException.class)
  641. public void noArgsConstructor_stringBind_throws() {
  642. binder.bind(new TextField(), "firstName");
  643. }
  644. @Test
  645. public void setReadOnly_unboundBinder() {
  646. binder.forField(nameField).bind(Person::getFirstName,
  647. Person::setFirstName);
  648. binder.forField(ageField);
  649. binder.setReadOnly(true);
  650. assertTrue(nameField.isReadOnly());
  651. assertFalse(ageField.isReadOnly());
  652. binder.setReadOnly(false);
  653. assertFalse(nameField.isReadOnly());
  654. assertFalse(ageField.isReadOnly());
  655. }
  656. @Test
  657. public void setReadOnly_boundBinder() {
  658. binder.forField(nameField).bind(Person::getFirstName,
  659. Person::setFirstName);
  660. binder.forField(ageField)
  661. .withConverter(new StringToIntegerConverter(""))
  662. .bind(Person::getAge, Person::setAge);
  663. binder.setBean(new Person());
  664. binder.setReadOnly(true);
  665. assertTrue(nameField.isReadOnly());
  666. assertTrue(ageField.isReadOnly());
  667. binder.setReadOnly(false);
  668. assertFalse(nameField.isReadOnly());
  669. assertFalse(ageField.isReadOnly());
  670. }
  671. @Test
  672. public void setReadOnly_binderLoadedByReadBean() {
  673. binder.forField(nameField).bind(Person::getFirstName,
  674. Person::setFirstName);
  675. binder.forField(ageField)
  676. .withConverter(new StringToIntegerConverter(""))
  677. .bind(Person::getAge, Person::setAge);
  678. binder.readBean(new Person());
  679. binder.setReadOnly(true);
  680. assertTrue(nameField.isReadOnly());
  681. assertTrue(ageField.isReadOnly());
  682. binder.setReadOnly(false);
  683. assertFalse(nameField.isReadOnly());
  684. assertFalse(ageField.isReadOnly());
  685. }
  686. @Test
  687. public void setReadonlyShouldIgnoreBindingsWithNullSetter() {
  688. binder.bind(nameField, Person::getFirstName, null);
  689. binder.forField(ageField)
  690. .withConverter(new StringToIntegerConverter(""))
  691. .bind(Person::getAge, Person::setAge);
  692. binder.setReadOnly(true);
  693. assertTrue("Name field should be ignored but should be readonly",
  694. nameField.isReadOnly());
  695. assertTrue("Age field should be readonly", ageField.isReadOnly());
  696. binder.setReadOnly(false);
  697. assertTrue("Name field should be ignored and should remain readonly",
  698. nameField.isReadOnly());
  699. assertFalse("Age field should not be readonly", ageField.isReadOnly());
  700. nameField.setReadOnly(false);
  701. binder.setReadOnly(false);
  702. assertFalse("Name field should be ignored and remain not readonly",
  703. nameField.isReadOnly());
  704. assertFalse("Age field should not be readonly", ageField.isReadOnly());
  705. binder.setReadOnly(true);
  706. assertFalse("Name field should be ignored and remain not readonly",
  707. nameField.isReadOnly());
  708. assertTrue("Age field should be readonly", ageField.isReadOnly());
  709. }
  710. @Test
  711. public void isValidTest_bound_binder() {
  712. binder.forField(nameField)
  713. .withValidator(Validator.from(
  714. name -> !name.equals("fail field validation"), ""))
  715. .bind(Person::getFirstName, Person::setFirstName);
  716. binder.withValidator(Validator.from(
  717. person -> !person.getFirstName().equals("fail bean validation"),
  718. ""));
  719. binder.setBean(item);
  720. assertTrue(binder.isValid());
  721. nameField.setValue("fail field validation");
  722. assertFalse(binder.isValid());
  723. nameField.setValue("");
  724. assertTrue(binder.isValid());
  725. nameField.setValue("fail bean validation");
  726. assertFalse(binder.isValid());
  727. }
  728. @Test
  729. public void isValidTest_unbound_binder() {
  730. binder.forField(nameField)
  731. .withValidator(Validator.from(
  732. name -> !name.equals("fail field validation"), ""))
  733. .bind(Person::getFirstName, Person::setFirstName);
  734. assertTrue(binder.isValid());
  735. nameField.setValue("fail field validation");
  736. assertFalse(binder.isValid());
  737. nameField.setValue("");
  738. assertTrue(binder.isValid());
  739. }
  740. @Test(expected = IllegalStateException.class)
  741. public void isValidTest_unbound_binder_throws_with_bean_level_validation() {
  742. binder.forField(nameField).bind(Person::getFirstName,
  743. Person::setFirstName);
  744. binder.withValidator(Validator.from(
  745. person -> !person.getFirstName().equals("fail bean validation"),
  746. ""));
  747. binder.isValid();
  748. }
  749. @Test
  750. public void getFields_returnsFields() {
  751. assertEquals(0, binder.getFields().count());
  752. binder.forField(nameField).bind(Person::getFirstName,
  753. Person::setFirstName);
  754. assertStreamEquals(Stream.of(nameField), binder.getFields());
  755. binder.forField(ageField)
  756. .withConverter(new StringToIntegerConverter(""))
  757. .bind(Person::getAge, Person::setAge);
  758. assertStreamEquals(Stream.of(nameField, ageField), binder.getFields());
  759. }
  760. private void assertStreamEquals(Stream<?> s1, Stream<?> s2) {
  761. assertArrayEquals(s1.toArray(), s2.toArray());
  762. }
  763. @Test
  764. public void multiple_calls_to_same_binding_builder() {
  765. String stringLength = "String length failure";
  766. String conversion = "Conversion failed";
  767. String ageLimit = "Age not in valid range";
  768. BindingValidationStatus validation;
  769. binder = new Binder<>(Person.class);
  770. BindingBuilder builder = binder.forField(ageField);
  771. builder.withValidator(new StringLengthValidator(stringLength, 0, 3));
  772. builder.withConverter(new StringToIntegerConverter(conversion));
  773. builder.withValidator(new IntegerRangeValidator(ageLimit, 3, 150));
  774. Binding<Person, ?> bind = builder.bind("age");
  775. binder.setBean(item);
  776. ageField.setValue("123123");
  777. validation = bind.validate();
  778. assertTrue(validation.isError());
  779. assertEquals(stringLength, validation.getMessage().get());
  780. ageField.setValue("age");
  781. validation = bind.validate();
  782. assertTrue(validation.isError());
  783. assertEquals(conversion, validation.getMessage().get());
  784. ageField.setValue("256");
  785. validation = bind.validate();
  786. assertTrue(validation.isError());
  787. assertEquals(ageLimit, validation.getMessage().get());
  788. ageField.setValue("30");
  789. validation = bind.validate();
  790. assertFalse(validation.isError());
  791. assertEquals(30, item.getAge());
  792. }
  793. @Test
  794. public void remove_field_binding() {
  795. binder.forField(ageField)
  796. .withConverter(new StringToIntegerConverter("Can't convert"))
  797. .bind(Person::getAge, Person::setAge);
  798. // Test that the binding does work
  799. assertTrue("Field not initially empty", ageField.isEmpty());
  800. binder.setBean(item);
  801. assertEquals("Binding did not work", String.valueOf(item.getAge()),
  802. ageField.getValue());
  803. binder.setBean(null);
  804. assertTrue("Field not cleared", ageField.isEmpty());
  805. // Remove the binding
  806. binder.removeBinding(ageField);
  807. // Test that it does not work anymore
  808. binder.setBean(item);
  809. assertNotEquals("Binding was not removed",
  810. String.valueOf(item.getAge()), ageField.getValue());
  811. }
  812. @Test
  813. public void remove_propertyname_binding() {
  814. // Use a bean aware binder
  815. Binder<Person> binder = new Binder<>(Person.class);
  816. binder.bind(nameField, "firstName");
  817. // Test that the binding does work
  818. assertTrue("Field not initially empty", nameField.isEmpty());
  819. binder.setBean(item);
  820. assertEquals("Binding did not work", item.getFirstName(),
  821. nameField.getValue());
  822. binder.setBean(null);
  823. assertTrue("Field not cleared", nameField.isEmpty());
  824. // Remove the binding
  825. binder.removeBinding("firstName");
  826. // Test that it does not work anymore
  827. binder.setBean(item);
  828. assertNotEquals("Binding was not removed", item.getFirstName(),
  829. nameField.getValue());
  830. }
  831. @Test
  832. public void remove_binding() {
  833. Binding<Person, Integer> binding = binder.forField(ageField)
  834. .withConverter(new StringToIntegerConverter("Can't convert"))
  835. .bind(Person::getAge, Person::setAge);
  836. // Test that the binding does work
  837. assertTrue("Field not initially empty", ageField.isEmpty());
  838. binder.setBean(item);
  839. assertEquals("Binding did not work", String.valueOf(item.getAge()),
  840. ageField.getValue());
  841. binder.setBean(null);
  842. assertTrue("Field not cleared", ageField.isEmpty());
  843. // Remove the binding
  844. binder.removeBinding(binding);
  845. // Test that it does not work anymore
  846. binder.setBean(item);
  847. assertNotEquals("Binding was not removed",
  848. String.valueOf(item.getAge()), ageField.getValue());
  849. }
  850. @Test
  851. public void remove_binding_fromFieldValueChangeListener() {
  852. // Add listener before bind to make sure it will be executed first.
  853. nameField.addValueChangeListener(e -> {
  854. if (e.getValue() == "REMOVE") {
  855. binder.removeBinding(nameField);
  856. }
  857. });
  858. binder.bind(nameField, Person::getFirstName, Person::setFirstName);
  859. binder.setBean(item);
  860. nameField.setValue("REMOVE");
  861. // Removed binding should not update bean.
  862. assertNotEquals("REMOVE", item.getFirstName());
  863. }
  864. @Test
  865. public void beanvalidation_two_fields_not_equal() {
  866. TextField lastNameField = new TextField();
  867. setBeanValidationFirstNameNotEqualsLastName(nameField, lastNameField);
  868. item.setLastName("Valid");
  869. binder.setBean(item);
  870. assertFalse("Should not have changes initially", binder.hasChanges());
  871. assertTrue("Should be ok initially", binder.validate().isOk());
  872. assertNotEquals("First name and last name are not same initially",
  873. item.getFirstName(), item.getLastName());
  874. nameField.setValue("Invalid");
  875. assertFalse("First name change not handled", binder.hasChanges());
  876. assertTrue(
  877. "Changing first name to something else than last name should be ok",
  878. binder.validate().isOk());
  879. lastNameField.setValue("Invalid");
  880. assertTrue("Last name should not be saved yet", binder.hasChanges());
  881. assertFalse("Binder validation should fail with pending illegal value",
  882. binder.validate().isOk());
  883. assertNotEquals("Illegal last name should not be stored to bean",
  884. item.getFirstName(), item.getLastName());
  885. nameField.setValue("Valid");
  886. assertFalse("With new first name both changes should be saved",
  887. binder.hasChanges());
  888. assertTrue("Everything should be ok for 'Valid Invalid'",
  889. binder.validate().isOk());
  890. assertNotEquals("First name and last name should never match.",
  891. item.getFirstName(), item.getLastName());
  892. }
  893. @Test
  894. public void beanvalidation_initially_broken_bean() {
  895. TextField lastNameField = new TextField();
  896. setBeanValidationFirstNameNotEqualsLastName(nameField, lastNameField);
  897. item.setLastName(item.getFirstName());
  898. binder.setBean(item);
  899. assertFalse(binder.isValid());
  900. assertFalse(binder.validate().isOk());
  901. }
  902. @Test(expected = IllegalStateException.class)
  903. public void beanvalidation_isValid_throws_with_readBean() {
  904. TextField lastNameField = new TextField();
  905. setBeanValidationFirstNameNotEqualsLastName(nameField, lastNameField);
  906. binder.readBean(item);
  907. assertTrue(binder.isValid());
  908. }
  909. @Test(expected = IllegalStateException.class)
  910. public void beanvalidation_validate_throws_with_readBean() {
  911. TextField lastNameField = new TextField();
  912. setBeanValidationFirstNameNotEqualsLastName(nameField, lastNameField);
  913. binder.readBean(item);
  914. assertTrue(binder.validate().isOk());
  915. }
  916. protected void setBeanValidationFirstNameNotEqualsLastName(
  917. TextField firstNameField, TextField lastNameField) {
  918. binder.bind(firstNameField, Person::getFirstName, Person::setFirstName);
  919. binder.forField(lastNameField)
  920. .withValidator(t -> !"foo".equals(t),
  921. "Last name cannot be 'foo'")
  922. .bind(Person::getLastName, Person::setLastName);
  923. binder.withValidator(p -> !p.getFirstName().equals(p.getLastName()),
  924. "First name and last name can't be the same");
  925. }
  926. static class MyBindingHandler implements BindingValidationStatusHandler {
  927. boolean expectingError = false;
  928. int callCount = 0;
  929. @Override
  930. public void statusChange(BindingValidationStatus<?> statusChange) {
  931. ++callCount;
  932. if (expectingError) {
  933. assertTrue("Expecting error", statusChange.isError());
  934. } else {
  935. assertFalse("Unexpected error", statusChange.isError());
  936. }
  937. }
  938. }
  939. @Test
  940. public void execute_binding_status_handler_from_binder_status_handler() {
  941. MyBindingHandler bindingHandler = new MyBindingHandler();
  942. binder.forField(nameField)
  943. .withValidator(t -> !t.isEmpty(), "No empty values.")
  944. .withValidationStatusHandler(bindingHandler)
  945. .bind(Person::getFirstName, Person::setFirstName);
  946. String ageError = "CONVERSIONERROR";
  947. binder.forField(ageField)
  948. .withConverter(new StringToIntegerConverter(ageError))
  949. .bind(Person::getAge, Person::setAge);
  950. binder.setValidationStatusHandler(
  951. status -> status.notifyBindingValidationStatusHandlers());
  952. String initialName = item.getFirstName();
  953. int initialAge = item.getAge();
  954. binder.setBean(item);
  955. // Test specific error handling.
  956. bindingHandler.expectingError = true;
  957. nameField.setValue("");
  958. // Test default error handling.
  959. ageField.setValue("foo");
  960. assertTrue("Component error does not contain error message",
  961. ageField.getComponentError().getFormattedHtmlMessage()
  962. .contains(ageError));
  963. // Restore values and test no errors.
  964. ageField.setValue(String.valueOf(initialAge));
  965. assertNull("There should be no component error",
  966. ageField.getComponentError());
  967. bindingHandler.expectingError = false;
  968. nameField.setValue(initialName);
  969. // Assert that the handler was called.
  970. assertEquals(
  971. "Unexpected callCount to binding validation status handler", 6,
  972. bindingHandler.callCount);
  973. }
  974. @Test
  975. public void removed_binding_not_updates_value() {
  976. Binding<Person, Integer> binding = binder.forField(ageField)
  977. .withConverter(new StringToIntegerConverter("Can't convert"))
  978. .bind(Person::getAge, Person::setAge);
  979. binder.setBean(item);
  980. String modifiedAge = String.valueOf(item.getAge() + 10);
  981. String ageBeforeUnbind = String.valueOf(item.getAge());
  982. binder.removeBinding(binding);
  983. ageField.setValue(modifiedAge);
  984. assertEquals("Binding still affects bean even after unbind",
  985. ageBeforeUnbind, String.valueOf(item.getAge()));
  986. }
  987. @Test
  988. public void info_validator_not_considered_error() {
  989. String infoMessage = "Young";
  990. binder.forField(ageField)
  991. .withConverter(new StringToIntegerConverter("Can't convert"))
  992. .withValidator(i -> i > 5, infoMessage, ErrorLevel.INFO)
  993. .bind(Person::getAge, Person::setAge);
  994. binder.setBean(item);
  995. ageField.setValue("3");
  996. assertEquals(infoMessage,
  997. ageField.getComponentError().getFormattedHtmlMessage());
  998. assertEquals(ErrorLevel.INFO,
  999. ageField.getComponentError().getErrorLevel());
  1000. assertEquals(3, item.getAge());
  1001. }
  1002. @Test
  1003. public void two_asRequired_fields_without_initial_values() {
  1004. binder.forField(nameField).asRequired("Empty name").bind(p -> "",
  1005. (p, s) -> {
  1006. });
  1007. binder.forField(ageField).asRequired("Empty age").bind(p -> "",
  1008. (p, s) -> {
  1009. });
  1010. binder.setBean(item);
  1011. assertNull("Initially there should be no errors",
  1012. nameField.getComponentError());
  1013. assertNull("Initially there should be no errors",
  1014. ageField.getComponentError());
  1015. nameField.setValue("Foo");
  1016. assertNull("Name with a value should not be an error",
  1017. nameField.getComponentError());
  1018. assertNull(
  1019. "Age field should not be in error, since it has not been modified.",
  1020. ageField.getComponentError());
  1021. nameField.setValue("");
  1022. assertNotNull("Empty name should now be in error.",
  1023. nameField.getComponentError());
  1024. assertNull("Age field should still be ok.",
  1025. ageField.getComponentError());
  1026. }
  1027. @Test
  1028. public void refreshValueFromBean() {
  1029. Binding<Person, String> binding = binder.bind(nameField,
  1030. Person::getFirstName, Person::setFirstName);
  1031. binder.readBean(item);
  1032. assertEquals("Name should be read from the item", item.getFirstName(),
  1033. nameField.getValue());
  1034. nameField.setValue("foo");
  1035. assertNotEquals("Name should be different from the item",
  1036. item.getFirstName(), nameField.getValue());
  1037. binding.read(item);
  1038. assertEquals("Name should be read again from the item",
  1039. item.getFirstName(), nameField.getValue());
  1040. }
  1041. @Test(expected = IllegalArgumentException.class)
  1042. public void remove_binding_from_different_binder() {
  1043. Binder<Person> anotherBinder = new Binder<>();
  1044. Binding<Person, String> binding = anotherBinder.bind(nameField,
  1045. Person::getFirstName, Person::setFirstName);
  1046. binder.removeBinding(binding);
  1047. }
  1048. @Test(expected = IllegalStateException.class)
  1049. public void bindWithNullSetterSetReadWrite() {
  1050. Binding<Person, String> binding = binder.bind(nameField,
  1051. Person::getFirstName, null);
  1052. binding.setReadOnly(false);
  1053. }
  1054. @Test
  1055. public void bindWithNullSetterShouldMarkFieldAsReadonly() {
  1056. Binding<Person, String> nameBinding = binder.bind(nameField,
  1057. Person::getFirstName, null);
  1058. binder.forField(ageField)
  1059. .withConverter(new StringToIntegerConverter(""))
  1060. .bind(Person::getAge, Person::setAge);
  1061. assertTrue("Name field should be readonly", nameField.isReadOnly());
  1062. assertFalse("Age field should not be readonly", ageField.isReadOnly());
  1063. assertTrue("Binding should be marked readonly",
  1064. nameBinding.isReadOnly());
  1065. }
  1066. @Test
  1067. public void setReadOnly_binding() {
  1068. Binding<Person, String> binding = binder.bind(nameField,
  1069. Person::getFirstName, Person::setFirstName);
  1070. assertFalse("Binding should not be readonly", binding.isReadOnly());
  1071. assertFalse("Name field should not be readonly",
  1072. nameField.isReadOnly());
  1073. binding.setReadOnly(true);
  1074. assertTrue("Binding should be readonly", binding.isReadOnly());
  1075. assertTrue("Name field should be readonly", nameField.isReadOnly());
  1076. }
  1077. @Test
  1078. public void conversionWithLocaleBasedErrorMessage() {
  1079. String fiError = "VIRHE";
  1080. String otherError = "ERROR";
  1081. binder.forField(ageField).withConverter(new StringToIntegerConverter(
  1082. context -> context.getLocale().map(Locale::getLanguage)
  1083. .orElse("en").equals("fi") ? fiError : otherError))
  1084. .bind(Person::getAge, Person::setAge);
  1085. binder.setBean(item);
  1086. ageField.setValue("not a number");
  1087. assertEquals(otherError,
  1088. ageField.getErrorMessage().getFormattedHtmlMessage());
  1089. ageField.setLocale(new Locale("fi"));
  1090. // Re-validate to get the error message with correct locale
  1091. binder.validate();
  1092. assertEquals(fiError,
  1093. ageField.getErrorMessage().getFormattedHtmlMessage());
  1094. }
  1095. @Test
  1096. public void valueChangeListenerOrder() {
  1097. AtomicBoolean beanSet = new AtomicBoolean();
  1098. nameField.addValueChangeListener(e -> {
  1099. if (!beanSet.get()) {
  1100. assertEquals("Value in bean updated earlier than expected",
  1101. e.getOldValue(), item.getFirstName());
  1102. }
  1103. });
  1104. binder.bind(nameField, Person::getFirstName, Person::setFirstName);
  1105. nameField.addValueChangeListener(e -> {
  1106. if (!beanSet.get()) {
  1107. assertEquals("Value in bean not updated when expected",
  1108. e.getValue(), item.getFirstName());
  1109. }
  1110. });
  1111. beanSet.set(true);
  1112. binder.setBean(item);
  1113. beanSet.set(false);
  1114. nameField.setValue("Foo");
  1115. }
  1116. @Test
  1117. public void nonSymetricValue_setBean_writtenToBean() {
  1118. binder.bind(nameField, Person::getLastName, Person::setLastName);
  1119. assertNull(item.getLastName());
  1120. binder.setBean(item);
  1121. assertEquals("", item.getLastName());
  1122. }
  1123. @Test
  1124. public void nonSymmetricValue_readBean_beanNotTouched() {
  1125. binder.bind(nameField, Person::getLastName, Person::setLastName);
  1126. binder.addValueChangeListener(
  1127. event -> fail("No value change event should be fired"));
  1128. assertNull(item.getLastName());
  1129. binder.readBean(item);
  1130. assertNull(item.getLastName());
  1131. }
  1132. @Test
  1133. public void symetricValue_setBean_beanNotUpdated() {
  1134. binder.bind(nameField, Person::getFirstName, Person::setFirstName);
  1135. binder.setBean(new Person() {
  1136. @Override
  1137. public String getFirstName() {
  1138. return "First";
  1139. }
  1140. @Override
  1141. public void setFirstName(String firstName) {
  1142. fail("Setter should not be called");
  1143. }
  1144. });
  1145. }
  1146. @Test
  1147. public void nullRejetingField_nullValue_wrappedExceptionMentionsNullRepresentation() {
  1148. TextField field = createNullAnd42RejectingFieldWithEmptyValue("");
  1149. Binder<AtomicReference<Integer>> binder = createIntegerConverterBinder(
  1150. field);
  1151. exceptionRule.expect(IllegalStateException.class);
  1152. exceptionRule.expectMessage("null representation");
  1153. exceptionRule.expectCause(CoreMatchers.isA(NullPointerException.class));
  1154. binder.readBean(new AtomicReference<>());
  1155. }
  1156. @Test
  1157. public void nullRejetingField_otherRejectedValue_originalExceptionIsThrown() {
  1158. TextField field = createNullAnd42RejectingFieldWithEmptyValue("");
  1159. Binder<AtomicReference<Integer>> binder = createIntegerConverterBinder(
  1160. field);
  1161. exceptionRule.expect(IllegalArgumentException.class);
  1162. exceptionRule.expectMessage("42");
  1163. binder.readBean(new AtomicReference<>(Integer.valueOf(42)));
  1164. }
  1165. @Test(expected = NullPointerException.class)
  1166. public void nullAcceptingField_nullValue_originalExceptionIsThrown() {
  1167. /*
  1168. * Edge case with a field that throws for null but has null as the empty
  1169. * value. This is most likely the case if the field doesn't explicitly
  1170. * reject null values but is instead somehow broken so that any value is
  1171. * rejected.
  1172. */
  1173. TextField field = createNullAnd42RejectingFieldWithEmptyValue(null);
  1174. Binder<AtomicReference<Integer>> binder = createIntegerConverterBinder(
  1175. field);
  1176. binder.readBean(new AtomicReference<>(null));
  1177. }
  1178. private TextField createNullAnd42RejectingFieldWithEmptyValue(
  1179. String emptyValue) {
  1180. return new TextField() {
  1181. @Override
  1182. public void setValue(String value) {
  1183. if (value == null) {
  1184. throw new NullPointerException("Null value");
  1185. } else if ("42".equals(value)) {
  1186. throw new IllegalArgumentException("42 is not allowed");
  1187. }
  1188. super.setValue(value);
  1189. }
  1190. @Override
  1191. public String getEmptyValue() {
  1192. return emptyValue;
  1193. }
  1194. };
  1195. }
  1196. private Binder<AtomicReference<Integer>> createIntegerConverterBinder(
  1197. TextField field) {
  1198. Binder<AtomicReference<Integer>> binder = new Binder<>();
  1199. binder.forField(field)
  1200. .withConverter(new StringToIntegerConverter("Must have number"))
  1201. .bind(AtomicReference::get, AtomicReference::set);
  1202. return binder;
  1203. }
  1204. /**
  1205. * A converter that adds/removes the euro sign and formats currencies with
  1206. * two decimal places.
  1207. */
  1208. public class EuroConverter extends StringToBigDecimalConverter {
  1209. public EuroConverter() {
  1210. super("defaultErrorMessage");
  1211. }
  1212. public EuroConverter(String errorMessage) {
  1213. super(errorMessage);
  1214. }
  1215. @Override
  1216. public Result<BigDecimal> convertToModel(String value,
  1217. ValueContext context) {
  1218. if (value.isEmpty()) {
  1219. return Result.ok(null);
  1220. }
  1221. value = value.replaceAll("[€\\s]", "").trim();
  1222. if (value.isEmpty()) {
  1223. value = "0";
  1224. }
  1225. return super.convertToModel(value, context);
  1226. }
  1227. @Override
  1228. public String convertToPresentation(BigDecimal value,
  1229. ValueContext context) {
  1230. if (value == null) {
  1231. return convertToPresentation(BigDecimal.ZERO, context);
  1232. }
  1233. return "€ " + super.convertToPresentation(value, context);
  1234. }
  1235. @Override
  1236. protected NumberFormat getFormat(Locale locale) {
  1237. // Always display currency with two decimals
  1238. NumberFormat format = super.getFormat(locale);
  1239. if (format instanceof DecimalFormat) {
  1240. ((DecimalFormat) format).setMaximumFractionDigits(2);
  1241. ((DecimalFormat) format).setMinimumFractionDigits(2);
  1242. }
  1243. return format;
  1244. }
  1245. }
  1246. }