|
|
@@ -0,0 +1,287 @@
|
|
|
+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 = "<INIT>";
|
|
|
+ String get() { return str; }
|
|
|
+ void set(String value) { this.str = value; }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Spy MockObserverListener signal;
|
|
|
+ @Mock Consumer<Value> 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(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<String, Value, Boolean>) 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((JFormattedTextField) 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<String, Value, Boolean>) 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<JTextField, Value> listener =
|
|
|
+ ObservableController.from(component, Value::get, Value::set);
|
|
|
+
|
|
|
+ listener.setObserved(value);
|
|
|
+
|
|
|
+ verify(component).setText("<INIT>");
|
|
|
+
|
|
|
+ component.setText("Hello");
|
|
|
+ waitForSwing();
|
|
|
+
|
|
|
+ verify(value).set(eq("Hello"));
|
|
|
+ verify(signal).updateWasSignalled();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testWillNotSignalOnNonChangingOperation() {
|
|
|
+ final ObservableListener<JTextField, Value> listener =
|
|
|
+ ObservableController.from(component, Value::get, Value::set);
|
|
|
+
|
|
|
+ listener.setObserved(value);
|
|
|
+
|
|
|
+ component.setText("<INIT>");
|
|
|
+ waitForSwing();
|
|
|
+
|
|
|
+ verify(signal, never()).updateWasSignalled();
|
|
|
+ verify(value, never()).set(eq("<INIT>"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testWillNotFireUpdateIfModelHasNotChanged() {
|
|
|
+ final ObservableListener<JTextField, Value> listener =
|
|
|
+ ObservableController.from(component, Value::get, Value::set);
|
|
|
+
|
|
|
+ listener.setObserved(value);
|
|
|
+
|
|
|
+ ObserverDispatch.notifySubscribers(value);
|
|
|
+
|
|
|
+ verify(component, times(1)).setText("<INIT>");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testByDefaultWeIgnoreEmptyString() {
|
|
|
+ final ObservableListener<JTextField, Value> 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<JTextField, Value> 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<JTextField, Value> 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<JTextField, Value> 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<JTextField, Value> updateComp = mock(BiConsumer.class);
|
|
|
+ final ObservableListener<JTextField, Value> 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<JFormattedTextField, Value> 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<JFormattedTextField, Value> 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<JFormattedTextField, Value> 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<JFormattedTextField, Value> listener =
|
|
|
+ ObservableController.from(formatted, Value::get, Value::set);
|
|
|
+
|
|
|
+ listener.setObserved(value);
|
|
|
+ formatted.setValue("<INIT>");
|
|
|
+ waitForSwing();
|
|
|
+
|
|
|
+ verify(value, never()).set(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void testFormattedManipulatingModelDoesNothingWhenEqual() {
|
|
|
+ final ObservableListener<JFormattedTextField, Value> 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<JFormattedTextField, Value> 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<JFormattedTextField, Value> listener =
|
|
|
+ ObservableController.from(formatted, Value::get, Value::set,
|
|
|
+ "<DEFAULT>");
|
|
|
+
|
|
|
+ listener.setObserved(value);
|
|
|
+
|
|
|
+ formatted.setValue("");
|
|
|
+ waitForSwing();
|
|
|
+
|
|
|
+ verify(signal, never()).updateWasSignalled();
|
|
|
+ verify(value).set(eq("<DEFAULT>"));
|
|
|
+ verify(value, never()).set(eq(""));
|
|
|
+ }
|
|
|
+}
|