package org.leumasjaffe.observer; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import javax.swing.JFormattedTextField; import javax.swing.JTextField; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class ObservableControllerTest extends SwingTestCase { private static class Value extends Observable.Instance { String str = ""; String get() { return str; } void set(String value) { this.str = value; } } @Spy MockObserverListener signal; @Mock Consumer onEmpty; @Spy Value value; @Spy JTextField component; @Spy JFormattedTextField formatted; @BeforeEach void setUp() { signal.setObserved(value); clearInvocations(signal); } @Test void testThrowsOnNullForAnythingButEmptyHandler() { assertThrows(NullPointerException.class, () -> ObservableController.from((JTextField) null, Value::get, Value::set)); assertThrows(NullPointerException.class, () -> ObservableController.from(null, Value::get, Value::set)); assertThrows(NullPointerException.class, () -> ObservableController.from(component, null, Value::set)); assertThrows(NullPointerException.class, () -> ObservableController.from(component, Value::get, null)); assertThrows(NullPointerException.class, () -> ObservableController.from(null, (str, model) -> false, (comp, model) -> {})); assertThrows(NullPointerException.class, () -> ObservableController.from(component, (BiFunction) null, (comp, model) -> {})); assertThrows(NullPointerException.class, () -> ObservableController.from(component, (str, model) -> false, null)); assertThrows(NullPointerException.class, () -> ObservableController.from((JTextField) null, Value::get, Value::set, null)); assertThrows(NullPointerException.class, () -> ObservableController.from(null, Value::get, Value::set, (String) null)); assertThrows(NullPointerException.class, () -> ObservableController.from(component, null, Value::set, null)); assertThrows(NullPointerException.class, () -> ObservableController.from(component, Value::get, null, null)); assertThrows(NullPointerException.class, () -> ObservableController.from(null, (str, model) -> false, (comp, model) -> {}, null)); assertThrows(NullPointerException.class, () -> ObservableController.from(component, (BiFunction) null, (comp, model) -> {}, null)); assertThrows(NullPointerException.class, () -> ObservableController.from(component, (str, model) -> false, null, null)); assertDoesNotThrow(() -> ObservableController.from(component, Value::get, Value::set)); assertDoesNotThrow(() -> ObservableController.from(component, Value::get, Value::set, null)); assertDoesNotThrow(() -> ObservableController.from(component, (str, model) -> false, (comp, model) -> {})); assertDoesNotThrow(() -> ObservableController.from(component, (str, model) -> false, (comp, model) -> {}, null)); assertDoesNotThrow(() -> ObservableController.from(formatted, Value::get, Value::set)); assertDoesNotThrow(() -> ObservableController.from(formatted, Value::get, Value::set, (String) null)); } @Test void testCanLinkComponentToObjectBeanlike() { final ObservableListener listener = ObservableController.from(component, Value::get, Value::set); listener.setObserved(value); verify(component).setText(""); component.setText("Hello"); waitForSwing(); verify(value).set(eq("Hello")); verify(signal).updateWasSignalled(); } @Test void testWillNotSignalOnNonChangingOperation() { final ObservableListener listener = ObservableController.from(component, Value::get, Value::set); listener.setObserved(value); component.setText(""); waitForSwing(); verify(signal, never()).updateWasSignalled(); verify(value, never()).set(eq("")); } @Test void testWillNotFireUpdateIfModelHasNotChanged() { final ObservableListener listener = ObservableController.from(component, Value::get, Value::set); listener.setObserved(value); ObserverDispatch.notifySubscribers(value); verify(component, times(1)).setText(""); } @Test void testByDefaultWeIgnoreEmptyString() { final ObservableListener listener = ObservableController.from(component, Value::get, Value::set); listener.setObserved(value); component.setText(""); waitForSwing(); verify(value, never()).set(eq("")); verify(signal, never()).updateWasSignalled(); } @Test void testCanProvideEmptyStringHandler() { final ObservableListener listener = ObservableController.from(component, Value::get, Value::set, onEmpty); listener.setObserved(value); component.setText(""); waitForSwing(); verify(onEmpty).accept(same(value)); verify(signal, never()).updateWasSignalled(); } @Test void testIfUpdateModelReturnsFalseThenWeDontSignal() { final ObservableListener listener = ObservableController.from(component, (str, model) -> false, (comp, model) -> {}); listener.setObserved(value); component.setText("Something"); waitForSwing(); verify(signal, never()).updateWasSignalled(); } @Test void testIfUpdateModelReturnsTrueThenWeSignal() { final ObservableListener listener = ObservableController.from(component, (str, model) -> true, (comp, model) -> {}); listener.setObserved(value); component.setText("Something"); waitForSwing(); verify(signal).updateWasSignalled(); } @Test void testIfSignalledThenConsumerIsInvoked() { @SuppressWarnings("unchecked") BiConsumer updateComp = mock(BiConsumer.class); final ObservableListener listener = ObservableController.from(component, (str, model) -> false, updateComp); listener.setObserved(value); ObserverDispatch.notifySubscribers(value); verify(updateComp, times(2)).accept(same(component), same(value)); } @Test void testFormattedAffectValueOverText() { final ObservableListener listener = ObservableController.from( formatted, Value::get, Value::set); listener.setObserved(value); value.set("1"); ObserverDispatch.notifySubscribers(value); verify(formatted).setValue(eq("1")); } @Test void testFormattedManipulatingTextDoesntPropogate() { final ObservableListener listener = ObservableController.from( formatted, Value::get, Value::set); listener.setObserved(value); formatted.setText("1"); waitForSwing(); verify(value, never()).set(eq("1")); } @Test void testFormattedManipulatingValuePropogates() { final ObservableListener listener = ObservableController.from( formatted, Value::get, Value::set); listener.setObserved(value); formatted.setValue("1"); waitForSwing(); verify(value).set(eq("1")); } @Test void testFormattedManipulatingValueDoesNothingWhenEqual() { final ObservableListener listener = ObservableController.from( formatted, Value::get, Value::set); listener.setObserved(value); formatted.setValue(""); waitForSwing(); verify(value, never()).set(any()); } @Test void testFormattedManipulatingModelDoesNothingWhenEqual() { final ObservableListener listener = ObservableController.from( formatted, Value::get, Value::set); listener.setObserved(value); clearInvocations(formatted); ObserverDispatch.notifySubscribers(value); verify(formatted, never()).setValue(any()); } @Test void testFormattedCanProvideEmptyStringHandler() { final ObservableListener listener = ObservableController.from(formatted, Value::get, Value::set, onEmpty); listener.setObserved(value); formatted.setValue(""); waitForSwing(); verify(onEmpty).accept(same(value)); verify(signal, never()).updateWasSignalled(); } @Test void testFormattedCanProvideDefaultValueOnEmpty() { final ObservableListener listener = ObservableController.from(formatted, Value::get, Value::set, ""); listener.setObserved(value); formatted.setValue(""); waitForSwing(); verify(signal, never()).updateWasSignalled(); verify(value).set(eq("")); verify(value, never()).set(eq("")); } }