package org.codefx.libfx.nesting.listener;
import static org.codefx.libfx.nesting.testhelper.NestingAccess.getNestingObservable;
import static org.codefx.libfx.nesting.testhelper.NestingAccess.setNestingObservable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import org.codefx.libfx.listener.handle.CreateListenerHandle;
import org.codefx.libfx.nesting.Nesting;
import org.codefx.libfx.nesting.testhelper.NestingAccess;
import org.junit.Before;
import org.junit.Test;
/**
* Abstract superclass to tests of {@link NestedChangeListenerHandle}.
*/
public abstract class AbstractNestedChangeListenerHandleTest {
// #begin INSTANCES USED FOR TESTING
/**
* The nesting's inner observable.
*/
private StringProperty innerObservable;
/**
* The nesting to which the listener is added.
*/
private NestingAccess.EditableNesting<StringProperty> nesting;
/**
* The default listener. This {@link ChangeListener} will be mocked to verify invocations.
*/
private ChangeListener<String> listener;
/**
* A listener which fails the test when being called.
*/
private ChangeListener<String> listenerWhichFailsWhenCalled;
/**
* The tested nested listener, which adds the {@link #listener} to the {@link #nesting}.
*/
private NestedChangeListenerHandle<String> nestedListenerHandle;
//#end INSTANCES USED FOR TESTING
// #begin SETUP
/**
* Creates a new instance of {@link #nesting} and {@link #listener}.
* <p>
* Note: A {@link #nestedListenerHandle} has to be created in the test according to the desired initial state
* (attached or detached).
*/
@Before
@SuppressWarnings("unchecked")
public void setUp() {
innerObservable = new SimpleStringProperty("initial value");
nesting = NestingAccess.EditableNesting.createWithInnerObservable(innerObservable);
listener = mock(ChangeListener.class);
listenerWhichFailsWhenCalled = (obs, oldValue, newValue) -> fail();
}
/**
* Creates a new, initially attached nested listener from the specified nesting and listener.
*
* @param <T>
* the value wrapped by the nesting's inner observable, which is also the type observed by the change
* listener
* @param nesting
* the {@link Nesting} to which the listener will be added
* @param listener
* the {@link ChangeListener} which will be added to the nesting
* @return a new {@link NestedChangeListenerHandle}
*/
private <T> NestedChangeListenerHandle<T> createAttachedNestedListenerHandle(
Nesting<? extends ObservableValue<T>> nesting, ChangeListener<T> listener) {
return createNestedListenerHandle(nesting, listener, CreateListenerHandle.ATTACHED);
}
/**
* Creates a new, initially detached nested listener from the specified nesting and listener.
*
* @param <T>
* the value wrapped by the nesting's inner observable, which is also the type observed by the change
* listener
* @param nesting
* the {@link Nesting} to which the listener will be added
* @param listener
* the {@link ChangeListener} which will be added to the nesting
* @return a new {@link NestedChangeListenerHandle}
*/
private <T> NestedChangeListenerHandle<T> createDetachedNestedListenerHandle(
Nesting<? extends ObservableValue<T>> nesting, ChangeListener<T> listener) {
return createNestedListenerHandle(nesting, listener, CreateListenerHandle.DETACHED);
}
/**
* Creates a new nested listener from the specified nesting and listener.
*
* @param <T>
* the value wrapped by the nesting's inner observable, which is also the type observed by the change
* listener
* @param nesting
* the {@link Nesting} to which the listener will be added
* @param listener
* the {@link ChangeListener} which will be added to the nesting
* @param attachedOrDetached
* indicates whether the listener will be initially attached or detached
* @return a new {@link NestedChangeListenerHandle}
*/
protected abstract <T> NestedChangeListenerHandle<T> createNestedListenerHandle(
Nesting<? extends ObservableValue<T>> nesting,
ChangeListener<T> listener,
CreateListenerHandle attachedOrDetached);
//end SETUP
// #begin TESTS
// construction
/**
* Tests whether the properties the tested nested listener owns have the correct bean.
*/
@Test
public void testPropertyBean() {
nestedListenerHandle = createDetachedNestedListenerHandle(nesting, listener);
assertSame(nestedListenerHandle, nestedListenerHandle.innerObservablePresentProperty().getBean());
}
/**
* Tests whether the {@link #nestedListenerHandle} correctly reports whether the inner observable is present.
*/
@Test
public void testObservablePresentAfterConstruction() {
nestedListenerHandle = createDetachedNestedListenerHandle(nesting, listener);
assertTrue(nestedListenerHandle.isInnerObservablePresent());
}
/**
* Tests whether the construction does not call the {@link #listener}.
*/
@Test
public void testNoInteractionWithListenerDuringConstruction() {
nestedListenerHandle = createDetachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
}
// changing value
/**
* Tests whether no listener invocation occurs when the nesting's observable changes its value and the listener is
* initially detached.
*/
@Test
public void testChangingValueWhenInitiallyDetached() {
nestedListenerHandle = createDetachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
innerObservable.set("new value");
}
/**
* Tests whether the listener is correctly invoked when the nesting's observable changes its value.
*/
@Test
public void testChangingValue() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listener);
innerObservable.set("new value");
// assert that 'changed' was called once and with the right arguments
verify(listener, times(1)).changed(innerObservable, "initial value", "new value");
verifyNoMoreInteractions(listener);
}
// changing observable
/**
* Tests whether no listener invocation occurs when the nesting's inner observable is changed.
*/
@Test
public void testChangingObservable() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
StringProperty newObservable = new SimpleStringProperty("new observable's initial value");
setNestingObservable(nesting, newObservable);
assertTrue(nestedListenerHandle.isInnerObservablePresent());
}
/**
* Tests whether no listener invocation occurs when the nesting's inner observable is changed to null.
*/
@Test
public void testChangingObservableToNull() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
setNestingObservable(nesting, null);
assertFalse(nestedListenerHandle.isInnerObservablePresent());
}
// changing observable and value
/**
* Tests whether the listener is correctly invoked when the nesting's new observable gets a new value.
*/
@Test
public void testChangingNewObservablesValue() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listener);
// set a new observable ...
StringProperty newObservable = new SimpleStringProperty("new observable's initial value");
setNestingObservable(nesting, newObservable);
// (assert that setting the observable worked)
assertEquals(newObservable, getNestingObservable(nesting));
// ... and change its value
newObservable.setValue("new observable's new value");
// assert that the listener was invoked once and with the new observable's old and new value
verify(listener, times(1)).changed(newObservable,
"new observable's initial value", "new observable's new value");
verifyNoMoreInteractions(listener);
}
/**
* Tests whether the listener is not invoked when the nesting's old observable gets a new value.
*/
@Test
public void testChangingOldObservablesValue() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
// set a new observable ...
StringProperty newObservable = new SimpleStringProperty("new observable's initial value");
setNestingObservable(nesting, newObservable);
// (assert that setting the observable worked)
assertEquals(newObservable, getNestingObservable(nesting));
// ... and change the old observable's value
innerObservable.setValue("intial observable's new value");
}
// attach & detach
/**
* Tests whether no listener invocation occurs when the nesting's inner observable's value is changed after the
* listener was detached.
*/
@Test
public void testDetach() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
nestedListenerHandle.detach();
innerObservable.set("new value while detached");
}
/**
* Tests whether the listener ignores values after it was detached repeatedly.
*/
@Test
public void testMultipleDetach() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listenerWhichFailsWhenCalled);
nestedListenerHandle.detach();
nestedListenerHandle.detach();
nestedListenerHandle.detach();
innerObservable.set("new value while detached");
}
/**
* Tests whether the listener is correctly invoked when the nesting's observable changes its value after the
* listener was detached and reattached.
*/
@Test
public void testReattach() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listener);
nestedListenerHandle.detach();
nestedListenerHandle.attach();
innerObservable.set("new value");
// assert that 'changed' was called once and with the right arguments
verify(listener, times(1)).changed(innerObservable, "initial value", "new value");
verifyNoMoreInteractions(listener);
}
/**
* Tests whether the listener is only called once even when attached is called repeatedly.
*/
@Test
public void testMultipleAttach() {
nestedListenerHandle = createAttachedNestedListenerHandle(nesting, listener);
nestedListenerHandle.attach();
nestedListenerHandle.attach();
nestedListenerHandle.attach();
innerObservable.set("new value");
// assert that 'changed' was called only once
verify(listener, times(1)).changed(innerObservable, "initial value", "new value");
verifyNoMoreInteractions(listener);
}
//#end TESTS
}