/** * Copyright (C) 2015 Valkyrie RCP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.valkyriercp.binding.form.support; import org.junit.Test; import org.springframework.beans.NotReadablePropertyException; import org.springframework.binding.convert.ConversionException; import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.service.GenericConversionService; import org.springframework.binding.convert.service.StaticConversionExecutor; import org.valkyriercp.AbstractValkyrieTest; import org.valkyriercp.binding.form.CommitListener; import org.valkyriercp.binding.form.FormModel; import org.valkyriercp.binding.support.BeanPropertyAccessStrategy; import org.valkyriercp.binding.value.ValueModel; import org.valkyriercp.binding.value.support.ValueHolder; import org.valkyriercp.test.TestBean; import org.valkyriercp.test.TestPropertyChangeListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import static org.junit.Assert.*; /** * Tests for * @link AbstractFormModel * * @author Oliver Hutchison */ public abstract class AbstractFormModelTests extends AbstractValkyrieTest { protected AbstractFormModel getFormModel(Object formObject) { return new TestAbstractFormModel(formObject); } protected AbstractFormModel getFormModel(BeanPropertyAccessStrategy pas, boolean buffering) { return new TestAbstractFormModel(pas, buffering); } protected AbstractFormModel getFormModel(ValueModel valueModel, boolean buffering) { return new TestAbstractFormModel(valueModel, buffering); } @Test public void testGetValueModelFromPAS() { TestBean p = new TestBean(); TestPropertyAccessStrategy tpas = new TestPropertyAccessStrategy(p); AbstractFormModel fm = getFormModel(tpas, true); ValueModel vm1 = fm.getValueModel("simpleProperty"); assertEquals(1, tpas.numValueModelRequests()); assertEquals("simpleProperty", tpas.lastRequestedValueModel()); ValueModel vm2 = fm.getValueModel("simpleProperty"); assertEquals(vm1, vm2); assertEquals(1, tpas.numValueModelRequests()); try { fm.getValueModel("iDontExist"); fail("should't be able to get value model for invalid property"); } catch (NotReadablePropertyException e) { // exprected } } @Test public void testUnbufferedWritesThrough() { TestBean p = new TestBean(); BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p); AbstractFormModel fm = getFormModel(pas, false); ValueModel vm = fm.getValueModel("simpleProperty"); vm.setValue("1"); assertEquals("1", p.getSimpleProperty()); vm.setValue(null); assertEquals(null, p.getSimpleProperty()); } @Test public void testBufferedDoesNotWriteThrough() { TestBean p = new TestBean(); BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p); AbstractFormModel fm = getFormModel(pas, true); ValueModel vm = fm.getValueModel("simpleProperty"); vm.setValue("1"); assertEquals(null, p.getSimpleProperty()); vm.setValue(null); assertEquals(null, p.getSimpleProperty()); } @Test public void testDirtyTrackingWithBuffering() { testDirtyTracking(true); } @Test public void testDirtyTrackingWithoutBuffering() { testDirtyTracking(false); } public void testDirtyTracking(boolean buffering) { TestBean p = new TestBean(); BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p); TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.DIRTY_PROPERTY); AbstractFormModel fm = getFormModel(pas, buffering); fm.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, pcl); ValueModel vm = fm.getValueModel("simpleProperty"); assertTrue(!fm.isDirty()); vm.setValue("2"); assertTrue(fm.isDirty()); assertEquals(1, pcl.eventCount()); fm.commit(); assertTrue(!fm.isDirty()); assertEquals(2, pcl.eventCount()); vm.setValue("1"); assertTrue(fm.isDirty()); assertEquals(3, pcl.eventCount()); fm.setFormObject(new TestBean()); assertTrue(!fm.isDirty()); assertEquals(4, pcl.eventCount()); vm.setValue("2"); assertTrue(fm.isDirty()); assertEquals(5, pcl.eventCount()); fm.revert(); assertTrue(!fm.isDirty()); assertEquals(6, pcl.eventCount()); } /** * Test on dirty state of parent-child relations. When child gets dirty, * parent should also be dirty. When parent reverts, child should revert * too. */ @Test public void testDirtyTracksKids() { TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.DIRTY_PROPERTY); AbstractFormModel pfm = getFormModel(new TestBean()); AbstractFormModel fm = getFormModel(new TestBean()); pfm.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, pcl); pfm.addChild(fm); ValueModel childSimpleProperty = fm.getValueModel("simpleProperty"); ValueModel parentSimpleProperty = pfm.getValueModel("simpleProperty"); // test child property dirty -> parent dirty childSimpleProperty.setValue("1"); assertTrue(pfm.isDirty()); assertEquals(1, pcl.eventCount()); fm.revert(); assertTrue(!pfm.isDirty()); assertEquals(2, pcl.eventCount()); // child dirty -> revert parent triggers revert on child childSimpleProperty.setValue("1"); assertTrue(pfm.isDirty()); assertEquals(3, pcl.eventCount()); pfm.revert(); assertTrue(!pfm.isDirty()); assertTrue(!fm.isDirty()); assertEquals(4, pcl.eventCount()); // child & parent property dirty -> parent dirty, revert child, then // parent childSimpleProperty.setValue("1"); assertTrue(pfm.isDirty()); assertEquals(5, pcl.eventCount()); parentSimpleProperty.setValue("2"); assertTrue(pfm.isDirty()); assertEquals(5, pcl.eventCount()); fm.revert(); assertTrue(pfm.isDirty()); assertEquals(5, pcl.eventCount()); pfm.revert(); assertTrue(!pfm.isDirty()); assertEquals(6, pcl.eventCount()); } @Test public void testSetFormObjectDoesNotRevertChangesToPreviousFormObject() { TestBean p1 = new TestBean(); BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p1); AbstractFormModel fm = getFormModel(pas, false); fm.getValueModel("simpleProperty").setValue("1"); fm.setFormObject(new TestBean()); assertEquals("1", p1.getSimpleProperty()); } @Test public void testCommitEvents() { TestBean p = new TestBean(); BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p); TestCommitListener cl = new TestCommitListener(); AbstractFormModel fm = getFormModel(pas, false); fm.addCommitListener(cl); ValueModel vm = fm.getValueModel("simpleProperty"); vm.setValue("1"); fm.commit(); assertEquals(1, cl.preEditCalls); assertEquals(1, cl.postEditCalls); } @Test public void testCommitWritesBufferingThrough() { TestBean p = new TestBean(); BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p); TestCommitListener cl = new TestCommitListener(); AbstractFormModel fm = getFormModel(pas, true); fm.addCommitListener(cl); ValueModel vm = fm.getValueModel("simpleProperty"); vm.setValue("1"); fm.commit(); assertEquals("1", p.getSimpleProperty()); } @Test public void testRevertWithBuffering() { testRevert(true); } @Test public void testRevertWithoutBuffering() { testRevert(false); } public void testRevert(boolean buffering) { TestBean p = new TestBean(); BeanPropertyAccessStrategy pas = new BeanPropertyAccessStrategy(p); TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.DIRTY_PROPERTY); AbstractFormModel fm = getFormModel(pas, buffering); fm.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, pcl); ValueModel vm = fm.getValueModel("simpleProperty"); vm.setValue("1"); fm.revert(); assertEquals(null, vm.getValue()); assertEquals(null, p.getSimpleProperty()); TestBean tb2 = new TestBean(); tb2.setSimpleProperty("tb2"); fm.setFormObject(tb2); vm.setValue("1"); fm.revert(); assertEquals("tb2", vm.getValue()); assertEquals("tb2", tb2.getSimpleProperty()); } @Test public void testEnabledEvents() { TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.ENABLED_PROPERTY); AbstractFormModel fm = getFormModel(new Object()); fm.addPropertyChangeListener(FormModel.ENABLED_PROPERTY, pcl); assertTrue(fm.isEnabled()); fm.setEnabled(false); assertTrue(!fm.isEnabled()); assertEquals(1, pcl.eventCount()); fm.setEnabled(false); assertTrue(!fm.isEnabled()); assertEquals(1, pcl.eventCount()); fm.setEnabled(true); assertTrue(fm.isEnabled()); assertEquals(2, pcl.eventCount()); fm.setEnabled(true); assertTrue(fm.isEnabled()); assertEquals(2, pcl.eventCount()); } @Test public void testEnabledTracksParent() { TestPropertyChangeListener pcl = new TestPropertyChangeListener(FormModel.ENABLED_PROPERTY); AbstractFormModel pfm = getFormModel(new Object()); AbstractFormModel fm = getFormModel(new Object()); fm.addPropertyChangeListener(FormModel.ENABLED_PROPERTY, pcl); pfm.addChild(fm); pfm.setEnabled(false); assertTrue(!fm.isEnabled()); assertEquals(1, pcl.eventCount()); pfm.setEnabled(true); assertTrue(fm.isEnabled()); assertEquals(2, pcl.eventCount()); pfm.setEnabled(false); assertTrue(!fm.isEnabled()); assertEquals(3, pcl.eventCount()); fm.setEnabled(false); assertTrue(!fm.isEnabled()); assertEquals(3, pcl.eventCount()); pfm.setEnabled(true); assertTrue(!fm.isEnabled()); assertEquals(3, pcl.eventCount()); fm.setEnabled(true); assertTrue(fm.isEnabled()); assertEquals(4, pcl.eventCount()); } @Test public void testConvertingValueModels() { AbstractFormModel fm = getFormModel(new TestBean()); TestConversionService cs = new TestConversionService(); fm.setConversionService(cs); ValueModel vm = fm.getValueModel("simpleProperty", String.class); assertEquals(fm.getValueModel("simpleProperty"), vm); assertEquals(0, cs.calls); cs.executer = new StaticConversionExecutor(String.class, Integer.class, new CopiedPublicNoOpConverter(String.class, Integer.class)); ValueModel cvm = fm.getValueModel("simpleProperty", Integer.class); assertEquals(2, cs.calls); assertEquals(Integer.class, cs.lastSource); assertEquals(String.class, cs.lastTarget); assertEquals(fm.getValueModel("simpleProperty", Integer.class), cvm); assertEquals(2, cs.calls); } @Test public void testFieldMetadata() { AbstractFormModel fm = getFormModel(new TestBean()); assertEquals(String.class, fm.getFieldMetadata("simpleProperty").getPropertyType()); assertTrue(!fm.getFieldMetadata("simpleProperty").isReadOnly()); assertEquals(Object.class, fm.getFieldMetadata("readOnly").getPropertyType()); assertTrue(fm.getFieldMetadata("readOnly").isReadOnly()); } @Test public void testSetFormObjectUpdatesDirtyState() { final AbstractFormModel fm = getFormModel(new TestBean()); fm.add("simpleProperty"); fm.add("singleSelectListProperty"); assertTrue(!fm.isDirty()); fm.getValueModel("simpleProperty").addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { fm.getValueModel("singleSelectListProperty").setValue(null); } }); TestBean newBean = new TestBean(); newBean.setSimpleProperty("simpleProperty"); newBean.setSingleSelectListProperty("singleSelectListProperty"); fm.setFormObject(newBean); assertEquals(null, fm.getValueModel("singleSelectListProperty").getValue()); assertTrue(fm.isDirty()); fm.getValueModel("singleSelectListProperty").setValue("singleSelectListProperty"); assertTrue(!fm.isDirty()); } @Test public void testFormPropertiesAreAccessableFromFormObjectChangeEvents() { final AbstractFormModel fm = getFormModel(new TestBean()); assertEquals(null, fm.getValueModel("simpleProperty").getValue()); TestBean newTestBean = new TestBean(); newTestBean.setSimpleProperty("NewValue"); fm.getFormObjectHolder().addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { assertEquals("NewValue", fm.getValueModel("simpleProperty").getValue()); } }); fm.setFormObject(newTestBean); } @Test public void testFormObjectChangeEventComesBeforePropertyChangeEvent() { final TestBean testBean = new TestBean(); final AbstractFormModel fm = getFormModel(testBean); final TestBean newTestBean = new TestBean(); newTestBean.setSimpleProperty("NewValue"); final boolean[] formObjectChangeCalled = new boolean[1]; fm.getFormObjectHolder().addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { formObjectChangeCalled[0] = true; } }); fm.getValueModel("simpleProperty").addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { assertEquals("Form property change event was called before form object change event", true, formObjectChangeCalled[0]); } }); fm.setFormObject(newTestBean); } @Test public void testFormObjectChangeEvents() { TestBean testBean = new TestBean(); final AbstractFormModel fm = getFormModel(testBean); TestBean newTestBean = new TestBean(); newTestBean.setSimpleProperty("NewValue"); TestPropertyChangeListener testPCL = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY); fm.getFormObjectHolder().addValueChangeListener(testPCL); fm.setFormObject(newTestBean); assertEquals(1, testPCL.eventCount()); assertEquals(testBean, testPCL.lastEvent().getOldValue()); assertEquals(newTestBean, testPCL.lastEvent().getNewValue()); } public static class TestCommitListener implements CommitListener { int preEditCalls; int postEditCalls; public void preCommit(FormModel formModel) { preEditCalls++; } public void postCommit(FormModel formModel) { postEditCalls++; } } public class TestConversionService extends GenericConversionService { public int calls; public Class lastSource; public Class lastTarget; public ConversionExecutor executer; public ConversionExecutor getConversionExecutor(String converterId, Class source, Class target) { calls++; lastSource = source; lastTarget = target; if (executer != null) { return executer; } throw new IllegalArgumentException("no converter found"); } public ConversionExecutor getConversionExecutor(Class source, Class target) { calls++; lastSource = source; lastTarget = target; if (executer != null) { return executer; } throw new IllegalArgumentException("no converter found"); } public ConversionExecutor getConversionExecutorByTargetAlias(Class arg0, String arg1) throws IllegalArgumentException { fail("this method should never be called"); return null; } public Class getClassByAlias(String arg0) { fail("this method should never be called"); return null; } public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass) throws ConversionException { fail("this method should never be called"); return null; } } /** * <p> * <b>Summary: </b>Setting a new FormObject should always result in a clean * model (not dirty). Using buffered=<code>true</code>. * </p> * * <p> * This test checks that when a valueModel is dirty and a new FormObject is * set which has the same value for that valueModel, the formModel should * not be dirty. * </p> */ @Test public void testBufferedFormModelSetFormObjectNotDirty() { String someString = "someString"; FormModel model = getFormModel(new TestBean()); ValueModel valueModel = model.getValueModel("simpleProperty"); assertEquals("Initial check, formmodel not dirty.", false, model.isDirty()); valueModel.setValue(someString); assertEquals("Value changed, model should be dirty.", true, model.isDirty()); TestBean newFormObject = new TestBean(); newFormObject.setSimpleProperty(someString); model.setFormObject(newFormObject); assertEquals("New formObject is set, model should not be dirty.", false, model.isDirty()); } /** * <p> * <b>Summary: </b>Setting a new FormObject should always result in a clean * model (not dirty). Using buffered=<code>false</code>. * </p> * * <p> * This test checks that when a valueModel is dirty and a new FormObject is * set which has the same value for that valueModel, the formModel should * not be dirty. * </p> */ @Test public void testFormModelSetFormObjectNotDirty() { String someString = "someString"; FormModel model = getFormModel(new ValueHolder(new TestBean()), false); ValueModel valueModel = model.getValueModel("simpleProperty"); assertEquals("Initial check, formmodel not dirty.", false, model.isDirty()); valueModel.setValue(someString); assertEquals("Value changed, model should be dirty.", true, model.isDirty()); TestBean newFormObject = new TestBean(); newFormObject.setSimpleProperty(someString); model.setFormObject(newFormObject); assertEquals("New formObject is set, model should not be dirty.", false, model.isDirty()); } /** * <p> * Test whether the enabled state is correctly propagated between * parent-child formModel and that the proper events are fired. * </p> * <p> * In detail: * <ul> * <li>if parent is enabled: should allow child to handle it's own state</li> * <li>if parent is disabled: should override child's enabled state</li> * </ul> * </p> */ @Test public void testParentChildEnabledState() { TestBean formObject = new TestBean(); AbstractFormModel parent = getFormModel(formObject); AbstractFormModel child = getFormModel(formObject); BooleanStatelistener listener = new BooleanStatelistener(FormModel.ENABLED_PROPERTY); listener.state = child.isEnabled(); child.addPropertyChangeListener(FormModel.ENABLED_PROPERTY, listener); parent.addChild(child); // check if parent->enabled then (child->enabled or child->disabled) parent.setEnabled(true); child.setEnabled(true); assertTrue(listener.state); child.setEnabled(false); assertFalse(listener.state); // check if parent->disabled then always child->disabled parent.setEnabled(false); child.setEnabled(false); assertFalse(listener.state); child.setEnabled(true); assertFalse(listener.state); parent.removeChild(child); // check initial state when adding a child formModel, state should be synchronized at setup and reverted when removing the relation // check parent->disabled is correctly overriding child state parent.setEnabled(false); child.setEnabled(true); parent.addChild(child); assertFalse(listener.state); parent.removeChild(child); assertTrue(listener.state); parent.setEnabled(false); child.setEnabled(false); parent.addChild(child); assertFalse(listener.state); parent.removeChild(child); assertFalse(listener.state); // check parent->enabled is correctly allowing child state to override parent.setEnabled(true); child.setEnabled(false); parent.addChild(child); assertFalse(listener.state); parent.removeChild(child); assertFalse(listener.state); parent.setEnabled(true); child.setEnabled(true); parent.addChild(child); assertTrue(listener.state); parent.removeChild(child); assertTrue(listener.state); } /** * <p> * Test whether the read-only state is correctly propagated between * parent-child formModel and that the proper events are fired. * </p> * <p> * In detail: * <ul> * <li>if parent is readOnly: child should be readOnly</li> * <li>if parent isn't readOnly: child can handle it's own state</li> * </ul> * </p> */ @Test public void testParentChildReadOnlyState() { TestBean formObject = new TestBean(); AbstractFormModel parent = getFormModel(formObject); AbstractFormModel child = getFormModel(formObject); BooleanStatelistener listener = new BooleanStatelistener(FormModel.READONLY_PROPERTY); listener.state = child.isReadOnly(); child.addPropertyChangeListener(FormModel.READONLY_PROPERTY, listener); parent.addChild(child); // if parent->readOnly then child->readOnly parent.setReadOnly(true); child.setReadOnly(false); assertTrue(listener.state); child.setReadOnly(true); assertTrue(listener.state); // if parent->writable then (child->writable or child->readOnly) parent.setReadOnly(false); child.setReadOnly(false); assertFalse(listener.state); child.setReadOnly(true); assertTrue(listener.state); parent.removeChild(child); // check initial state when adding a child formModel, state should be synchronized at setup and reverted when removing the relation // check parent->writable is correctly allowing child to override parent.setReadOnly(false); child.setReadOnly(true); parent.addChild(child); assertTrue(listener.state); parent.removeChild(child); assertTrue(listener.state); parent.setReadOnly(false); child.setReadOnly(false); parent.addChild(child); assertFalse(listener.state); parent.removeChild(child); assertFalse(listener.state); // check parent->readOnly is correctly overriding child state parent.setReadOnly(true); child.setReadOnly(false); parent.addChild(child); assertTrue(listener.state); parent.removeChild(child); assertFalse(listener.state); parent.setReadOnly(true); child.setReadOnly(true); parent.addChild(child); assertTrue(listener.state); parent.removeChild(child); assertTrue(listener.state); } /** * Listener to register on boolean properties to check if they are in the expected state. */ protected static class BooleanStatelistener implements PropertyChangeListener { final String property; boolean state = false; public BooleanStatelistener(final String property) { this.property = property; } public void propertyChange(PropertyChangeEvent evt) { if (property.equals(evt.getPropertyName())) state = Boolean.parseBoolean(evt.getNewValue().toString()); } } /** * <p> * Test whether the dirty state is correctly propagated between * parent-child formModel and that the proper events are fired. * </p> * <p> * In detail: * <ul> * <li>if child is dirty then parent MUST be dirty</li> * <li>if child isn't dirty then parent CAN be dirty</li> * </ul> */ @Test public void testParentChildDirtyState() { TestBean formObject = new TestBean(); AbstractFormModel parent = getFormModel(formObject); ValueModel parentValueModel = parent.getValueModel("simpleProperty"); AbstractFormModel child = getFormModel(formObject); ValueModel childValueModel = child.getValueModel("booleanProperty"); BooleanStatelistener listener = new BooleanStatelistener(FormModel.DIRTY_PROPERTY); listener.state = parent.isDirty(); parent.addPropertyChangeListener(FormModel.DIRTY_PROPERTY, listener); parent.addChild(child); // check if child->dirty then parent->dirty assertFalse(listener.state); childValueModel.setValue(Boolean.TRUE); assertTrue(listener.state); parentValueModel.setValue("x"); assertTrue(listener.state); parentValueModel.setValue(null); // original value assertTrue(listener.state); childValueModel.setValue(Boolean.FALSE); //original value assertFalse(listener.state); // check if child->clean then (parent->clean or parent->dirty) parentValueModel.setValue("x"); assertTrue(listener.state); parentValueModel.setValue(null); // original value assertFalse(listener.state); parent.removeChild(child); // check initial state when adding a child formModel, state should be synchronized at setup and reverted when removing the relation // check if dirty child sets parent dirty childValueModel.setValue(Boolean.TRUE); assertFalse(listener.state); parent.addChild(child); assertTrue(listener.state); parent.removeChild(child); assertFalse(listener.state); parentValueModel.setValue("x"); assertTrue(listener.state); parent.addChild(child); assertTrue(listener.state); parent.removeChild(child); assertTrue(listener.state); // check if clean child allows parent to override child.revert(); parent.revert(); assertFalse(listener.state); parent.addChild(child); assertFalse(listener.state); parent.removeChild(child); parentValueModel.setValue("x"); assertTrue(listener.state); parent.addChild(child); assertTrue(listener.state); parent.removeChild(child); assertTrue(listener.state); } }