1   /*
2    * Copyright 2002-2004 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package org.springframework.binding.form.support;
17  
18  import org.springframework.beans.NotReadablePropertyException;
19  import org.springframework.binding.convert.ConversionException;
20  import org.springframework.binding.convert.ConversionExecutor;
21  import org.springframework.binding.convert.ConversionService;
22  import org.springframework.binding.form.CommitListener;
23  import org.springframework.binding.form.FormModel;
24  import org.springframework.binding.support.BeanPropertyAccessStrategy;
25  import org.springframework.binding.support.TestBean;
26  import org.springframework.binding.support.TestPropertyChangeListener;
27  import org.springframework.binding.value.ValueModel;
28  import org.springframework.binding.value.support.ValueHolder;
29  import org.springframework.richclient.test.SpringRichTestCase;
30  
31  import java.beans.PropertyChangeEvent;
32  import java.beans.PropertyChangeListener;
33  
34  /**
35   * Tests for
36   * @link AbstractFormModel
37   * 
38   * @author Oliver Hutchison
39   */
40  public abstract class AbstractFormModelTests extends SpringRichTestCase {
41  
42  	protected AbstractFormModel getFormModel(Object formObject) {
43  		return new TestAbstractFormModel(formObject);
44  	}
45  
46  	protected AbstractFormModel getFormModel(BeanPropertyAccessStrategy pas, boolean buffering) {
47  		return new TestAbstractFormModel(pas, buffering);
48  	}
49  
50  	protected AbstractFormModel getFormModel(ValueModel valueModel, boolean buffering) {
51  		return new TestAbstractFormModel(valueModel, buffering);
52  	}
53  
54  	public void testGetValueModelFromPAS() {
55  		TestBean p = new TestBean();
56  		TestPropertyAccessStrategy tpas = new TestPropertyAccessStrategy(p);
57  		AbstractFormModel fm = getFormModel(tpas, true);
58  		ValueModel vm1 = fm.getValueModel("simpleProperty");
59  		assertEquals(1, tpas.numValueModelRequests());
60  		assertEquals("simpleProperty", tpas.lastRequestedValueModel());
61  		ValueModel vm2 = fm.getValueModel("simpleProperty");
62  		assertEquals(vm1, vm2);
63  		assertEquals(1, tpas.numValueModelRequests());
64  
65  		try {
66  			fm.getValueModel("iDontExist");
67  			fail("should't be able to get value model for invalid property");
68  		}
69  		catch (NotReadablePropertyException e) {
70  			// exprected
71  		}
72  	}
73  
74  	public void testUnbufferedWritesThrough() {
75  		TestBean p = new TestBean();
76  		BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
77  		AbstractFormModel fm = getFormModel(pas, false);
78  		ValueModel vm = fm.getValueModel("simpleProperty");
79  
80  		vm.setValue("1");
81  		assertEquals("1", p.getSimpleProperty());
82  
83  		vm.setValue(null);
84  		assertEquals(null, p.getSimpleProperty());
85  	}
86  
87  	public void testBufferedDoesNotWriteThrough() {
88  		TestBean p = new TestBean();
89  		BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
90  		AbstractFormModel fm = getFormModel(pas, true);
91  		ValueModel vm = fm.getValueModel("simpleProperty");
92  
93  		vm.setValue("1");
94  		assertEquals(null, p.getSimpleProperty());
95  
96  		vm.setValue(null);
97  		assertEquals(null, p.getSimpleProperty());
98  	}
99  
100 	public void testDirtyTrackingWithBuffering() {
101 		testDirtyTracking(true);
102 	}
103 
104 	public void testDirtyTrackingWithoutBuffering() {
105 		testDirtyTracking(false);
106 	}
107 
108 	public void testDirtyTracking(boolean buffering) {
109 		TestBean p = new TestBean();
110 		BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
111 		TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.DIRTY_PROPERTY);
112 		AbstractFormModel fm = getFormModel(pas, buffering);
113 		fm.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, pcl);
114 		ValueModel vm = fm.getValueModel("simpleProperty");
115 		assertTrue(!fm.isDirty());
116 
117 		vm.setValue("2");
118 		assertTrue(fm.isDirty());
119 		assertEquals(1, pcl.eventCount());
120 
121 		fm.commit();
122 		assertTrue(!fm.isDirty());
123 		assertEquals(2, pcl.eventCount());
124 
125 		vm.setValue("1");
126 		assertTrue(fm.isDirty());
127 		assertEquals(3, pcl.eventCount());
128 
129 		fm.setFormObject(new TestBean());
130 		assertTrue(!fm.isDirty());
131 		assertEquals(4, pcl.eventCount());
132 
133 		vm.setValue("2");
134 		assertTrue(fm.isDirty());
135 		assertEquals(5, pcl.eventCount());
136 
137 		fm.revert();
138 		assertTrue(!fm.isDirty());
139 		assertEquals(6, pcl.eventCount());
140 	}
141 
142 	/**
143 	 * Test on dirty state of parent-child relations. When child gets dirty,
144 	 * parent should also be dirty. When parent reverts, child should revert
145 	 * too.
146 	 */
147 	public void testDirtyTracksKids() {
148 		TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.DIRTY_PROPERTY);
149 		AbstractFormModel pfm = getFormModel(new TestBean());
150 		AbstractFormModel fm = getFormModel(new TestBean());
151 		pfm.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, pcl);
152 		pfm.addChild(fm);
153 		ValueModel childSimpleProperty = fm.getValueModel("simpleProperty");
154 		ValueModel parentSimpleProperty = pfm.getValueModel("simpleProperty");
155 		// test child property dirty -> parent dirty
156 		childSimpleProperty.setValue("1");
157 		assertTrue(pfm.isDirty());
158 		assertEquals(1, pcl.eventCount());
159 
160 		fm.revert();
161 		assertTrue(!pfm.isDirty());
162 		assertEquals(2, pcl.eventCount());
163 		// child dirty -> revert parent triggers revert on child
164 		childSimpleProperty.setValue("1");
165 		assertTrue(pfm.isDirty());
166 		assertEquals(3, pcl.eventCount());
167 
168 		pfm.revert();
169 		assertTrue(!pfm.isDirty());
170 		assertTrue(!fm.isDirty());
171 		assertEquals(4, pcl.eventCount());
172 		// child & parent property dirty -> parent dirty, revert child, then
173 		// parent
174 		childSimpleProperty.setValue("1");
175 		assertTrue(pfm.isDirty());
176 		assertEquals(5, pcl.eventCount());
177 
178 		parentSimpleProperty.setValue("2");
179 		assertTrue(pfm.isDirty());
180 		assertEquals(5, pcl.eventCount());
181 
182 		fm.revert();
183 		assertTrue(pfm.isDirty());
184 		assertEquals(5, pcl.eventCount());
185 
186 		pfm.revert();
187 		assertTrue(!pfm.isDirty());
188 		assertEquals(6, pcl.eventCount());
189 	}
190 
191 	public void testSetFormObjectDoesNotRevertChangesToPreviousFormObject() {
192 		TestBean p1 = new TestBean();
193 		BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p1);
194 		AbstractFormModel fm = getFormModel(pas, false);
195 		fm.getValueModel("simpleProperty").setValue("1");
196 		fm.setFormObject(new TestBean());
197 		assertEquals("1", p1.getSimpleProperty());
198 	}
199 
200 	public void testCommitEvents() {
201 		TestBean p = new TestBean();
202 		BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
203 		TestCommitListener cl = new TestCommitListener();
204 		AbstractFormModel fm = getFormModel(pas, false);
205 		fm.addCommitListener(cl);
206 		ValueModel vm = fm.getValueModel("simpleProperty");
207 
208 		vm.setValue("1");
209 		fm.commit();
210 		assertEquals(1, cl.preEditCalls);
211 		assertEquals(1, cl.postEditCalls);
212 	}
213 
214 	public void testCommitWritesBufferingThrough() {
215 		TestBean p = new TestBean();
216 		BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
217 		TestCommitListener cl = new TestCommitListener();
218 		AbstractFormModel fm = getFormModel(pas, true);
219 		fm.addCommitListener(cl);
220 		ValueModel vm = fm.getValueModel("simpleProperty");
221 
222 		vm.setValue("1");
223 		fm.commit();
224 		assertEquals("1", p.getSimpleProperty());
225 	}
226 
227 	public void testRevertWithBuffering() {
228 		testRevert(true);
229 	}
230 
231 	public void testRevertWithoutBuffering() {
232 		testRevert(false);
233 	}
234 
235 	public void testRevert(boolean buffering) {
236 		TestBean p = new TestBean();
237 		BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
238 		TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.DIRTY_PROPERTY);
239 		AbstractFormModel fm = getFormModel(pas, buffering);
240 		fm.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, pcl);
241 		ValueModel vm = fm.getValueModel("simpleProperty");
242 
243 		vm.setValue("1");
244 		fm.revert();
245 
246 		assertEquals(null, vm.getValue());
247 		assertEquals(null, p.getSimpleProperty());
248 
249 		TestBean tb2 = new TestBean();
250 		tb2.setSimpleProperty("tb2");
251 		fm.setFormObject(tb2);
252 
253 		vm.setValue("1");
254 		fm.revert();
255 
256 		assertEquals("tb2", vm.getValue());
257 		assertEquals("tb2", tb2.getSimpleProperty());
258 	}
259 
260 	public void testEnabledEvents() {
261 		TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.ENABLED_PROPERTY);
262 		AbstractFormModel fm = getFormModel(new Object());
263 		fm.addPropertyChangeListener(FormModel.ENABLED_PROPERTY, pcl);
264 
265 		assertTrue(fm.isEnabled());
266 
267 		fm.setEnabled(false);
268 		assertTrue(!fm.isEnabled());
269 		assertEquals(1, pcl.eventCount());
270 
271 		fm.setEnabled(false);
272 		assertTrue(!fm.isEnabled());
273 		assertEquals(1, pcl.eventCount());
274 
275 		fm.setEnabled(true);
276 		assertTrue(fm.isEnabled());
277 		assertEquals(2, pcl.eventCount());
278 
279 		fm.setEnabled(true);
280 		assertTrue(fm.isEnabled());
281 		assertEquals(2, pcl.eventCount());
282 	}
283 
284 	public void testEnabledTracksParent() {
285 		TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.ENABLED_PROPERTY);
286 		AbstractFormModel pfm = getFormModel(new Object());
287 		AbstractFormModel fm = getFormModel(new Object());
288 		fm.addPropertyChangeListener(FormModel.ENABLED_PROPERTY, pcl);
289 		pfm.addChild(fm);
290 
291 		pfm.setEnabled(false);
292 		assertTrue(!fm.isEnabled());
293 		assertEquals(1, pcl.eventCount());
294 
295 		pfm.setEnabled(true);
296 		assertTrue(fm.isEnabled());
297 		assertEquals(2, pcl.eventCount());
298 
299 		pfm.setEnabled(false);
300 		assertTrue(!fm.isEnabled());
301 		assertEquals(3, pcl.eventCount());
302 
303 		fm.setEnabled(false);
304 		assertTrue(!fm.isEnabled());
305 		assertEquals(3, pcl.eventCount());
306 
307 		pfm.setEnabled(true);
308 		assertTrue(!fm.isEnabled());
309 		assertEquals(3, pcl.eventCount());
310 
311 		fm.setEnabled(true);
312 		assertTrue(fm.isEnabled());
313 		assertEquals(4, pcl.eventCount());
314 	}
315 
316 	public void testConvertingValueModels() {
317 		AbstractFormModel fm = getFormModel(new TestBean());
318 		TestConversionService cs = new TestConversionService();
319 		fm.setConversionService(cs);
320 
321 		ValueModel vm = fm.getValueModel("simpleProperty", String.class);
322 		assertEquals(fm.getValueModel("simpleProperty"), vm);
323 		assertEquals(0, cs.calls);
324 
325 		try {
326 			fm.getValueModel("simpleProperty", Integer.class);
327 			fail("should have throw IllegalArgumentException");
328 		}
329 		catch (IllegalArgumentException e) {
330 			// expected
331 		}
332 		assertEquals(1, cs.calls);
333 		assertEquals(String.class, cs.lastSource);
334 		assertEquals(Integer.class, cs.lastTarget);
335 
336 		cs.executer = new ConversionExecutor(String.class, Integer.class, new CopiedPublicNoOpConverter(String.class,
337 				Integer.class));
338 		ValueModel cvm = fm.getValueModel("simpleProperty", Integer.class);
339 		assertEquals(3, cs.calls);
340 		assertEquals(Integer.class, cs.lastSource);
341 		assertEquals(String.class, cs.lastTarget);
342 
343 		assertEquals(fm.getValueModel("simpleProperty", Integer.class), cvm);
344 		assertEquals(3, cs.calls);
345 	}
346 
347 	public void testFieldMetadata() {
348 		AbstractFormModel fm = getFormModel(new TestBean());
349 
350 		assertEquals(String.class, fm.getFieldMetadata("simpleProperty").getPropertyType());
351 		assertTrue(!fm.getFieldMetadata("simpleProperty").isReadOnly());
352 
353 		assertEquals(Object.class, fm.getFieldMetadata("readOnly").getPropertyType());
354 		assertTrue(fm.getFieldMetadata("readOnly").isReadOnly());
355 	}
356 
357 	public void testSetFormObjectUpdatesDirtyState() {
358 		final AbstractFormModel fm = getFormModel(new TestBean());
359 		fm.add("simpleProperty");
360 		fm.add("singleSelectListProperty");
361 
362 		assertTrue(!fm.isDirty());
363 
364 		fm.getValueModel("simpleProperty").addValueChangeListener(new PropertyChangeListener() {
365 
366 			public void propertyChange(PropertyChangeEvent evt) {
367 				fm.getValueModel("singleSelectListProperty").setValue(null);
368 			}
369 		});
370 
371 		TestBean newBean = new TestBean();
372 		newBean.setSimpleProperty("simpleProperty");
373 		newBean.setSingleSelectListProperty("singleSelectListProperty");
374 		fm.setFormObject(newBean);
375 		assertEquals(null, fm.getValueModel("singleSelectListProperty").getValue());
376 		assertTrue(fm.isDirty());
377 		fm.getValueModel("singleSelectListProperty").setValue("singleSelectListProperty");
378 		assertTrue(!fm.isDirty());
379 	}
380 
381 	public void testFormPropertiesAreAccessableFromFormObjectChangeEvents() {
382 		final AbstractFormModel fm = getFormModel(new TestBean());
383 		assertEquals(null, fm.getValueModel("simpleProperty").getValue());
384 		TestBean newTestBean = new TestBean();
385 		newTestBean.setSimpleProperty("NewValue");
386 		fm.getFormObjectHolder().addValueChangeListener(new PropertyChangeListener() {
387 
388 			public void propertyChange(PropertyChangeEvent evt) {
389 				assertEquals("NewValue", fm.getValueModel("simpleProperty").getValue());
390 			}
391 		});
392 		fm.setFormObject(newTestBean);
393 	}
394 
395 	public void testFormObjectChangeEventComesBeforePropertyChangeEvent() {
396 		final TestBean testBean = new TestBean();
397 		final AbstractFormModel fm = getFormModel(testBean);
398 		final TestBean newTestBean = new TestBean();
399 		newTestBean.setSimpleProperty("NewValue");
400 		final boolean[] formObjectChangeCalled = new boolean[1];
401 		fm.getFormObjectHolder().addValueChangeListener(new PropertyChangeListener() {
402 
403 			public void propertyChange(PropertyChangeEvent evt) {
404 				formObjectChangeCalled[0] = true;
405 			}
406 		});
407 		fm.getValueModel("simpleProperty").addValueChangeListener(new PropertyChangeListener() {
408 
409 			public void propertyChange(PropertyChangeEvent evt) {
410 				assertEquals("Form property change event was called before form object change event", true,
411 						formObjectChangeCalled[0]);
412 			}
413 		});
414 		fm.setFormObject(newTestBean);
415 	}
416 
417 	public void testFormObjectChangeEvents() {
418 		TestBean testBean = new TestBean();
419 		final AbstractFormModel fm = getFormModel(testBean);
420 		TestBean newTestBean = new TestBean();
421 		newTestBean.setSimpleProperty("NewValue");
422 		TestPropertyChangeListener testPCL = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY);
423 		fm.getFormObjectHolder().addValueChangeListener(testPCL);
424 		fm.setFormObject(newTestBean);
425 		assertEquals(1, testPCL.eventCount());
426 		assertEquals(testBean, testPCL.lastEvent().getOldValue());
427 		assertEquals(newTestBean, testPCL.lastEvent().getNewValue());
428 	}
429 
430 	public static class TestCommitListener implements CommitListener {
431 		int preEditCalls;
432 
433 		int postEditCalls;
434 
435 		public void preCommit(FormModel formModel) {
436 			preEditCalls++;
437 		}
438 
439 		public void postCommit(FormModel formModel) {
440 			postEditCalls++;
441 		}
442 	}
443 
444 	public class TestConversionService implements ConversionService {
445 
446 		public int calls;
447 
448 		public Class lastSource;
449 
450 		public Class lastTarget;
451 
452 		public ConversionExecutor executer;
453 
454 		public ConversionExecutor getConversionExecutor(Class source, Class target) {
455 			calls++;
456 			lastSource = source;
457 			lastTarget = target;
458 			if (executer != null) {
459 				return executer;
460 			}
461 			throw new IllegalArgumentException("no converter found");
462 		}
463 
464 		public ConversionExecutor getConversionExecutorByTargetAlias(Class arg0, String arg1)
465 				throws IllegalArgumentException {
466 			fail("this method should never be called");
467 			return null;
468 		}
469 
470 		public Class getClassByAlias(String arg0) {
471 			fail("this method should never be called");
472 			return null;
473 		}
474 
475 		public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass) throws ConversionException {
476 			fail("this method should never be called");
477 			return null;
478 		}
479 	}
480 
481 	/**
482 	 * <p>
483 	 * <b>Summary: </b>Setting a new FormObject should always result in a clean
484 	 * model (not dirty). Using buffered=<code>true</code>.
485 	 * </p>
486 	 * 
487 	 * <p>
488 	 * This test checks that when a valueModel is dirty and a new FormObject is
489 	 * set which has the same value for that valueModel, the formModel should
490 	 * not be dirty.
491 	 * </p>
492 	 */
493 	public void testBufferedFormModelSetFormObjectNotDirty() {
494 		String someString = "someString";
495 		FormModel model = getFormModel(new TestBean());
496 		ValueModel valueModel = model.getValueModel("simpleProperty");
497 
498 		assertEquals("Initial check, formmodel not dirty.", false, model.isDirty());
499 
500 		valueModel.setValue(someString);
501 		assertEquals("Value changed, model should be dirty.", true, model.isDirty());
502 
503 		TestBean newFormObject = new TestBean();
504 		newFormObject.setSimpleProperty(someString);
505 		model.setFormObject(newFormObject);
506 		assertEquals("New formObject is set, model should not be dirty.", false, model.isDirty());
507 	}
508 
509 	/**
510 	 * <p>
511 	 * <b>Summary: </b>Setting a new FormObject should always result in a clean
512 	 * model (not dirty). Using buffered=<code>false</code>.
513 	 * </p>
514 	 * 
515 	 * <p>
516 	 * This test checks that when a valueModel is dirty and a new FormObject is
517 	 * set which has the same value for that valueModel, the formModel should
518 	 * not be dirty.
519 	 * </p>
520 	 */
521 	public void testFormModelSetFormObjectNotDirty() {
522 		String someString = "someString";
523 		FormModel model = getFormModel(new ValueHolder(new TestBean()), false);
524 		ValueModel valueModel = model.getValueModel("simpleProperty");
525 
526 		assertEquals("Initial check, formmodel not dirty.", false, model.isDirty());
527 
528 		valueModel.setValue(someString);
529 		assertEquals("Value changed, model should be dirty.", true, model.isDirty());
530 
531 		TestBean newFormObject = new TestBean();
532 		newFormObject.setSimpleProperty(someString);
533 		model.setFormObject(newFormObject);
534 		assertEquals("New formObject is set, model should not be dirty.", false, model.isDirty());
535 	}
536 
537 	/**
538 	 * <p>
539 	 * Test whether the enabled state is correctly propagated between
540 	 * parent-child formModel and that the proper events are fired.
541 	 * </p>
542 	 * <p>
543 	 * In detail:
544 	 * <ul>
545 	 * <li>if parent is enabled: should allow child to handle it's own state</li>
546 	 * <li>if parent is disabled: should override child's enabled state</li>
547 	 * </ul>
548 	 * </p>
549 	 */
550 	public void testParentChildEnabledState() {
551 		TestBean formObject = new TestBean();
552 		AbstractFormModel parent = getFormModel(formObject);
553 		AbstractFormModel child = getFormModel(formObject);
554 		BooleanStatelistener listener = new BooleanStatelistener(FormModel.ENABLED_PROPERTY);
555 		listener.state = child.isEnabled();
556 		child.addPropertyChangeListener(FormModel.ENABLED_PROPERTY, listener);
557 
558 		parent.addChild(child);
559  
560 		// check if parent->enabled then (child->enabled or child->disabled) 
561 		parent.setEnabled(true);
562 		child.setEnabled(true);
563 		assertTrue(listener.state);
564 		child.setEnabled(false);
565 		assertFalse(listener.state);
566 		
567 		// check if parent->disabled then always child->disabled
568 		parent.setEnabled(false);
569 		child.setEnabled(false);
570 		assertFalse(listener.state);
571 		child.setEnabled(true);
572 		assertFalse(listener.state);
573 
574 		parent.removeChild(child);
575 		
576 		// check initial state when adding a child formModel, state should be synchronized at setup and reverted when removing the relation
577 		
578 		// check parent->disabled is correctly overriding child state 
579 		parent.setEnabled(false);
580 		child.setEnabled(true);
581 		parent.addChild(child);
582 		assertFalse(listener.state);
583 		parent.removeChild(child);
584 		assertTrue(listener.state);
585 		parent.setEnabled(false);
586 		child.setEnabled(false);
587 		parent.addChild(child);
588 		assertFalse(listener.state);
589 		parent.removeChild(child);
590 		assertFalse(listener.state);
591 		
592 		// check parent->enabled is correctly allowing child state to override 
593 		parent.setEnabled(true);
594 		child.setEnabled(false);
595 		parent.addChild(child);
596 		assertFalse(listener.state);
597 		parent.removeChild(child);
598 		assertFalse(listener.state);
599 		parent.setEnabled(true);
600 		child.setEnabled(true);
601 		parent.addChild(child);
602 		assertTrue(listener.state);
603 		parent.removeChild(child);
604 		assertTrue(listener.state);
605 	}
606 	
607 	/**
608 	 * <p>
609 	 * Test whether the read-only state is correctly propagated between
610 	 * parent-child formModel and that the proper events are fired.
611 	 * </p>
612 	 * <p>
613 	 * In detail:
614 	 * <ul>
615 	 * <li>if parent is readOnly: child should be readOnly</li>
616 	 * <li>if parent isn't readOnly: child can handle it's own state</li>
617 	 * </ul>
618 	 * </p>
619 	 */
620 	public void testParentChildReadOnlyState() {
621 		TestBean formObject = new TestBean();
622 		AbstractFormModel parent = getFormModel(formObject);
623 		AbstractFormModel child = getFormModel(formObject);
624 		BooleanStatelistener listener = new BooleanStatelistener(FormModel.READONLY_PROPERTY);
625 		listener.state = child.isReadOnly();
626 		child.addPropertyChangeListener(FormModel.READONLY_PROPERTY, listener);
627 
628 		parent.addChild(child);
629  
630 		// if parent->readOnly then child->readOnly
631 		parent.setReadOnly(true);
632 		child.setReadOnly(false);
633 		assertTrue(listener.state);
634 		child.setReadOnly(true);
635 		assertTrue(listener.state);
636 		
637 		// if parent->writable then (child->writable or child->readOnly)
638 		parent.setReadOnly(false);
639 		child.setReadOnly(false);
640 		assertFalse(listener.state);
641 		child.setReadOnly(true);
642 		assertTrue(listener.state);
643 
644 		parent.removeChild(child);
645 
646 		// check initial state when adding a child formModel, state should be synchronized at setup and reverted when removing the relation
647 
648 		// check parent->writable is correctly allowing child to override
649 		parent.setReadOnly(false);
650 		child.setReadOnly(true);
651 		parent.addChild(child);
652 		assertTrue(listener.state);
653 		parent.removeChild(child);
654 		assertTrue(listener.state);
655 		parent.setReadOnly(false);
656 		child.setReadOnly(false);
657 		parent.addChild(child);
658 		assertFalse(listener.state);
659 		parent.removeChild(child);
660 		assertFalse(listener.state);
661 		
662 		// check parent->readOnly is correctly overriding child state
663 		parent.setReadOnly(true);
664 		child.setReadOnly(false);
665 		parent.addChild(child);
666 		assertTrue(listener.state);
667 		parent.removeChild(child);
668 		assertFalse(listener.state);
669 		parent.setReadOnly(true);
670 		child.setReadOnly(true);
671 		parent.addChild(child);
672 		assertTrue(listener.state);
673 		parent.removeChild(child);
674 		assertTrue(listener.state);
675 	}
676 	
677 	/**
678 	 * Listener to register on boolean properties to check if they are in the expected state.
679 	 */
680 	protected static class BooleanStatelistener implements PropertyChangeListener {
681 		
682 		final String property;
683 		
684 		boolean state = false;
685 		
686 		public BooleanStatelistener(final String property) {
687 			this.property = property;
688 		}
689 		
690 		public void propertyChange(PropertyChangeEvent evt) {
691 			if (property.equals(evt.getPropertyName()))
692 				state = Boolean.parseBoolean(evt.getNewValue().toString());
693 		}
694 	}
695 
696 	/**
697 	 * <p>
698 	 * Test whether the dirty state is correctly propagated between
699 	 * parent-child formModel and that the proper events are fired.
700 	 * </p>
701 	 * <p>
702 	 * In detail:
703 	 * <ul>
704 	 * <li>if child is dirty then parent MUST be dirty</li>
705 	 * <li>if child isn't dirty then parent CAN be dirty</li>
706 	 * </ul>
707 	 */
708 	public void testParentChildDirtyState() {
709 		TestBean formObject = new TestBean();
710 		AbstractFormModel parent = getFormModel(formObject);
711 		ValueModel parentValueModel = parent.getValueModel("simpleProperty");
712 		AbstractFormModel child = getFormModel(formObject);
713 		ValueModel childValueModel = child.getValueModel("booleanProperty");
714 		BooleanStatelistener listener = new BooleanStatelistener(FormModel.DIRTY_PROPERTY);
715 		listener.state = parent.isDirty();
716 		parent.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, listener);
717 
718 		parent.addChild(child);
719  
720 		// check if child->dirty then parent->dirty
721 		assertFalse(listener.state);
722 		childValueModel.setValue(Boolean.TRUE);
723 		assertTrue(listener.state);
724 		parentValueModel.setValue("x");
725 		assertTrue(listener.state);
726 		parentValueModel.setValue(null); // original value
727 		assertTrue(listener.state);
728 		childValueModel.setValue(Boolean.FALSE); //original value
729 		assertFalse(listener.state);
730 		
731 		// check if child->clean then (parent->clean or parent->dirty)
732 		parentValueModel.setValue("x");
733 		assertTrue(listener.state);
734 		parentValueModel.setValue(null); // original value
735 		assertFalse(listener.state);
736 				
737 		parent.removeChild(child);
738 
739 		// check initial state when adding a child formModel, state should be synchronized at setup and reverted when removing the relation
740 
741 		// check if dirty child sets parent dirty
742 		childValueModel.setValue(Boolean.TRUE);
743 		assertFalse(listener.state);
744 		parent.addChild(child);
745 		assertTrue(listener.state);
746 		parent.removeChild(child);
747 		assertFalse(listener.state);
748 		parentValueModel.setValue("x");
749 		assertTrue(listener.state);
750 		parent.addChild(child);
751 		assertTrue(listener.state);
752 		parent.removeChild(child);
753 		assertTrue(listener.state);
754 		
755 		// check if clean child allows parent to override
756 		child.revert();
757 		parent.revert();
758 		assertFalse(listener.state);
759 		parent.addChild(child);
760 		assertFalse(listener.state);
761 		parent.removeChild(child);
762 		parentValueModel.setValue("x");
763 		assertTrue(listener.state);
764 		parent.addChild(child);
765 		assertTrue(listener.state);
766 		parent.removeChild(child);
767 		assertTrue(listener.state);
768 	}
769 }