001    /*
002     * Copyright 2002-2004 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.binding.form.support;
017    
018    import org.springframework.beans.NotReadablePropertyException;
019    import org.springframework.binding.convert.ConversionException;
020    import org.springframework.binding.convert.ConversionExecutor;
021    import org.springframework.binding.convert.ConversionService;
022    import org.springframework.binding.form.CommitListener;
023    import org.springframework.binding.form.FormModel;
024    import org.springframework.binding.support.BeanPropertyAccessStrategy;
025    import org.springframework.binding.support.TestBean;
026    import org.springframework.binding.support.TestPropertyChangeListener;
027    import org.springframework.binding.value.ValueModel;
028    import org.springframework.binding.value.support.ValueHolder;
029    import org.springframework.richclient.test.SpringRichTestCase;
030    
031    import java.beans.PropertyChangeEvent;
032    import java.beans.PropertyChangeListener;
033    
034    /**
035     * Tests for
036     * @link AbstractFormModel
037     * 
038     * @author Oliver Hutchison
039     */
040    public abstract class AbstractFormModelTests extends SpringRichTestCase {
041    
042            protected AbstractFormModel getFormModel(Object formObject) {
043                    return new TestAbstractFormModel(formObject);
044            }
045    
046            protected AbstractFormModel getFormModel(BeanPropertyAccessStrategy pas, boolean buffering) {
047                    return new TestAbstractFormModel(pas, buffering);
048            }
049    
050            protected AbstractFormModel getFormModel(ValueModel valueModel, boolean buffering) {
051                    return new TestAbstractFormModel(valueModel, buffering);
052            }
053    
054            public void testGetValueModelFromPAS() {
055                    TestBean p = new TestBean();
056                    TestPropertyAccessStrategy tpas = new TestPropertyAccessStrategy(p);
057                    AbstractFormModel fm = getFormModel(tpas, true);
058                    ValueModel vm1 = fm.getValueModel("simpleProperty");
059                    assertEquals(1, tpas.numValueModelRequests());
060                    assertEquals("simpleProperty", tpas.lastRequestedValueModel());
061                    ValueModel vm2 = fm.getValueModel("simpleProperty");
062                    assertEquals(vm1, vm2);
063                    assertEquals(1, tpas.numValueModelRequests());
064    
065                    try {
066                            fm.getValueModel("iDontExist");
067                            fail("should't be able to get value model for invalid property");
068                    }
069                    catch (NotReadablePropertyException e) {
070                            // exprected
071                    }
072            }
073    
074            public void testUnbufferedWritesThrough() {
075                    TestBean p = new TestBean();
076                    BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
077                    AbstractFormModel fm = getFormModel(pas, false);
078                    ValueModel vm = fm.getValueModel("simpleProperty");
079    
080                    vm.setValue("1");
081                    assertEquals("1", p.getSimpleProperty());
082    
083                    vm.setValue(null);
084                    assertEquals(null, p.getSimpleProperty());
085            }
086    
087            public void testBufferedDoesNotWriteThrough() {
088                    TestBean p = new TestBean();
089                    BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p);
090                    AbstractFormModel fm = getFormModel(pas, true);
091                    ValueModel vm = fm.getValueModel("simpleProperty");
092    
093                    vm.setValue("1");
094                    assertEquals(null, p.getSimpleProperty());
095    
096                    vm.setValue(null);
097                    assertEquals(null, p.getSimpleProperty());
098            }
099    
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    }