/** * 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.value.support; import org.junit.Before; import org.junit.Test; import org.valkyriercp.AbstractValkyrieTest; import org.valkyriercp.binding.support.BeanPropertyAccessStrategy; import org.valkyriercp.binding.value.CommitTrigger; import org.valkyriercp.binding.value.ValueChangeDetector; import org.valkyriercp.binding.value.ValueModel; import org.valkyriercp.test.TestBean; import org.valkyriercp.test.TestPropertyChangeListener; import static org.junit.Assert.*; public final class BufferedValueModelTests extends AbstractValkyrieTest { private static final Object INITIAL_VALUE = "initial value"; private static final Object RESET_VALUE = "reset value"; private ValueModel wrapped; private CommitTrigger commitTrigger; @Before public void doSetUp() throws Exception { wrapped = new ValueHolder(INITIAL_VALUE); commitTrigger = new CommitTrigger(); } @Test public void testGetWrappedValueModel() { BufferedValueModel buffer = createDefaultBufferedValueModel(); assertSame(wrapped, buffer.getWrappedValueModel()); assertSame(wrapped, buffer.getInnerMostWrappedValueModel()); ValueModel nestedValueModel = new AbstractValueModelWrapper(wrapped) {}; buffer = new BufferedValueModel(nestedValueModel); assertSame(nestedValueModel, buffer.getWrappedValueModel()); assertSame(wrapped, buffer.getInnerMostWrappedValueModel()); } @Test public void testReturnsWrappedValueIfNoValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); assertEquals( "Buffer value equals the wrapped value before any changes.", buffer.getValue(), wrapped.getValue()); wrapped.setValue("change1"); assertEquals( "Buffer value equals the wrapped value changes.", buffer.getValue(), wrapped.getValue()); wrapped.setValue(null); assertEquals( "Buffer value equals the wrapped value changes.", buffer.getValue(), wrapped.getValue()); wrapped.setValue("change2"); assertEquals( "Buffer value equals the wrapped value changes.", buffer.getValue(), wrapped.getValue()); } /** * Tests that the BufferedValueModel returns the buffered values * once a value has been assigned. */ @Test public void testReturnsBufferedValueIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object newValue1 = wrapped.getValue(); buffer.setValue(newValue1); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue1); Object newValue2 = "change1"; buffer.setValue(newValue2); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue2); Object newValue3 = null; buffer.setValue(newValue3); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue3); Object newValue4 = "change2"; buffer.setValue(newValue4); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue4); } @Test public void testDetectedWrappedValueChangeIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object newValue1 = "change1"; buffer.setValue(newValue1); wrapped.setValue("change3"); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), "change3"); wrapped.setValue(newValue1); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue1); wrapped.setValue(null); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), null); } /** * Tests that the BufferedValueModel returns the wrapped's values * after a commit. */ @Test public void testReturnsWrappedValueAfterCommit() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); // shall buffer now commit(); assertEquals( "Buffer value equals the wrapped value after a commit.", buffer.getValue(), wrapped.getValue()); wrapped.setValue("change2"); assertEquals( "Buffer value equals the wrapped value after a commit.", buffer.getValue(), wrapped.getValue()); wrapped.setValue(buffer.getValue()); assertEquals( "Buffer value equals the wrapped value after a commit.", buffer.getValue(), wrapped.getValue()); } /** * Tests that the BufferedValueModel returns the wrapped's values * after a flush. */ @Test public void testReturnsWrappedValueAfterFlush() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); // shall buffer now revert(); assertEquals( "Buffer value equals the wrapped value after a flush.", wrapped.getValue(), buffer.getValue()); wrapped.setValue("change2"); assertEquals( "Buffer value equals the wrapped value after a flush.", wrapped.getValue(), buffer.getValue()); } // Testing Proper Value Commit and Flush ********************************** /** * Tests the core of the buffering feature: buffer modifications * do not affect the wrapped before a commit. */ @Test public void testWrappedValuesUnchangedBeforeCommit() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object oldWrappedValue = wrapped.getValue(); buffer.setValue("changedBuffer1"); assertEquals( "Buffer changes do not change the wrapped value before a commit.", wrapped.getValue(), oldWrappedValue ); buffer.setValue(null); assertEquals( "Buffer changes do not change the wrapped value before a commit.", wrapped.getValue(), oldWrappedValue ); buffer.setValue(oldWrappedValue); assertEquals( "Buffer changes do not change the wrapped value before a commit.", wrapped.getValue(), oldWrappedValue ); buffer.setValue("changedBuffer2"); assertEquals( "Buffer changes do not change the wrapped value before a commit.", wrapped.getValue(), oldWrappedValue ); } /** * Tests the core of a commit: buffer changes are written through on commit * and change the wrapped value. */ @Test public void testCommitChangesWrappedValue() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object oldWrappedValue = wrapped.getValue(); Object newValue1 = "change1"; buffer.setValue(newValue1); assertEquals( "Wrapped value is unchanged before the first commit.", wrapped.getValue(), oldWrappedValue); commit(); assertEquals( "Wrapped value is the new value after the first commit.", wrapped.getValue(), newValue1); // Set the buffer to the current wrapped value to check whether // the starts buffering, even if there's no value difference. Object newValue2 = wrapped.getValue(); buffer.setValue(newValue2); commit(); assertEquals( "Wrapped value is the new value after the second commit.", wrapped.getValue(), newValue2); } /** * Tests the core of a flush action: buffer changes are overridden * by wrapped changes after a flush. */ @Test public void testFlushResetsTheBufferedValue() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object newValue1 = "new value1"; buffer.setValue(newValue1); assertSame( "Buffer value reflects changes before the first flush.", buffer.getValue(), newValue1); revert(); assertEquals( "Buffer value is the wrapped value after the first flush.", buffer.getValue(), wrapped.getValue()); // Set the buffer to the current wrapped value to check whether // the starts buffering, even if there's no value difference. Object newValue2 = wrapped.getValue(); buffer.setValue(newValue2); assertSame( "Buffer value reflects changes before the flush.", buffer.getValue(), newValue2); revert(); assertEquals( "Buffer value is the wrapped value after the second flush.", buffer.getValue(), wrapped.getValue()); } // Tests a Proper Buffering State ***************************************** /** * Tests that a buffer isn't buffering as long as no value has been assigned. */ @Test public void testIsNotBufferingIfNoValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); assertFalse( "Initially the buffer does not buffer.", buffer.isBuffering()); Object newValue = "change1"; wrapped.setValue(newValue); assertFalse( "Wrapped changes do not affect the buffering state.", buffer.isBuffering()); wrapped.setValue(null); assertFalse( "Wrapped change to null does not affect the buffering state.", buffer.isBuffering()); } /** * Tests that the buffer is buffering once a value has been assigned. */ @Test public void testIsBufferingIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); assertTrue( "Setting a value (even the wrapped's value) turns on buffering.", buffer.isBuffering()); buffer.setValue("change2"); assertTrue( "Changing the value doesn't affect the buffering state.", buffer.isBuffering()); buffer.setValue(wrapped.getValue()); assertTrue( "Resetting the value to the wrapped's value should affect buffering.", !buffer.isBuffering()); } /** * Tests that the buffer is not buffering after a commit. */ @Test public void testIsNotBufferingAfterCommit() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); commit(); assertFalse( "The buffer does not buffer after a commit.", buffer.isBuffering()); Object newValue = "change1"; wrapped.setValue(newValue); assertFalse( "The buffer does not buffer after a commit and wrapped change1.", buffer.isBuffering()); wrapped.setValue(null); assertFalse( "The buffer does not buffer after a commit and wrapped change2.", buffer.isBuffering()); } /** * Tests that the buffer is not buffering after a flush. */ @Test public void testIsNotBufferingAfterFlush() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); revert(); assertFalse( "The buffer does not buffer after a flush.", buffer.isBuffering()); Object newValue = "change1"; wrapped.setValue(newValue); assertFalse( "The buffer does not buffer after a flush and wrapped change1.", buffer.isBuffering()); wrapped.setValue(null); assertFalse( "The buffer does not buffer after a flush and wrapped change2.", buffer.isBuffering()); } /** * Tests that changing the buffering state fires changes of * the <i>buffering</i> property. */ @Test public void testFiresBufferingChanges() { BufferedValueModel buffer = createDefaultBufferedValueModel(); TestPropertyChangeListener pcl = new TestPropertyChangeListener(BufferedValueModel.BUFFERING_PROPERTY); buffer.addPropertyChangeListener(BufferedValueModel.BUFFERING_PROPERTY, pcl); assertEquals("Initial state.", 0, pcl.eventCount()); buffer.getValue(); assertEquals("Reading initial value.", 0, pcl.eventCount()); buffer.setCommitTrigger(null); buffer.setCommitTrigger(commitTrigger); assertEquals("After commit trigger change.", 0, pcl.eventCount()); buffer.setValue("now buffering"); assertEquals("After setting the first value.", 1, pcl.eventCount()); buffer.setValue("still buffering"); assertEquals("After setting the second value.", 1, pcl.eventCount()); buffer.getValue(); assertEquals("Reading buffered value.", 1, pcl.eventCount()); wrapped.setValue(buffer.getValue()); assertEquals("Changing wrapped to same as buffer.", 2, pcl.eventCount()); commit(); assertEquals("After committing.", 2, pcl.eventCount()); buffer.getValue(); assertEquals("Reading unbuffered value.", 2, pcl.eventCount()); buffer.setValue("buffering again"); assertEquals("After second buffering switch.", 3, pcl.eventCount()); revert(); assertEquals("After flushing.", 4, pcl.eventCount()); buffer.getValue(); assertEquals("Reading unbuffered value.", 4, pcl.eventCount()); buffer.setValue("before real commit"); assertEquals("With new change to be committed", 5, pcl.eventCount()); commit(); assertEquals("With new change committed", 6, pcl.eventCount()); } @Test public void testSetValueSendsProperValueChangeEvents() { Object obj1 = new Integer(1); Object obj2a = new Integer(2); Object obj2b = new Integer(2); testSetValueSendsProperEvents(null, obj1, true); testSetValueSendsProperEvents(obj1, null, true); testSetValueSendsProperEvents(obj1, obj1, false); testSetValueSendsProperEvents(obj1, obj2a, true); testSetValueSendsProperEvents(obj2a, obj2b, false); testSetValueSendsProperEvents(null, null, false); } @Test public void testValueChangeSendsProperValueChangeEvents() { Object obj1 = new Integer(1); Object obj2a = new Integer(2); Object obj2b = new Integer(2); testValueChangeSendsProperEvents(null, obj1, true); testValueChangeSendsProperEvents(obj1, null, true); testValueChangeSendsProperEvents(obj1, obj1, false); testValueChangeSendsProperEvents(obj1, obj2a, true); testValueChangeSendsProperEvents(obj2a, obj2b, false); testValueChangeSendsProperEvents(null, null, false); } // Commit Trigger Tests ************************************************* /** * Checks that #setCommitTrigger changes the commit trigger. */ @Test public void testCommitTriggerChange() { CommitTrigger trigger1 = new CommitTrigger(); CommitTrigger trigger2 = new CommitTrigger(); BufferedValueModel buffer = new BufferedValueModel(wrapped, trigger1); assertSame( "Commit trigger has been changed.", buffer.getCommitTrigger(), trigger1); buffer.setCommitTrigger(trigger2); assertSame( "Commit trigger has been changed.", buffer.getCommitTrigger(), trigger2); buffer.setCommitTrigger(null); assertSame( "Commit trigger has been changed.", buffer.getCommitTrigger(), null); } /** * Checks and verifies that commit and flush events are driven * by the current commit trigger. */ @Test public void testListensToCurrentCommitTrigger() { CommitTrigger trigger1 = new CommitTrigger(); CommitTrigger trigger2 = new CommitTrigger(); BufferedValueModel buffer = new BufferedValueModel(wrapped, trigger1); buffer.setValue("change1"); Object wrappedValue = wrapped.getValue(); Object bufferedValue = buffer.getValue(); trigger2.commit(); assertEquals( "Changing the unrelated trigger2 to commit has no effect on the wrapped.", wrapped.getValue(), wrappedValue); assertSame( "Changing the unrelated trigger2 to commit has no effect on the buffer.", buffer.getValue(), bufferedValue); trigger2.revert(); assertEquals( "Changing the unrelated trigger2 to revert has no effect on the wrapped.", wrapped.getValue(), wrappedValue); assertSame( "Changing the unrelated trigger2 to revert has no effect on the buffer.", buffer.getValue(), bufferedValue); // Change the commit trigger to trigger2. buffer.setCommitTrigger(trigger2); assertSame( "Commit trigger has been changed.", buffer.getCommitTrigger(), trigger2); trigger1.commit(); assertEquals( "Changing the unrelated trigger1 to commit has no effect on the wrapped.", wrapped.getValue(), wrappedValue); assertSame( "Changing the unrelated trigger1 to commit has no effect on the buffer.", buffer.getValue(), bufferedValue); trigger1.revert(); assertEquals( "Changing the unrelated trigger1 to revert has no effect on the wrapped.", wrapped.getValue(), wrappedValue); assertSame( "Changing the unrelated trigger1 to revert has no effect on the buffer.", buffer.getValue(), bufferedValue); // Commit using trigger2. trigger2.commit(); assertEquals( "Changing the current trigger2 to commit commits the buffered value.", buffer.getValue(), wrapped.getValue()); buffer.setValue("change2"); wrappedValue = wrapped.getValue(); trigger2.revert(); assertEquals( "Changing the current trigger2 to revert flushes the buffered value.", buffer.getValue(), wrapped.getValue()); assertEquals( "Changing the current trigger2 to revert flushes the buffered value.", buffer.getValue(), wrappedValue); } // Tests Proper Update Notifications ************************************** /** * Checks that wrapped changes fire value changes * if no value has been assigned. */ @Test public void testPropagatesWrappedChangesIfNoValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); TestPropertyChangeListener pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY); buffer.addValueChangeListener(pcl); wrapped.setValue("change1"); assertEquals("Value change.", 1, pcl.eventCount()); wrapped.setValue(null); assertEquals("Value change.", 2, pcl.eventCount()); wrapped.setValue("change2"); assertEquals("Value change.", 3, pcl.eventCount()); wrapped.setValue(buffer.getValue()); assertEquals("No value change.", 3, pcl.eventCount()); } /** * Tests that wrapped changes are propagated once a value has * been assigned, i.e. the buffer is buffering. */ @Test public void testIgnoresWrappedChangesIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); TestPropertyChangeListener pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY); buffer.addValueChangeListener(pcl); buffer.setValue("new buffer"); wrapped.setValue("change1"); assertEquals("Value change.", 2, pcl.eventCount()); buffer.setValue("new buffer"); wrapped.setValue(null); assertEquals("Value change.", 4, pcl.eventCount()); buffer.setValue("new buffer"); wrapped.setValue("change2"); assertEquals("Value change.", 6, pcl.eventCount()); buffer.setValue("new buffer"); wrapped.setValue(buffer.getValue()); // won't fire event assertEquals("No value change.", 7, pcl.eventCount()); } /** * Checks and verifies that a commit fires no value change. */ @Test public void testCommitFiresNoChangeOnSameOldAndNewValues() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("value1"); TestPropertyChangeListener pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY); buffer.addValueChangeListener(pcl); assertEquals("No initial change.", 0, pcl.eventCount()); commit(); assertEquals("First commit: no change.", 0, pcl.eventCount()); buffer.setValue("value2"); assertEquals("Setting a value: a change.", 1, pcl.eventCount()); commit(); assertEquals("Second commit: no change.", 1, pcl.eventCount()); } @Test public void testCommitFiresChangeOnDifferentOldAndNewValues() { BufferedValueModel buffer = createDefaultBufferedValueModel( new ToUpperCaseStringHolder()); buffer.setValue("initialValue"); TestPropertyChangeListener pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY); buffer.addValueChangeListener(pcl); buffer.setValue("value1"); assertEquals("One event fired", 1, pcl.eventCount()); assertEquals("First value set.", "value1", pcl.lastEvent().getNewValue()); commit(); assertEquals("Commit fires if the wrapped modifies the value.", 2, pcl.eventCount()); assertEquals("Old value is the buffered value.", "value1", pcl.lastEvent().getOldValue()); assertEquals("New value is the modified value.", "VALUE1", pcl.lastEvent().getNewValue()); } /** * Tests that a flush event fires a value change if and only if * the flushed value does not equal the buffered value. */ @Test public void testFlushFiresTrueValueChanges() { BufferedValueModel buffer = createDefaultBufferedValueModel(); TestPropertyChangeListener pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY); wrapped.setValue("new wrapped"); buffer.setValue("new buffer"); buffer.addValueChangeListener(pcl); revert(); assertEquals("First flush changes value.", 1, pcl.eventCount()); buffer.setValue(wrapped.getValue()); assertEquals("Resetting value: no change.", 1, pcl.eventCount()); revert(); assertEquals("Second flush: no change.", 1, pcl.eventCount()); buffer.setValue("new buffer2"); assertEquals("Second value change.", 2, pcl.eventCount()); wrapped.setValue("new wrapped2"); assertEquals("Setting new wrapped value: no change.", 3, pcl.eventCount()); buffer.setValue(wrapped.getValue()); assertEquals("Third value change.", 3, pcl.eventCount()); revert(); assertEquals("Third flush: no change.", 3, pcl.eventCount()); } // Misc Tests ************************************************************* /** * Tests read actions on a read-only model. */ @Test public void testReadOnly() { TestBean bean = new TestBean(); ValueModel readOnlyModel = new BeanPropertyAccessStrategy(bean).getPropertyValueModel("readOnly"); BufferedValueModel buffer = new BufferedValueModel(readOnlyModel, commitTrigger); assertSame( "Can read values from a read-only model.", buffer.getValue(), readOnlyModel.getValue()); Object newValue1 = "new value"; buffer.setValue(newValue1); assertSame( "Can read values from a read-only model when buffering.", buffer.getValue(), newValue1); revert(); assertSame( "Can read values from a read-only model after a flush.", buffer.getValue(), bean.getReadOnly()); buffer.setValue("new value2"); try { commit(); fail("Cannot commit to a read-only model."); } catch (Exception e) { // The expected behavior } } // Test Implementations *************************************************** private void testSetValueSendsProperEvents(Object oldValue, Object newValue, boolean eventExpected) { BufferedValueModel valueModel = new BufferedValueModel(new ValueHolder(oldValue), new CommitTrigger()); testSendsProperEvents(valueModel, oldValue, newValue, eventExpected); } private void testValueChangeSendsProperEvents(Object oldValue, Object newValue, boolean eventExpected) { BufferedValueModel defaultModel = createDefaultBufferedValueModel(); defaultModel.setValue(oldValue); testSendsProperEvents(defaultModel, oldValue, newValue, eventExpected); } private void testSendsProperEvents(BufferedValueModel valueModel, Object oldValue, Object newValue, boolean eventExpected) { TestPropertyChangeListener pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY); valueModel.addValueChangeListener(pcl); int expectedEventCount = eventExpected ? 1 : 0; valueModel.setValue(newValue); assertEquals( "Expected event count after ( " + oldValue + " -> " + newValue + ").", expectedEventCount, pcl.eventCount()); if (eventExpected) { assertEquals("Event's old value.", oldValue, pcl.lastEvent().getOldValue()); assertEquals("Event's new value.", newValue, pcl.lastEvent().getNewValue()); } } // Helper Code ************************************************************ private void commit() { commitTrigger.commit(); } private void revert() { commitTrigger.revert(); } private BufferedValueModel createDefaultBufferedValueModel() { wrapped.setValue(RESET_VALUE); return new BufferedValueModel(wrapped, commitTrigger); } private BufferedValueModel createDefaultBufferedValueModel(ValueModel wrapped) { wrapped.setValue(RESET_VALUE); return new BufferedValueModel(wrapped, commitTrigger); } // A String typed ValueModel that modifies set values to uppercase. private static class ToUpperCaseStringHolder extends AbstractValueModel { private String text; public Object getValue() { return text; } public void setValue(Object newValue) { String newText = ((String) newValue).toUpperCase(); Object oldText = text; text = newText; fireValueChange(oldText, newText); } } /** * This class is used to test alternate value change detection methods. */ private static class StrictEquivalenceValueChangeDetector implements ValueChangeDetector { public boolean hasValueChanged(Object oldValue, Object newValue) { return oldValue != newValue; } } }