diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/ViewModel.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/ViewModel.java
index cca35ce63..97071d741 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/ViewModel.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/ViewModel.java
@@ -15,6 +15,7 @@
******************************************************************************/
package de.saxsys.mvvmfx;
+import de.saxsys.mvvmfx.utils.notifications.NotificationTestHelper;
import javafx.application.Platform;
import de.saxsys.mvvmfx.internal.viewloader.View;
import de.saxsys.mvvmfx.utils.notifications.NotificationCenter;
@@ -45,8 +46,17 @@
public interface ViewModel {
/**
- * Publishes a notification to the subscribers of the notificationId. This notification will be send to the
- * UI-Thread.
+ * Publishes a notification to the subscribers of the messageName. This notification will be send to the
+ * UI-Thread (if the UI-toolkit was bootstrapped). If no UI-Toolkit is available the notification will be directly
+ * published. This is typically the case in unit tests.
+ *
+ *
+ * This notification mechanism uses the {@link NotificationCenter} internally with the difference that messages send
+ * by this method aren't globally available. Instead they can only be received by this viewModels {@link #subscribe(String, NotificationObserver)}
+ * method or when using this viewModel instance as argument to the {@link NotificationCenter#subscribe(ViewModel, String, NotificationObserver)} method.
+ *
+ *
+ * See {@link NotificationTestHelper} for a utility that's purpose is to simplify unit tests with notifications.
*
* @param messageName
* of the notification
@@ -57,7 +67,18 @@ default void publish(String messageName, Object... payload) {
if (Platform.isFxApplicationThread()) {
MvvmFX.getNotificationCenter().publish(this, messageName, payload);
} else {
- Platform.runLater(() -> MvvmFX.getNotificationCenter().publish(this, messageName, payload));
+ try {
+ Platform.runLater(() -> MvvmFX.getNotificationCenter().publish(this, messageName, payload));
+ } catch(IllegalStateException e) {
+
+ // If the toolkit isn't initialized yet we will publish the notification directly.
+ // In most cases this means that we are in a unit test and not JavaFX application is running.
+ if(e.getMessage().equals("Toolkit not initialized")) {
+ MvvmFX.getNotificationCenter().publish(this, messageName, payload);
+ } else {
+ throw e;
+ }
+ }
}
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationCenter.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationCenter.java
index 4724860b3..3d50d0509 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationCenter.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationCenter.java
@@ -24,6 +24,11 @@
*
* {@code Object[]} with a notification.
*
+ *
+ *
+ * There is a util {@link NotificationTestHelper} that's purpose is to simplify testing of notifications in Unit-Tests.
+ *
+ *
* @author sialcasa
*
*/
@@ -99,7 +104,7 @@ void unsubscribe(String messageName,
* @param observer
* which should execute when the notification occurs
*/
- void subscribe(ViewModel view, String messageName,
+ void subscribe(ViewModel viewModel, String messageName,
NotificationObserver observer);
/**
@@ -118,7 +123,7 @@ void unsubscribe(ViewModel viewModel, String messageName,
* @param viewModel
* @param observer
*/
- void unsubscribe(ViewModel view,
+ void unsubscribe(ViewModel viewModel,
NotificationObserver observer);
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationObserver.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationObserver.java
index 6aa4963ac..a800c1e9e 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationObserver.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationObserver.java
@@ -31,5 +31,5 @@ public interface NotificationObserver {
* @param payload
* which are passed
*/
- public void receivedNotification(String key, Object... payload);
+ void receivedNotification(String key, Object... payload);
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationTestHelper.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationTestHelper.java
new file mode 100644
index 000000000..513274ebd
--- /dev/null
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/NotificationTestHelper.java
@@ -0,0 +1,138 @@
+package de.saxsys.mvvmfx.utils.notifications;
+
+import de.saxsys.mvvmfx.ViewModel;
+import javafx.application.Platform;
+import javafx.embed.swing.JFXPanel;
+import javafx.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * The {@link NotificationTestHelper} is used to simplify the testing of
+ * notifications. It is especially useful when notifications are send from
+ * different thread and when testing the direct notification between a viewModel and the View
+ * (via {@link ViewModel#publish(String, Object...)} and {@link ViewModel#subscribe(String, NotificationObserver)})
+ *
+ * This class implements {@link NotificationObserver} and therefore can be added as subscriber. It will record
+ * every received notification and can be tested afterwards.
+ *
+ *
+ * The {@link ViewModel#publish(String, Object...)} method will send all notifications on the JavaFX UI thread.
+ * Therefore when testing the publishing of notifications JavaFX has to be running which isn't the case
+ * with plain JUnit tests. The {@link NotificationTestHelper} will take care for thread handling.
+ *
+ *
+ * Example:
+ *
+ *
+ *
+ *
+ * public class MyViewModel implements ViewModel {
+ * public static final String ACTION_KEY = "my-action";
+ *
+ * public void someAction() {
+ * ...
+ * publish(ACTION_KEY);
+ * }
+ * }
+ *
+ * // unit test
+ * {@code @Test}
+ * public void testSomething() {
+ * MyViewModel viewModel = new MyViewModel();
+ *
+ * NotificationTestHelper helper = new NotificationTestHelper();
+ * viewModel.subscribe(MyViewModel.ACTION_KEY, helper);
+ *
+ *
+ * viewModel.someAction();
+ *
+ * assertEquals(1, helper.numberOfReceivedNotifications());
+ * }
+ *
+ *
+ *
+ *
+ * You can provide a timeout as constructor parameter.
+ * This is useful in case of asynchronous code (f.e. when notifications are send from another Thread).
+ *
+ * By default the timeout is set to {@value #DEFAULT_TIMEOUT}. When you have a long running thread
+ * you should use a higher timeout.
+ *
+ * @author manuel.mauky
+ */
+public class NotificationTestHelper implements NotificationObserver {
+
+ public static final long DEFAULT_TIMEOUT = 0l;
+
+ private List> notifications = new ArrayList<>();
+
+ private long timeout = DEFAULT_TIMEOUT;
+
+ /**
+ * Create a test helper with a default timeout of {@value #DEFAULT_TIMEOUT} millis.
+ */
+ public NotificationTestHelper() {
+ new JFXPanel();
+ }
+
+ /**
+ * Create a test helper with the given timeout in millis.
+ *
+ * @param timeoutInMillis the timeout.
+ */
+ public NotificationTestHelper(long timeoutInMillis) {
+ this();
+ this.timeout = timeoutInMillis;
+ }
+
+ @Override
+ public void receivedNotification(String key, Object... payload) {
+ notifications.add(new Pair<>(key, payload));
+ }
+
+ /**
+ * @return the number of received notifications.
+ */
+ public int numberOfReceivedNotifications() {
+ waitForUiThread();
+ return notifications.size();
+ }
+
+ /**
+ * @param key the key of the notification.
+ * @return the number of received notifications for the given key.
+ */
+ public int numberOfReceivedNotifications(String key) {
+ waitForUiThread();
+ return (int) notifications.stream()
+ .filter(pair -> pair.getKey().equals(key))
+ .count();
+ }
+
+ private void waitForUiThread() {
+ CompletableFuture future = new CompletableFuture<>();
+
+ Platform.runLater(() -> {
+ if(timeout > 0) {
+ try {
+ Thread.sleep(timeout);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ future.complete(null);
+ });
+
+ try {
+ future.get(timeout+50, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/CompositeValidator.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/CompositeValidator.java
index 19ac80c15..d472ed967 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/CompositeValidator.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/CompositeValidator.java
@@ -13,7 +13,7 @@
*/
public class CompositeValidator implements Validator {
- private CompositeValidationResult result = new CompositeValidationResult();
+ private CompositeValidationResult validationStatus = new CompositeValidationResult();
public CompositeValidator() {
}
@@ -24,19 +24,19 @@ public CompositeValidator(Validator... validators) {
public void addValidators(Validator... validators) {
- result.addResults(Stream.of(validators)
+ validationStatus.addResults(Stream.of(validators)
.map(Validator::getValidationStatus)
.collect(Collectors.toList()));
}
public void removeValidators(Validator... validators) {
- result.removeResults(Stream.of(validators)
+ validationStatus.removeResults(Stream.of(validators)
.map(Validator::getValidationStatus)
.collect(Collectors.toList()));
}
@Override
public ValidationStatus getValidationStatus() {
- return result;
+ return validationStatus;
}
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/FunctionBasedValidator.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/FunctionBasedValidator.java
index a0f640124..c1430adbf 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/FunctionBasedValidator.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/FunctionBasedValidator.java
@@ -33,7 +33,7 @@
*/
public class FunctionBasedValidator implements Validator {
- private ValidationStatus result = new ValidationStatus();
+ private ValidationStatus validationStatus = new ValidationStatus();
private Function> validateFunction;
@@ -83,13 +83,13 @@ public FunctionBasedValidator(ObservableValue source, Predicate predicate,
}
private void validate(T newValue) {
- result.clearMessages();
+ validationStatus.clearMessages();
Optional message = validateFunction.apply(newValue);
- message.ifPresent(result::addMessage);
+ message.ifPresent(validationStatus::addMessage);
}
@Override
public ValidationStatus getValidationStatus() {
- return result;
+ return validationStatus;
}
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ObservableRuleBasedValidator.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ObservableRuleBasedValidator.java
index 291ba6ea2..20c087274 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ObservableRuleBasedValidator.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ObservableRuleBasedValidator.java
@@ -30,7 +30,7 @@ public class ObservableRuleBasedValidator implements Validator {
private List> rules = new ArrayList<>();
- private ValidationStatus result = new ValidationStatus();
+ private ValidationStatus validationStatus = new ValidationStatus();
/**
* Add a rule for this validator.
@@ -58,15 +58,15 @@ public void addRule(ObservableValue rule, ValidationMessage message) {
private void validateRule(boolean isValid, ValidationMessage message) {
if (isValid) {
- result.removeMessage(message);
+ validationStatus.removeMessage(message);
} else {
- result.addMessage(message);
+ validationStatus.addMessage(message);
}
}
@Override
public ValidationStatus getValidationStatus() {
- return result;
+ return validationStatus;
}
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ValidationStatus.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ValidationStatus.java
index e5161c836..016a196a3 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ValidationStatus.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ValidationStatus.java
@@ -82,12 +82,18 @@ public boolean isValid() {
* @return an Optional containing the ValidationMessage or an empty Optional.
*/
public Optional getHighestMessage() {
- if (!errorMessages.isEmpty()) {
- return Optional.of(errorMessages.get(0));
- } else if (!warningMessages.isEmpty()) {
- return Optional.of(warningMessages.get(0));
+ final Optional error = messages.stream()
+ .filter(message -> message.getSeverity().equals(Severity.ERROR))
+ .findFirst();
+
+ if (error.isPresent()) {
+ return error;
} else {
- return Optional.empty();
+ final Optional warning = messages.stream()
+ .filter(message -> message.getSeverity().equals(Severity.WARNING))
+ .findFirst();
+
+ return warning;
}
}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/CompositeCommandTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/CompositeCommandTest.java
index 56e4ba9a0..ad9d7b103 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/CompositeCommandTest.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/CompositeCommandTest.java
@@ -189,57 +189,43 @@ protected void action() throws Exception {
CompositeCommand compositeCommand = new CompositeCommand(delegateCommand1, delegateCommand2, delegateCommand3);
- compositeCommand.progressProperty().addListener(new ChangeListener() {
-
- @Override
- public void changed(ObservableValue extends Number> observable, Number oldValue, Number newValue) {
- }
- });
-
GCVerifier.forceGC();
- compositeCommand.runningProperty().addListener(new ChangeListener() {
- @Override
- public void changed(ObservableValue extends Boolean> observable, Boolean oldValue, Boolean newValue) {
- if (newValue && !oldValue) {
- Platform.runLater(new Runnable() {
- @Override
- public void run() {
- assertTrue(compositeCommand.runningProperty().get());
- assertTrue(delegateCommand1.runningProperty().get());
- assertTrue(delegateCommand2.runningProperty().get());
- assertTrue(delegateCommand3.runningProperty().get());
- assertFalse(compositeCommand.notRunningProperty().get());
- assertFalse(delegateCommand1.notRunningProperty().get());
- assertFalse(delegateCommand2.notRunningProperty().get());
- assertFalse(delegateCommand3.notRunningProperty().get());
- commandCompleted.complete(null);
- }
- });
- }
- if (oldValue && !newValue) {
- Platform.runLater(new Runnable() {
- @Override
- public void run() {
- assertFalse(compositeCommand.runningProperty().get());
- assertFalse(delegateCommand1.runningProperty().get());
- assertFalse(delegateCommand2.runningProperty().get());
- assertFalse(delegateCommand3.runningProperty().get());
- assertTrue(compositeCommand.notRunningProperty().get());
- assertTrue(delegateCommand1.notRunningProperty().get());
- assertTrue(delegateCommand2.notRunningProperty().get());
- assertTrue(delegateCommand3.notRunningProperty().get());
- commandStarted.complete(null);
- }
- });
- }
+ compositeCommand.runningProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue && !oldValue) { // command is now running, wasn't running before
+ Platform.runLater(() -> {
+ assertTrue(compositeCommand.runningProperty().get());
+ assertTrue(delegateCommand1.runningProperty().get());
+ assertTrue(delegateCommand2.runningProperty().get());
+ assertTrue(delegateCommand3.runningProperty().get());
+ assertFalse(compositeCommand.notRunningProperty().get());
+ assertFalse(delegateCommand1.notRunningProperty().get());
+ assertFalse(delegateCommand2.notRunningProperty().get());
+ assertFalse(delegateCommand3.notRunningProperty().get());
+
+ commandStarted.complete(null);
+ });
+ }
+ if (oldValue && !newValue) { // command was running, isn't running now
+ Platform.runLater(() -> {
+ assertFalse(compositeCommand.runningProperty().get());
+ assertFalse(delegateCommand1.runningProperty().get());
+ assertFalse(delegateCommand2.runningProperty().get());
+ assertFalse(delegateCommand3.runningProperty().get());
+ assertTrue(compositeCommand.notRunningProperty().get());
+ assertTrue(delegateCommand1.notRunningProperty().get());
+ assertTrue(delegateCommand2.notRunningProperty().get());
+ assertTrue(delegateCommand3.notRunningProperty().get());
+
+ commandCompleted.complete(null);
+ });
}
});
compositeCommand.execute();
- commandStarted.get(3, TimeUnit.SECONDS);
- future.get(3, TimeUnit.SECONDS);
- commandCompleted.get(4, TimeUnit.SECONDS);
+ commandStarted.get(5, TimeUnit.SECONDS);
+ future.get(5, TimeUnit.SECONDS);
+ commandCompleted.get(5, TimeUnit.SECONDS);
}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenterTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenterTest.java
index c0c72cc63..1b1afe823 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenterTest.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenterTest.java
@@ -87,22 +87,22 @@ public void addAndRemoveObserverForNameToDefaultNotificationCenterAndPostNotific
defaultCenter.publish(TEST_NOTIFICATION);
Mockito.verify(observer1, Mockito.never()).receivedNotification(TEST_NOTIFICATION);
}
-
+
@Test
public void subscribeSameObserverMultipleTimes() {
defaultCenter.subscribe(TEST_NOTIFICATION, observer1);
defaultCenter.subscribe(TEST_NOTIFICATION, observer1);
-
+
defaultCenter.publish(TEST_NOTIFICATION);
Mockito.verify(observer1, Mockito.times(2)).receivedNotification(TEST_NOTIFICATION);
}
-
+
@Test
public void unsubscribeObserverThatWasSubscribedMultipleTimes() {
defaultCenter.subscribe(TEST_NOTIFICATION, observer1);
defaultCenter.subscribe(TEST_NOTIFICATION, observer1);
defaultCenter.subscribe(TEST_NOTIFICATION, observer1);
-
+
defaultCenter.unsubscribe(observer1);
defaultCenter.publish(TEST_NOTIFICATION);
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/NotificationTestHelperTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/NotificationTestHelperTest.java
new file mode 100644
index 000000000..a2f65dc5e
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/NotificationTestHelperTest.java
@@ -0,0 +1,102 @@
+package de.saxsys.mvvmfx.utils.notifications;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import de.saxsys.mvvmfx.ViewModel;
+
+/**
+ * @author manuel.mauky
+ */
+public class NotificationTestHelperTest {
+
+ public class MyViewModel implements ViewModel {
+ }
+
+ private MyViewModel viewModel;
+
+ @Before
+ public void setUp() throws Exception {
+ viewModel = new MyViewModel();
+ }
+
+ @Test
+ public void singlePublish() {
+ NotificationTestHelper helper = new NotificationTestHelper();
+ viewModel.subscribe("test", helper);
+
+ viewModel.publish("test");
+
+ assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
+ }
+
+ @Test
+ public void multiplePublish() {
+ NotificationTestHelper helper = new NotificationTestHelper();
+ viewModel.subscribe("test", helper);
+
+ int n = 10;
+
+ for (int i = 0; i < n; i++) {
+ viewModel.publish("test");
+ }
+
+ assertThat(helper.numberOfReceivedNotifications()).isEqualTo(n);
+ }
+
+ @Test
+ public void globalNotificationCenter() {
+ NotificationTestHelper helper = new NotificationTestHelper();
+
+ NotificationCenter notificationCenter = new DefaultNotificationCenter();
+
+ notificationCenter.subscribe("OK", helper);
+
+
+ notificationCenter.publish("OK");
+
+
+ assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
+ }
+
+ @Test
+ public void publishOnOtherThread() {
+ NotificationTestHelper helper = new NotificationTestHelper(50l);
+
+ NotificationCenter notificationCenter = new DefaultNotificationCenter();
+
+ notificationCenter.subscribe("OK", helper);
+
+
+ Runnable r = () -> notificationCenter.publish("OK");
+
+ new Thread(r).start();
+
+ assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
+ }
+
+ @Test
+ public void timeout() {
+ NotificationTestHelper helper = new NotificationTestHelper(300l);
+
+ NotificationCenter notificationCenter = new DefaultNotificationCenter();
+
+ notificationCenter.subscribe("OK", helper);
+
+
+ Runnable r = () -> {
+ try {
+ Thread.sleep(100l);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ notificationCenter.publish("OK");
+ };
+
+ new Thread(r).start();
+
+ assertThat(helper.numberOfReceivedNotifications()).isEqualTo(1);
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/ViewModelTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/ViewModelTest.java
index 117a143ab..b6ea747a9 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/ViewModelTest.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/ViewModelTest.java
@@ -41,7 +41,15 @@ public void init() {
@Test
public void observerIsCalledFromUiThread() throws InterruptedException, ExecutionException, TimeoutException {
-
+ // Check that there is a UI-Thread available. This JUnit-Test isn't running on the UI-Thread but there needs to
+ // be a UI-Thread available in the background.
+ CompletableFuture uiThreadIsAvailable = new CompletableFuture<>();
+ Platform.runLater(() -> uiThreadIsAvailable.complete(null)); // This would throw an IllegalStateException if no
+ // UI-Thread is available.
+ uiThreadIsAvailable.get(1l, TimeUnit.SECONDS);
+
+
+
CompletableFuture future = new CompletableFuture<>();
// The test doesn't run on the FX thread.
@@ -49,9 +57,9 @@ public void observerIsCalledFromUiThread() throws InterruptedException, Executio
viewModel.subscribe(TEST_NOTIFICATION, (key, payload) -> {
// the notification is executed on the FX thread.
- future.complete(Platform.isFxApplicationThread());
- });
-
+ future.complete(Platform.isFxApplicationThread());
+ });
+
viewModel.publish(TEST_NOTIFICATION);
@@ -59,22 +67,22 @@ public void observerIsCalledFromUiThread() throws InterruptedException, Executio
assertThat(wasCalledOnUiThread).isTrue();
}
-
-
+
+
@Test
public void observerFromOutsideDoesNotReceiveNotifications() {
MvvmFX.getNotificationCenter().subscribe(TEST_NOTIFICATION, observer1);
viewModel.publish(TEST_NOTIFICATION);
-
+
waitForUiThread();
- Mockito.verify(observer1, Mockito.never()).receivedNotification(TEST_NOTIFICATION);
+ Mockito.verify(observer1, Mockito.never()).receivedNotification(TEST_NOTIFICATION);
}
@Test
public void addObserverAndPublish() throws Exception {
viewModel.subscribe(TEST_NOTIFICATION, observer1);
viewModel.publish(TEST_NOTIFICATION, OBJECT_ARRAY_FOR_NOTIFICATION);
-
+
waitForUiThread();
Mockito.verify(observer1).receivedNotification(TEST_NOTIFICATION, OBJECT_ARRAY_FOR_NOTIFICATION);
}
@@ -84,14 +92,14 @@ public void addAndRemoveObserverAndPublish() throws Exception {
viewModel.subscribe(TEST_NOTIFICATION, observer1);
viewModel.unsubscribe(observer1);
viewModel.publish(TEST_NOTIFICATION);
-
+
waitForUiThread();
Mockito.verify(observer1, Mockito.never()).receivedNotification(TEST_NOTIFICATION);
viewModel.subscribe(TEST_NOTIFICATION, observer1);
viewModel.unsubscribe(TEST_NOTIFICATION, observer1);
viewModel.publish(TEST_NOTIFICATION);
-
+
waitForUiThread();
Mockito.verify(observer1, Mockito.never()).receivedNotification(TEST_NOTIFICATION);
}
@@ -102,7 +110,7 @@ public void addMultipleObserverAndPublish() throws Exception {
viewModel.subscribe(TEST_NOTIFICATION, observer2);
viewModel.subscribe(TEST_NOTIFICATION, observer3);
viewModel.publish(TEST_NOTIFICATION, OBJECT_ARRAY_FOR_NOTIFICATION);
-
+
waitForUiThread();
Mockito.verify(observer1).receivedNotification(TEST_NOTIFICATION, OBJECT_ARRAY_FOR_NOTIFICATION);
@@ -118,7 +126,7 @@ public void addMultipleObserverAndRemoveOneAndPublish() throws Exception {
viewModel.subscribe(TEST_NOTIFICATION, observer3);
viewModel.unsubscribe(observer1);
viewModel.publish(TEST_NOTIFICATION, OBJECT_ARRAY_FOR_NOTIFICATION);
-
+
waitForUiThread();
Mockito.verify(observer1, Mockito.never()).receivedNotification(TEST_NOTIFICATION,
@@ -128,9 +136,10 @@ public void addMultipleObserverAndRemoveOneAndPublish() throws Exception {
Mockito.verify(observer3).receivedNotification(TEST_NOTIFICATION,
OBJECT_ARRAY_FOR_NOTIFICATION);
}
-
+
/**
- * This method is used to wait until the UI thread has done all work that was queued via {@link Platform#runLater(Runnable)}.
+ * This method is used to wait until the UI thread has done all work that was queued via
+ * {@link Platform#runLater(Runnable)}.
*/
private void waitForUiThread() {
CompletableFuture future = new CompletableFuture<>();
@@ -141,7 +150,7 @@ private void waitForUiThread() {
throw new IllegalStateException(e);
}
}
-
+
private class DummyNotificationObserver implements NotificationObserver {
@Override
public void receivedNotification(String key, Object... payload) {
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/ViewModelWithoutUiThreadTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/ViewModelWithoutUiThreadTest.java
new file mode 100644
index 000000000..adbdf4a18
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/notifications/ViewModelWithoutUiThreadTest.java
@@ -0,0 +1,39 @@
+package de.saxsys.mvvmfx.utils.notifications;
+
+import de.saxsys.mvvmfx.ViewModel;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This test verifies the behaviour of the publish/subscribe mechanism of ViewModels when no JavaFX thread is running.
+ * In this case the publish/subscribe should still be working.
+ *
+ * @author manuel.mauky
+ */
+public class ViewModelWithoutUiThreadTest {
+
+ public static class MyViewModel implements ViewModel {
+ }
+
+
+ @Test
+ public void test() throws InterruptedException, ExecutionException, TimeoutException {
+
+ MyViewModel viewModel = new MyViewModel();
+
+ CompletableFuture future = new CompletableFuture<>();
+
+ viewModel.subscribe("test", (key, payload) -> {
+ future.complete(null);
+ });
+
+
+ viewModel.publish("test");
+
+ future.get(1l, TimeUnit.SECONDS);
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/validation/HighestMessageBugTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/validation/HighestMessageBugTest.java
new file mode 100644
index 000000000..61bfa184e
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/validation/HighestMessageBugTest.java
@@ -0,0 +1,73 @@
+package de.saxsys.mvvmfx.utils.validation;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.collections.ListChangeListener;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test case reproduces the bug #264
+ *
+ * @author manuel.mauky
+ */
+public class HighestMessageBugTest {
+
+
+ private StringProperty value;
+ private Validator validator;
+ private ValidationStatus validationStatus;
+
+
+ @Before
+ public void setUp() throws Exception {
+ value = new SimpleStringProperty("");
+ validator = new FunctionBasedValidator<>(value, v -> v != null, ValidationMessage.error("error"));
+
+
+ validationStatus = validator.getValidationStatus();
+ }
+
+ @Test
+ public void testValidProperty() throws Exception {
+ assertThat(validationStatus.getHighestMessage().isPresent()).isFalse();
+
+ CompletableFuture future = new CompletableFuture<>();
+
+ validationStatus.validProperty().addListener((observable, oldValue, newValue) -> {
+ assertThat(validationStatus.getHighestMessage().isPresent()).isTrue();
+
+ future.complete(null);
+ });
+
+
+ value.set(null);
+
+ future.get(1l, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void testMessagesList() throws Exception {
+ assertThat(validationStatus.getHighestMessage().isPresent()).isFalse();
+
+ CompletableFuture future = new CompletableFuture<>();
+
+ validationStatus.getMessages().addListener((ListChangeListener) c -> {
+ assertThat(validationStatus.getHighestMessage().isPresent()).isTrue();
+
+ future.complete(null);
+ });
+
+
+ value.set(null);
+
+ future.get(1l, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/validation/crossfieldexample/RegisterFormView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/validation/crossfieldexample/RegisterFormView.java
index 3dac00b55..f1896fac6 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/validation/crossfieldexample/RegisterFormView.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/validation/crossfieldexample/RegisterFormView.java
@@ -44,9 +44,9 @@ public void initialize() {
}
private void updateMessage() {
- Platform.runLater(() -> message.setText(
+ message.setText(
viewModel.getValidation().getHighestMessage().map(ValidationMessage::getMessage)
- .orElse("everythink ok")));
+ .orElse("everythink ok"));
}
public void ok() {
diff --git a/pom.xml b/pom.xml
index f68de8a9e..a87170cdd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
de.saxsys
mvvmfx-parent
pom
- 1.3.0
+ 1.3.1
mvvmFX parent
Application Framework for MVVM with JavaFX.
http://www.saxsys.de
@@ -87,6 +87,38 @@
+
+ de.saxsys
+ mvvmfx
+ ${project.version}
+
+
+ de.saxsys
+ mvvmfx-cdi
+ ${project.version}
+
+
+ de.saxsys
+ mvvmfx-guice
+ ${project.version}
+
+
+ de.saxsys
+ mvvmfx-archetype
+ ${project.version}
+
+
+ de.saxsys
+ mvvmfx-utils
+ ${project.version}
+
+
+ de.saxsys
+ mvvmfx-testing-utils
+ ${project.version}
+ test
+
+
org.slf4j
slf4j-api