) change -> propertyWasChanged());
}
@@ -707,9 +711,23 @@ public void reload() {
calculateDifferenceFlag();
}
}
-
-
-
+
+
+ /**
+ * This method can be used to copy all values of this {@link ModelWrapper} instance
+ * to the model instance provided as argument.
+ * Existing values in the provided model instance will be overwritten.
+ *
+ * This method doesn't change the state of this modelWrapper or the wrapped model instance.
+ *
+ * @param model a non-null instance of a model.
+ */
+ public void copyValuesTo(M model) {
+ Objects.requireNonNull(model);
+ fields.forEach(field -> field.commit(model));
+ }
+
+
private void propertyWasChanged() {
dirtyFlag.set(true);
calculateDifferenceFlag();
@@ -851,13 +869,13 @@ public StringProperty field(StringPropertyAccessor accessor, String defaultVa
* @return The wrapped property instance.
*/
public StringProperty field(String identifier, StringGetter getter, StringSetter setter) {
- return addIdentified(identifier, new BeanPropertyField<>(getter, setter, SimpleStringProperty::new));
+ return addIdentified(identifier, new BeanPropertyField<>(getter, setter, () -> new SimpleStringProperty(null, identifier)));
}
public StringProperty field(String identifier, StringGetter getter, StringSetter setter,
String defaultValue) {
return addIdentified(identifier, new BeanPropertyField<>(getter, setter, defaultValue,
- SimpleStringProperty::new));
+ () -> new SimpleStringProperty(null, identifier)));
}
/**
@@ -875,12 +893,12 @@ public StringProperty field(String identifier, StringGetter getter, StringSet
* @return The wrapped property instance.
*/
public StringProperty field(String identifier, StringPropertyAccessor accessor) {
- return addIdentified(identifier, new FxPropertyField<>(accessor::apply, SimpleStringProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor, () -> new SimpleStringProperty(null, identifier)));
}
public StringProperty field(String identifier, StringPropertyAccessor accessor, String defaultValue) {
return addIdentified(identifier,
- new FxPropertyField<>(accessor::apply, defaultValue, SimpleStringProperty::new));
+ new FxPropertyField<>(accessor, defaultValue, () -> new SimpleStringProperty(null, identifier)));
}
/** Field type Boolean **/
@@ -902,21 +920,21 @@ public BooleanProperty field(BooleanPropertyAccessor accessor, boolean defaul
}
public BooleanProperty field(String identifier, BooleanGetter getter, BooleanSetter setter) {
- return addIdentified(identifier, new BeanPropertyField<>(getter, setter, SimpleBooleanProperty::new));
+ return addIdentified(identifier, new BeanPropertyField<>(getter, setter, () -> new SimpleBooleanProperty(null, identifier)));
}
public BooleanProperty field(String identifier, BooleanGetter getter, BooleanSetter setter,
boolean defaultValue) {
return addIdentified(identifier, new BeanPropertyField<>(getter, setter, defaultValue,
- SimpleBooleanProperty::new));
+ () -> new SimpleBooleanProperty(null, identifier)));
}
public BooleanProperty field(String identifier, BooleanPropertyAccessor accessor) {
- return addIdentified(identifier, new FxPropertyField<>(accessor, SimpleBooleanProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor, () -> new SimpleBooleanProperty(null, identifier)));
}
public BooleanProperty field(String identifier, BooleanPropertyAccessor accessor, boolean defaultValue) {
- return addIdentified(identifier, new FxPropertyField<>(accessor, defaultValue, SimpleBooleanProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor, defaultValue, () -> new SimpleBooleanProperty(null, identifier)));
}
@@ -948,9 +966,10 @@ public DoubleProperty field(DoublePropertyAccessor accessor, double defaultVa
}
public DoubleProperty field(String identifier, DoubleGetter getter, DoubleSetter setter) {
- final ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
- getter::apply, (m, number) -> setter.accept(m, number.doubleValue()),
- SimpleDoubleProperty::new);
+ final ModelWrapper.BeanPropertyField beanPropertyField =
+ new BeanPropertyField<>(
+ getter::apply, (m, number) -> setter.accept(m, number.doubleValue()),
+ () -> new SimpleDoubleProperty(null, identifier));
return addIdentified(identifier, beanPropertyField);
}
@@ -960,17 +979,17 @@ public DoubleProperty field(String identifier, DoubleGetter getter, DoubleSet
ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
getter::apply, (m, number) -> setter.accept(m, number.doubleValue()),
defaultValue,
- SimpleDoubleProperty::new);
+ () -> new SimpleDoubleProperty(null, identifier));
return addIdentified(identifier, beanPropertyField);
}
public DoubleProperty field(String identifier, DoublePropertyAccessor accessor) {
- return addIdentified(identifier, new FxPropertyField<>(accessor::apply, SimpleDoubleProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor::apply, () -> new SimpleDoubleProperty(null, identifier)));
}
public DoubleProperty field(String identifier, DoublePropertyAccessor accessor, double defaultValue) {
return addIdentified(identifier,
- new FxPropertyField<>(accessor::apply, defaultValue, SimpleDoubleProperty::new));
+ new FxPropertyField<>(accessor::apply, defaultValue, () -> new SimpleDoubleProperty(null, identifier)));
}
@@ -1003,7 +1022,7 @@ public FloatProperty field(FloatPropertyAccessor accessor, float defaultValue
public FloatProperty field(String identifier, FloatGetter getter, FloatSetter setter) {
ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
getter::apply, (m, number) -> setter.accept(m, number.floatValue()),
- SimpleFloatProperty::new);
+ () -> new SimpleFloatProperty(null, identifier));
return addIdentified(identifier, beanPropertyField);
}
@@ -1012,17 +1031,17 @@ public FloatProperty field(String identifier, FloatGetter getter, FloatSetter
ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
getter::apply, (m, number) -> setter.accept(m, number.floatValue()),
defaultValue,
- SimpleFloatProperty::new);
+ () -> new SimpleFloatProperty(null, identifier));
return addIdentified(identifier, beanPropertyField);
}
public FloatProperty field(String identifier, FloatPropertyAccessor accessor) {
- return addIdentified(identifier, new FxPropertyField<>(accessor::apply, SimpleFloatProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor::apply, () -> new SimpleFloatProperty(null, identifier)));
}
public FloatProperty field(String identifier, FloatPropertyAccessor accessor, float defaultValue) {
return addIdentified(identifier,
- new FxPropertyField<>(accessor::apply, defaultValue, SimpleFloatProperty::new));
+ new FxPropertyField<>(accessor::apply, defaultValue, () -> new SimpleFloatProperty(null, identifier)));
}
@@ -1056,7 +1075,7 @@ public IntegerProperty field(IntPropertyAccessor accessor, int defaultValue)
public IntegerProperty field(String identifier, IntGetter getter, IntSetter setter) {
ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
getter::apply, (m, number) -> setter.accept(m, number.intValue()),
- SimpleIntegerProperty::new);
+ () -> new SimpleIntegerProperty(null, identifier));
return addIdentified(identifier, beanPropertyField);
}
@@ -1064,18 +1083,18 @@ public IntegerProperty field(String identifier, IntGetter getter, IntSetter.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
getter::apply, (m, number) -> setter.accept(m, number.intValue()),
defaultValue,
- SimpleIntegerProperty::new);
+ () -> new SimpleIntegerProperty(null, identifier));
return addIdentified(identifier, beanPropertyField);
}
public IntegerProperty field(String identifier, IntPropertyAccessor accessor) {
- return addIdentified(identifier, new FxPropertyField<>(accessor::apply, SimpleIntegerProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor::apply, () -> new SimpleIntegerProperty(null, identifier)));
}
public IntegerProperty field(String identifier, IntPropertyAccessor accessor, int defaultValue) {
return addIdentified(identifier, new FxPropertyField<>(accessor::apply, defaultValue,
- SimpleIntegerProperty::new));
+ () -> new SimpleIntegerProperty(null, identifier)));
}
@@ -1109,7 +1128,7 @@ public LongProperty field(LongPropertyAccessor accessor, long defaultValue) {
public LongProperty field(String identifier, LongGetter getter, LongSetter setter) {
ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
getter::apply, (m, number) -> setter.accept(m, number.longValue()),
- SimpleLongProperty::new);
+ () -> new SimpleLongProperty(null, identifier));
return addIdentified(identifier, beanPropertyField);
}
@@ -1117,17 +1136,17 @@ public LongProperty field(String identifier, LongGetter getter, LongSetter
ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>(
getter::apply, (m, number) -> setter.accept(m, number.longValue()),
defaultValue,
- SimpleLongProperty::new);
+ () -> new SimpleLongProperty(null, identifier));
return addIdentified(identifier,
beanPropertyField);
}
public LongProperty field(String identifier, LongPropertyAccessor accessor) {
- return addIdentified(identifier, new FxPropertyField<>(accessor::apply, SimpleLongProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor::apply, () -> new SimpleLongProperty(null, identifier)));
}
public LongProperty field(String identifier, LongPropertyAccessor accessor, long defaultValue) {
- return addIdentified(identifier, new FxPropertyField<>(accessor::apply, defaultValue, SimpleLongProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor::apply, defaultValue, () -> new SimpleLongProperty(null, identifier)));
}
@@ -1144,72 +1163,72 @@ public ObjectProperty field(ObjectGetter getter, ObjectSetter
}
public ObjectProperty field(ObjectPropertyAccessor accessor) {
- return add(new FxPropertyField<>(accessor::apply, SimpleObjectProperty::new));
+ return add(new FxPropertyField<>(accessor, SimpleObjectProperty::new));
}
public ObjectProperty field(ObjectPropertyAccessor accessor, T defaultValue) {
- return add(new FxPropertyField<>(accessor::apply, defaultValue, SimpleObjectProperty::new));
+ return add(new FxPropertyField<>(accessor, defaultValue, SimpleObjectProperty::new));
}
public ObjectProperty field(String identifier, ObjectGetter getter, ObjectSetter setter) {
- return addIdentified(identifier, new BeanPropertyField<>(getter, setter, SimpleObjectProperty::new));
+ return addIdentified(identifier, new BeanPropertyField<>(getter, setter, () -> new SimpleObjectProperty(null, identifier)));
}
public ObjectProperty field(String identifier, ObjectGetter getter, ObjectSetter setter,
T defaultValue) {
return addIdentified(identifier, new BeanPropertyField<>(getter, setter, defaultValue,
- SimpleObjectProperty::new));
+ () -> new SimpleObjectProperty(null, identifier)));
}
public ObjectProperty field(String identifier, ObjectPropertyAccessor accessor) {
- return addIdentified(identifier, new FxPropertyField<>(accessor::apply, SimpleObjectProperty::new));
+ return addIdentified(identifier, new FxPropertyField<>(accessor, () -> new SimpleObjectProperty(null, identifier)));
}
public ObjectProperty field(String identifier, ObjectPropertyAccessor accessor, T defaultValue) {
return addIdentified(identifier,
- new FxPropertyField<>(accessor::apply, defaultValue, SimpleObjectProperty::new));
+ new FxPropertyField<>(accessor, defaultValue, () -> new SimpleObjectProperty(null, identifier)));
}
/** Field type list **/
public ListProperty field(ListGetter getter, ListSetter setter) {
- return add(new BeanListPropertyField<>(getter::apply,
- (m, list) -> setter.accept(m, FXCollections.observableArrayList(list))));
+ return add(new BeanListPropertyField<>(getter,
+ (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), SimpleListProperty::new));
}
public ListProperty field(ListGetter getter, ListSetter setter, List defaultValue) {
- return add(new BeanListPropertyField<>(getter::apply,
- (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), defaultValue));
+ return add(new BeanListPropertyField<>(getter,
+ (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), SimpleListProperty::new, defaultValue));
}
public ListProperty field(ListPropertyAccessor accessor) {
- return add(new FxListPropertyField<>(accessor::apply));
+ return add(new FxListPropertyField<>(accessor, SimpleListProperty::new));
}
public ListProperty field(ListPropertyAccessor accessor, List defaultValue) {
- return add(new FxListPropertyField<>(accessor::apply, defaultValue));
+ return add(new FxListPropertyField<>(accessor, SimpleListProperty::new, defaultValue));
}
public ListProperty field(String identifier, ListGetter getter, ListSetter setter) {
- return addIdentified(identifier, new BeanListPropertyField<>(getter::apply,
- (m, list) -> setter.accept(m, FXCollections.observableArrayList(list))));
+ return addIdentified(identifier, new BeanListPropertyField<>(getter,
+ (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), () -> new SimpleListProperty<>(null, identifier)));
}
public ListProperty field(String identifier, ListGetter getter, ListSetter setter,
List defaultValue) {
- return addIdentified(identifier, new BeanListPropertyField<>(getter::apply,
- (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), defaultValue));
+ return addIdentified(identifier, new BeanListPropertyField<>(getter,
+ (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), () -> new SimpleListProperty<>(null, identifier), defaultValue));
}
public ListProperty field(String identifier, ListPropertyAccessor accessor) {
- return addIdentified(identifier, new FxListPropertyField<>(accessor::apply));
+ return addIdentified(identifier, new FxListPropertyField<>(accessor, () -> new SimpleListProperty<>(null, identifier)));
}
public ListProperty field(String identifier, ListPropertyAccessor accessor, List defaultValue) {
- return addIdentified(identifier, new FxListPropertyField<>(accessor::apply, defaultValue));
+ return addIdentified(identifier, new FxListPropertyField<>(accessor, () -> new SimpleListProperty<>(null, identifier), defaultValue));
}
private > R add(PropertyField field) {
@@ -1248,7 +1267,7 @@ private > R addIdentified(String fieldName, PropertyFie
* change the data of the model instance directly, this property won't turn to true
.
*
*
- * @return a reay-only property indicating a difference between model and wrapper.
+ * @return a read-only property indicating a difference between model and wrapper.
*/
public ReadOnlyBooleanProperty differentProperty() {
return diffFlag.getReadOnlyProperty();
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenter.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenter.java
index 7af4cbb1a..65bab704a 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenter.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/notifications/DefaultNotificationCenter.java
@@ -39,6 +39,9 @@ public class DefaultNotificationCenter implements NotificationCenter {
@Override
public void subscribe(String messageName, NotificationObserver observer) {
+ if(observer==null) {
+ throw new IllegalArgumentException("The observer must not be null.");
+ }
addObserver(messageName, observer, globalObservers);
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/CompositeValidationStatus.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/CompositeValidationStatus.java
deleted file mode 100644
index 42cc27754..000000000
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/CompositeValidationStatus.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*******************************************************************************
- * Copyright 2015 Alexander Casall, Manuel Mauky
- *
- * 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 de.saxsys.mvvmfx.utils.validation;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * This class is used as {@link ValidationStatus} for {@link CompositeValidator}.
- *
- * In contrast to the basic {@link ValidationStatus} this class not only tracks
- * {@link ValidationMessage} alone but also keeps track of the {@link Validator}s that
- * the messages belong to. This is needed to be able to remove only those messages for
- * a specific validator.
- *
- * @author manuel.mauky
- */
-class CompositeValidationStatus extends ValidationStatus {
-
- /**
- * This wrapper class is used to additionally store the information of the validator
- * that the messages belong to.
- *
- * Instead of storing the validator instance itself we only store an {@link System#identityHashCode(Object)}
- * because we don't need the validator itself but only a way to distinguish validator instances.
- * Using an identity hashcode instead of the actual instance can minimize the possibility of memory leaks.
- */
- private static class CompositeValidationMessageWrapper extends ValidationMessage {
-
- private Integer validatorCode;
-
- CompositeValidationMessageWrapper(ValidationMessage base, Validator validator) {
- super(base.getSeverity(), base.getMessage());
- this.validatorCode = System.identityHashCode(validator);
- }
-
- Integer getValidatorCode() {
- return validatorCode;
- }
- }
-
-
- void addMessage(Validator validator, List extends ValidationMessage> messages) {
- /*
- Instead of adding the messages directly to the message list ...
- */
- getMessagesInternal().addAll(
- messages.stream()
- // ... we wrap them to keep track of the used validator.
- .map(message -> new CompositeValidationMessageWrapper(message, validator))
- .collect(Collectors.toList()));
- }
-
- /*
- Remove all given messages for the given validator.
- */
- void removeMessage(final Validator validator, final List extends ValidationMessage> messages) {
- final List messagesToRemove =
- getMessagesInternal().stream()
- .filter(messages::contains) // only the given messages
- .filter(message -> (message instanceof CompositeValidationMessageWrapper))
- .map(message -> (CompositeValidationMessageWrapper) message)
- .filter(message -> message.getValidatorCode().equals(System.identityHashCode(validator)))
- .collect(Collectors.toList());
-
- getMessagesInternal().removeIf(validationMessage -> {
- if (validationMessage instanceof CompositeValidationMessageWrapper) {
- final CompositeValidationMessageWrapper wrapper = (CompositeValidationMessageWrapper) validationMessage;
- return messagesToRemove.stream()
- .filter(m -> m.getValidatorCode().equals(wrapper.getValidatorCode()))
- .anyMatch(wrapper::equals);
- }
-
- return false;
- });
- }
-
- /*
- * Remove all messages for this particular validator.
- */
- void removeMessage(final Validator validator) {
- getMessagesInternal().removeIf(validationMessage -> {
- if (validationMessage instanceof CompositeValidationMessageWrapper) {
- final CompositeValidationMessageWrapper wrapper = (CompositeValidationMessageWrapper) validationMessage;
-
- return wrapper.getValidatorCode().equals(System.identityHashCode(validator));
- }
-
- return false;
- });
- }
-
-}
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
deleted file mode 100644
index 32400ba4b..000000000
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/validation/ObservableRuleBasedValidator.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*******************************************************************************
- * Copyright 2015 Alexander Casall, Manuel Mauky
- *
- * 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 de.saxsys.mvvmfx.utils.validation;
-
-import javafx.beans.value.ObservableValue;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * This {@link Validator} implementation uses observable boolean values as rules. In comparison to the
- * {@link FunctionBasedValidator} with this implementation more complex validation logic can be implemented.
- *
- * It is useful for use cases where:
- *
- *
- * - the validation logic is already available as observable boolean
- * - you need to define more multiple rules
- * - you need to define complex rules that f.e. are considering multiple fields (cross-field-validation)
- *
- *
- * You can add multiple rules via the {@link #addRule(ObservableValue, ValidationMessage)} method. Each rule is an
- * observable boolean value. If the rule evaluates to true
it is considered to be "valid" and no message
- * will be present in the {@link ValidationStatus} ({@link #getValidationStatus()}). If the rule evaluates to
- * false
it is considered to be "invalid". In this case the given {@link ValidationMessage} will be
- * present.
- *
- * If multiple rules are violated, each message will be present.
- */
-public class ObservableRuleBasedValidator implements Validator {
-
- private List> rules = new ArrayList<>();
-
- private ValidationStatus validationStatus = new ValidationStatus();
-
- /**
- * Add a rule for this validator.
- *
- * The rule defines a condition that has to be fulfilled.
- *
- * A rule is defined by an observable boolean value. If the rule has a value of true
the rule is
- * "fulfilled". If the rule has a value of false
the rule is violated. In this case the given message
- * object will be added to the status of this validator.
- *
- * There are some predefined rules for common use cases in the {@link ObservableRules} class that can be used.
- *
- * @param rule
- * @param message
- */
- public void addRule(ObservableValue rule, ValidationMessage message) {
- rules.add(rule);
-
- rule.addListener((observable, oldValue, newValue) -> {
- validateRule(newValue, message);
- });
-
- validateRule(rule.getValue(), message);
- }
-
- private void validateRule(boolean isValid, ValidationMessage message) {
- if (isValid) {
- validationStatus.removeMessage(message);
- } else {
- validationStatus.addMessage(message);
- }
- }
-
- @Override
- public ValidationStatus getValidationStatus() {
- return validationStatus;
- }
-
-}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_ResourceBundle_Test.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_ResourceBundle_Test.java
index 4a8da07e8..00983d8f0 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_ResourceBundle_Test.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_ResourceBundle_Test.java
@@ -17,41 +17,76 @@
import static de.saxsys.mvvmfx.internal.viewloader.ResourceBundleAssert.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
import java.io.StringReader;
+import java.util.ListResourceBundle;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
+import de.saxsys.mvvmfx.MvvmFX;
+import de.saxsys.mvvmfx.internal.viewloader.example.*;
+import de.saxsys.mvvmfx.resourcebundle.global.TestView;
+import de.saxsys.mvvmfx.resourcebundle.global.TestViewModel;
+import de.saxsys.mvvmfx.testingutils.ExceptionUtils;
+import de.saxsys.mvvmfx.testingutils.jfxrunner.JfxRunner;
import javafx.scene.layout.VBox;
+import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import de.saxsys.mvvmfx.FluentViewLoader;
import de.saxsys.mvvmfx.InjectResourceBundle;
import de.saxsys.mvvmfx.JavaView;
import de.saxsys.mvvmfx.ViewTuple;
-import de.saxsys.mvvmfx.internal.viewloader.example.TestFxmlViewResourceBundle;
-import de.saxsys.mvvmfx.internal.viewloader.example.TestFxmlViewResourceBundleWithoutController;
-import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModelWithResourceBundle;
-
+import org.junit.runner.RunWith;
/**
* This test focuses on the handling of {@link ResourceBundle}s.
- *
+ *
* A resourceBundle can be injected into the View with default behaviour of JavaFX. Additionally the user can use the
* mvvmfx annotation {@link InjectResourceBundle} to inject the resourceBundle in the View and in the ViewModel.
*/
+@RunWith(JfxRunner.class)
public class FluentViewLoader_ResourceBundle_Test {
-
-
+
+
private ResourceBundle resourceBundle;
-
+ private ResourceBundle globalResourceBundle;
+
@Before
public void setup() throws Exception {
- resourceBundle = new PropertyResourceBundle(new StringReader(""));
+ resourceBundle = new ListResourceBundle() {
+ @Override
+ protected Object[][] getContents() {
+ return new Object[][] {
+ { "key_local1", "value_local1" },
+ { "key_local2", "value_local2" },
+ { "key_global1", "value_global1" } // overwrite global values
+ };
+ }
+ };
+
+ globalResourceBundle = new ListResourceBundle() {
+ @Override
+ protected Object[][] getContents() {
+ return new Object[][] {
+ { "key_global1", "value_global1" },
+ { "key_global2", "value_global2" }
+ };
+ }
+ };
+
+ MvvmFX.setGlobalResourceBundle(null);
+ }
+
+ @After
+ public void tearDown() {
+ MvvmFX.setGlobalResourceBundle(null);
}
-
+
@Test
public void success_fxml_injectionOfResourceBundles() {
final ViewTuple viewTuple =
@@ -59,58 +94,59 @@ public void success_fxml_injectionOfResourceBundles() {
.fxmlView(TestFxmlViewResourceBundle.class)
.resourceBundle(resourceBundle)
.load();
-
+
final TestViewModelWithResourceBundle viewModel = viewTuple.getViewModel();
final TestFxmlViewResourceBundle view = viewTuple.getCodeBehind();
-
+
assertThat(view.resources).isNotNull().hasSameContent(resourceBundle);
assertThat(view.resourceBundle).isNotNull().hasSameContent(resourceBundle);
-
+
assertThat(viewModel.resourceBundle).isNotNull().hasSameContent(resourceBundle);
}
-
+
@Test
public void success_fxml_injectionWithExistingViewModel() {
TestViewModelWithResourceBundle viewModel = new TestViewModelWithResourceBundle();
-
+
final ViewTuple viewTuple = FluentViewLoader
.fxmlView(TestFxmlViewResourceBundle.class)
.resourceBundle(resourceBundle)
.viewModel(viewModel)
.load();
-
+
assertThat(viewTuple.getViewModel()).isEqualTo(viewModel);
final TestFxmlViewResourceBundle view = viewTuple.getCodeBehind();
-
-
+
+
assertThat(view.resourceBundle).isNotNull().hasSameContent(resourceBundle);
+ assertThat(view.resources).isNotNull().hasSameContent(resourceBundle);
assertThat(viewModel.resourceBundle).isNotNull().hasSameContent(resourceBundle);
}
-
-
+
+
@Test
public void success_fxml_existingCodeBehind() {
TestFxmlViewResourceBundleWithoutController codeBehind = new TestFxmlViewResourceBundleWithoutController();
-
+
final ViewTuple viewTuple =
FluentViewLoader
.fxmlView(TestFxmlViewResourceBundleWithoutController.class)
.codeBehind(codeBehind)
.resourceBundle(resourceBundle)
.load();
-
+
assertThat(viewTuple.getCodeBehind()).isEqualTo(codeBehind);
final TestViewModelWithResourceBundle viewModel = viewTuple.getViewModel();
-
+
assertThat(viewModel.resourceBundle).hasSameContent(resourceBundle);
assertThat(codeBehind.resourceBundle).hasSameContent(resourceBundle);
}
-
+
@Test
public void success_fxml_existingCodeBehind_and_existingViewModel() {
TestFxmlViewResourceBundleWithoutController codeBehind = new TestFxmlViewResourceBundleWithoutController();
TestViewModelWithResourceBundle viewModel = new TestViewModelWithResourceBundle();
-
+
final ViewTuple viewTuple =
FluentViewLoader
.fxmlView(TestFxmlViewResourceBundleWithoutController.class)
@@ -118,22 +154,22 @@ public void success_fxml_existingCodeBehind_and_existingViewModel() {
.viewModel(viewModel)
.resourceBundle(resourceBundle)
.load();
-
-
-
+
+
+
assertThat(viewTuple.getCodeBehind()).isEqualTo(codeBehind);
assertThat(viewTuple.getViewModel()).isEqualTo(viewModel);
-
+
assertThat(viewModel.resourceBundle).hasSameContent(resourceBundle);
assertThat(codeBehind.resourceBundle).hasSameContent(resourceBundle);
}
-
+
public static class TestJavaView extends VBox implements JavaView {
@InjectResourceBundle
ResourceBundle resourceBundle;
}
-
-
+
+
@Test
public void success_java_injectionOfResourceBundles() {
final ViewTuple viewTuple =
@@ -141,30 +177,87 @@ public void success_java_injectionOfResourceBundles() {
.javaView(TestJavaView.class)
.resourceBundle(resourceBundle)
.load();
-
+
final TestViewModelWithResourceBundle viewModel = viewTuple.getViewModel();
final TestJavaView view = viewTuple.getCodeBehind();
-
+
assertThat(view.resourceBundle).isNotNull().hasSameContent(resourceBundle);
-
+
assertThat(viewModel.resourceBundle).isNotNull().hasSameContent(resourceBundle);
}
-
+
@Test
public void success_java_injectionWithExistingViewModel() {
TestViewModelWithResourceBundle viewModel = new TestViewModelWithResourceBundle();
-
+
final ViewTuple viewTuple = FluentViewLoader
.javaView(TestJavaView.class)
.resourceBundle(resourceBundle)
.viewModel(viewModel)
.load();
-
+
assertThat(viewTuple.getViewModel()).isEqualTo(viewModel);
final TestJavaView view = viewTuple.getCodeBehind();
-
-
+
+
assertThat(view.resourceBundle).isNotNull().hasSameContent(resourceBundle);
assertThat(viewModel.resourceBundle).isNotNull().hasSameContent(resourceBundle);
}
+
+
+ /**
+ * Both the View and ViewModel have {@link InjectResourceBundle} annotations (without optional argument)
+ * but no resourceBundle was provided at loading time. Therefore an exception is thrown.
+ */
+ @Test
+ // @Ignore("until fixed. See issue #435")
+ public void fail_noResourceBundleGivenForViewAndViewModel() {
+ MvvmFX.setGlobalResourceBundle(null);
+
+ try {
+ FluentViewLoader.fxmlView(TestFxmlViewResourceBundle.class).load();
+
+ failBecauseExceptionWasNotThrown(RuntimeException.class);
+ } catch (Exception ex) {
+ assertThat(ExceptionUtils.getRootCause(ex)).isInstanceOf(IllegalStateException.class).hasMessageContaining(
+ "expects a ResourceBundle to be injected but no ResourceBundle was defined while loading.");
+ }
+ }
+
+ /**
+ * Only the ViewModel is using the {@link InjectResourceBundle} annotation
+ * but no resourceBundle was provided at loading time. Therefore an exception is thrown.
+ */
+ @Test
+ public void fail_noResourceBundleGivenForViewModel() {
+ MvvmFX.setGlobalResourceBundle(null);
+
+ try {
+ FluentViewLoader.fxmlView(TestFxmlViewOnlyViewModelResourceBundle.class).load();
+
+ failBecauseExceptionWasNotThrown(RuntimeException.class);
+ } catch (Exception ex) {
+ assertThat(ExceptionUtils.getRootCause(ex)).isInstanceOf(IllegalStateException.class).hasMessageContaining(
+ "expects a ResourceBundle to be injected but no ResourceBundle was defined while loading.");
+ }
+ }
+
+ /**
+ * Only the View is using the {@link InjectResourceBundle} annotation
+ * but no resourceBundle was provided at loading time. Therefore an exception is thrown.
+ */
+ @Test
+ public void fail_noResourceBundleGivenForView() {
+ MvvmFX.setGlobalResourceBundle(null);
+
+ try {
+ FluentViewLoader.fxmlView(TestFxmlViewOnlyViewResourceBundle.class).load();
+
+ failBecauseExceptionWasNotThrown(RuntimeException.class);
+ } catch (Exception ex) {
+ assertThat(ExceptionUtils.getRootCause(ex)).isInstanceOf(IllegalStateException.class).hasMessageContaining(
+ "expects a ResourceBundle to be injected but no ResourceBundle was defined while loading.");
+ }
+ }
+
}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjectorTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjectorTest.java
index 576bb9bbd..f2be52263 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjectorTest.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjectorTest.java
@@ -26,11 +26,13 @@
import de.saxsys.mvvmfx.InjectResourceBundle;
+import de.saxsys.mvvmfx.internal.viewloader.ResourceBundleManager;
+
public class ResourceBundleInjectorTest {
private ResourceBundle resourceBundle;
-
+
@Before
public void setup() throws Exception {
resourceBundle = new PropertyResourceBundle(new StringReader(""));
@@ -98,7 +100,7 @@ class Example {
Example example = new Example();
- ResourceBundleInjector.injectResourceBundle(example, null);
+ ResourceBundleInjector.injectResourceBundle(example, ResourceBundleManager.EMPTY_RESOURCE_BUNDLE);
}
/**
@@ -112,7 +114,7 @@ class Example {
Example example = new Example();
- ResourceBundleInjector.injectResourceBundle(example, null);
+ ResourceBundleInjector.injectResourceBundle(example, ResourceBundleManager.EMPTY_RESOURCE_BUNDLE);
assertThat(example.resourceBundle).isNull();
}
@@ -130,7 +132,7 @@ class Example {
Example example = new Example();
- ResourceBundleInjector.injectResourceBundle(example, null);
+ ResourceBundleInjector.injectResourceBundle(example, ResourceBundleManager.EMPTY_RESOURCE_BUNDLE);
}
@@ -163,7 +165,7 @@ class Example {
Example example = new Example();
- ResourceBundleInjector.injectResourceBundle(example, null);
+ ResourceBundleInjector.injectResourceBundle(example, ResourceBundleManager.EMPTY_RESOURCE_BUNDLE);
assertThat(example.resourceBundle).isNull();
}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtilsTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtilsTest.java
index 8fed48609..cd660e897 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtilsTest.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtilsTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2015 Alexander Casall, Manuel Mauky
+ * Copyright 2015 Alexander Casall, Manuel Mauky, Sven Lechner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +15,12 @@
******************************************************************************/
package de.saxsys.mvvmfx.internal.viewloader;
-import static org.assertj.core.api.Assertions.*;
-
-import org.junit.Test;
-
import de.saxsys.mvvmfx.ViewModel;
import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModelWithDoubleInjection;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
public class ViewLoaderReflectionUtilsTest {
@@ -54,4 +54,12 @@ class TestView implements View {
assertThat(viewModel).isNull();
}
+
+ @Test(expected = IllegalStateException.class)
+ public void testDoubleInjection() {
+ class TestView implements View {}
+
+ ViewModel viewModel = ViewLoaderReflectionUtils.createViewModel(new TestView());
+ ViewLoaderReflectionUtils.initializeViewModel(viewModel);
+ }
}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewOnlyViewModelResourceBundle.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewOnlyViewModelResourceBundle.java
new file mode 100644
index 000000000..b10423dc5
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewOnlyViewModelResourceBundle.java
@@ -0,0 +1,18 @@
+package de.saxsys.mvvmfx.internal.viewloader.example;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+/**
+ * A CodeBehind class that has no resourceBundles injected but has a ViewModel that injects
+ * a ResourceBundle. This is needed to check that an exception is thrown when no resourcebundle is provided on loading.
+ *
+ * When the View itself injects a ResourceBundle that is used in the FXML file, an exception is
+ * thrown by the FXMLLoader directly. However, it's possible that the resourceBundle is only used
+ * in the ViewModel. In this case we have to throw an exception on our own.
+ */
+public class TestFxmlViewOnlyViewModelResourceBundle implements FxmlView {
+
+ @InjectViewModel
+ public TestViewModelWithResourceBundle viewModel;
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewOnlyViewResourceBundle.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewOnlyViewResourceBundle.java
new file mode 100644
index 000000000..ef7f02c86
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewOnlyViewResourceBundle.java
@@ -0,0 +1,18 @@
+package de.saxsys.mvvmfx.internal.viewloader.example;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+/**
+ * A CodeBehind class that has a resourceBundle injected but the ViewModel not.
+ * This is needed to check that an exception is thrown when no resourceBundle is provided on loading.
+ *
+ * When the viewModel gets a resourceBundle injected an exception is thrown. However, to verify
+ * that an exception is also thrown the only Codebehind needs the resourceBundle we use
+ * this class for this use case.
+ */
+public class TestFxmlViewOnlyViewResourceBundle implements FxmlView {
+
+ @InjectViewModel
+ public TestViewModelWithResourceBundle viewModel;
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestViewModelWithDoubleInjection.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestViewModelWithDoubleInjection.java
new file mode 100644
index 000000000..1753dafd4
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestViewModelWithDoubleInjection.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2016 Sven Lechner
+ *
+ * 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 de.saxsys.mvvmfx.internal.viewloader.example;
+
+import de.saxsys.mvvmfx.ViewModel;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * Created by Sven on 23/06/16.
+ */
+public class TestViewModelWithDoubleInjection implements ViewModel {
+
+ @PostConstruct
+ public void initialize() {}
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/LifecycleTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/LifecycleTest.java
new file mode 100644
index 000000000..1cd03af62
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/LifecycleTest.java
@@ -0,0 +1,360 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle;
+
+import de.saxsys.mvvmfx.FluentViewLoader;
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewTuple;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic.LifecycleTestRootView;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic.LifecycleTestRootViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic.LifecycleTestSub1ViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic.LifecycleTestSub2ViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc.LifecycleGCTestRootView;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc.LifecycleGCTestRootViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc.LifecycleGCTestSub1ViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc.LifecycleGCTestSub2ViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification.LifecycleNotificationView;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification.LifecycleNotificationViewModel;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification_without_lifecycle.NotificationWithoutLifecycleView;
+import de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification_without_lifecycle.NotificationWithoutLifecycleViewModel;
+import de.saxsys.mvvmfx.testingutils.GCVerifier;
+import de.saxsys.mvvmfx.testingutils.jfxrunner.JfxRunner;
+import de.saxsys.mvvmfx.testingutils.jfxrunner.TestInJfxThread;
+import de.saxsys.mvvmfx.utils.notifications.DefaultNotificationCenter;
+import de.saxsys.mvvmfx.utils.notifications.NotificationCenterFactory;
+import javafx.scene.Scene;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(JfxRunner.class)
+public class LifecycleTest {
+
+ /**
+ * This test shows that the lifecycle methods are invoked
+ * for the root-viewModel and sub-viewModels.
+ *
+ * In this test no Garbage Collection is triggered.
+ * In the test case {@link #testLifecycleWithSubViewsWithGC()}
+ * the same workflow is tested with the only difference that
+ * garbage collection is enforced which produces a different behaviour
+ * of the lifecycle.
+ */
+ @Test
+ @TestInJfxThread
+ public void testLifecycleWithSubViewsWithoutGC() {
+ LifecycleTestRootViewModel.onViewAddedCalled = 0;
+ LifecycleTestRootViewModel.onViewRemovedCalled = 0;
+ LifecycleTestSub1ViewModel.onViewAddedCalled = 0;
+ LifecycleTestSub1ViewModel.onViewRemovedCalled = 0;
+ LifecycleTestSub2ViewModel.onViewAddedCalled = 0;
+ LifecycleTestSub2ViewModel.onViewRemovedCalled = 0;
+
+ ViewTuple viewTuple = FluentViewLoader.fxmlView(LifecycleTestRootView.class).load();
+
+ // the root view is not directly added to the Scene but encapsulated in
+ // another container
+ VBox subContainer = new VBox();
+ subContainer.getChildren().add(viewTuple.getView());
+
+ VBox container = new VBox();
+
+ Stage stage = new Stage();
+ Scene scene = new Scene(container);
+ stage.setScene(scene);
+
+
+ // before adding to scene
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(0);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(0);
+
+ // add rootView to container
+ container.getChildren().add(subContainer);
+
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(0);
+
+ // remove from container
+ container.getChildren().clear();
+
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(1);
+
+
+ // add again to container
+ container.getChildren().add(subContainer);
+
+ // the lifecycle methods are invoked again
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(2);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(2);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(2);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(1);
+ }
+
+
+ /**
+ * This test case scenario is similar to {@link #testLifecycleWithSubViewsWithoutGC()}
+ * with the only difference that this time garbage collection is enforeced
+ * on every relevant intermediate step.
+ * The expected behavior is that the viewModel isn't garbage collected until after
+ * the {@link SceneLifecycle#onViewRemoved()} method is invoked.
+ * This way the the user can use this method to cleanup other resources.
+ * However, after this method is invoked, no guarantee is given by the framework
+ * that the viewModel is still active. This means that the ViewModel will be collected
+ * by GC and therefore the {@link SceneLifecycle#onViewAdded()} is not invoked again
+ * when the view is added to the scene a second time.
+ */
+ @Test
+ @TestInJfxThread
+ public void testLifecycleWithSubViewsWithGC() {
+ LifecycleTestRootViewModel.onViewAddedCalled = 0;
+ LifecycleTestRootViewModel.onViewRemovedCalled = 0;
+ LifecycleTestSub1ViewModel.onViewAddedCalled = 0;
+ LifecycleTestSub1ViewModel.onViewRemovedCalled = 0;
+ LifecycleTestSub2ViewModel.onViewAddedCalled = 0;
+ LifecycleTestSub2ViewModel.onViewRemovedCalled = 0;
+
+ ViewTuple viewTuple = FluentViewLoader.fxmlView(LifecycleTestRootView.class).load();
+
+ // GC is performed, however as we still have a reference to the viewTuple,
+ // nothing will be collected yet.
+ GCVerifier.forceGC();
+
+ // the root view is not directly added to the Scene. Instead it is encapsulated in
+ // another container
+ VBox subContainer = new VBox();
+ subContainer.getChildren().add(viewTuple.getView());
+
+ VBox container = new VBox();
+
+ Stage stage = new Stage();
+ Scene scene = new Scene(container);
+ stage.setScene(scene);
+
+
+ // now we clear the viewTuple reference ...
+ viewTuple = null;
+
+ // and perform GC a second time.
+ // This time, both the ViewModel and the CodeBehind would be collected
+ // if the framework hadn't prevented it.
+ GCVerifier.forceGC();
+
+ // before adding to scene
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(0);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(0);
+
+ // add rootView to container
+ container.getChildren().add(subContainer);
+
+ // onViewAdded is invoked for all viewModels
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(0);
+
+ GCVerifier.forceGC();
+
+ // remove from container
+ container.getChildren().clear();
+
+ GCVerifier.forceGC();
+
+ // onViewRemoved is invoked on all ViewModels
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(1);
+
+ // Here the guarantee of the framework ends. This time the viewModels
+ // will be collected
+ GCVerifier.forceGC();
+
+ // add again to container
+ container.getChildren().add(subContainer);
+
+ // no methods are invoked.
+ assertThat(LifecycleTestRootViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestRootViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub1ViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewAddedCalled).isEqualTo(1);
+ assertThat(LifecycleTestSub2ViewModel.onViewRemovedCalled).isEqualTo(1);
+ }
+
+
+ /**
+ * This test scenario shows a use case were no lifecycle is used and
+ * a memory leak is produced.
+ * The test case {@link #testGarbageCollection()} shows the same
+ * example but with the usage of the lifecycle which prevents the memory leak.
+ *
+ * The ViewModel in this example subscribes to the notification center which
+ * leads to the notification center holding a reference of the ViewModel which
+ * prevents Garbage collection.
+ */
+ @Test
+ @TestInJfxThread
+ public void testGarbageCollectionFailed() {
+ NotificationCenterFactory.setNotificationCenter(new DefaultNotificationCenter());
+
+ ViewTuple viewTuple = FluentViewLoader.fxmlView(NotificationWithoutLifecycleView.class).load();
+
+ VBox container = new VBox();
+
+ Stage stage = new Stage();
+ Scene scene = new Scene(container);
+ stage.setScene(scene);
+
+ GCVerifier vmVerifier = GCVerifier.create(viewTuple.getViewModel());
+
+ assertThat(vmVerifier.isAvailableForGC()).isFalse();
+
+ container.getChildren().add(viewTuple.getView());
+
+ viewTuple = null;
+
+ // The ViewModel has a listener subscribed so it isn't available for GC
+ assertThat(vmVerifier.isAvailableForGC()).isFalse();
+
+ container.getChildren().clear();
+
+ // even after the view isn't used anymore, the ViewModel still can't be garbage collected
+ // because it is still registered in the notification center
+ assertThat(vmVerifier.isAvailableForGC()).isFalse();
+
+
+ // only if we replace the notification center ...
+ NotificationCenterFactory.setNotificationCenter(new DefaultNotificationCenter());
+
+
+ // the viewModel can be garbage collected. Of cause in practice this isn't a suitable solution
+ assertThat(vmVerifier.isAvailableForGC()).isTrue();
+ }
+
+ /**
+ * This test case shows a typical use case of the lifecycle methods.
+ * The test case {@link #testGarbageCollectionFailed()} shows
+ * the same use case but without the usage of lifecycle methods which leads to a memory leak.
+ * In this test case this memory leak is prevented.
+ *
+ * The ViewModel subscribes to the notificationCenter in it's "initialize" method.
+ * In the {@link SceneLifecycle#onViewRemoved()} this notification is unsubscribed.
+ * Therefore the ViewModel is available for garbage collection afterwards.
+ */
+ @Test
+ @TestInJfxThread
+ public void testGarbageCollection() {
+ NotificationCenterFactory.setNotificationCenter(new DefaultNotificationCenter());
+
+ ViewTuple viewTuple = FluentViewLoader.fxmlView(LifecycleNotificationView.class).load();
+
+ VBox container = new VBox();
+
+ Stage stage = new Stage();
+ Scene scene = new Scene(container);
+ stage.setScene(scene);
+
+ GCVerifier vmVerifier = GCVerifier.create(viewTuple.getViewModel());
+
+ assertThat(vmVerifier.isAvailableForGC()).isFalse();
+
+ container.getChildren().add(viewTuple.getView());
+
+ viewTuple = null;
+
+ // The ViewModel has a listener subscribed so it isn't available for GC now
+ assertThat(vmVerifier.isAvailableForGC()).isFalse();
+
+ // this triggeres the lifecycle method which is used to deregister the listener
+ container.getChildren().clear();
+
+
+ // therefore the ViewModel can now be garbage collected.
+ assertThat(vmVerifier.isAvailableForGC()).isTrue();
+
+
+ // cleanup notification center to not infer with other tests
+ NotificationCenterFactory.setNotificationCenter(new DefaultNotificationCenter());
+ }
+
+
+ /**
+ * This test scenario reproduces a misbehavior of the
+ * first prove-of-concept implementation of the lifecycle handling:
+ * When multiple ViewModels in a view hierarchy implement the {@link SceneLifecycle}
+ * interface, all viewModel's {@link SceneLifecycle#onViewRemoved()} have to be
+ * invoked when the root view is removed from the scene.
+ * The framework guarantees that all methods are invoked before the ViewModels become
+ * available for garbage collection.
+ * In the first implementation this guarantee wasn't fulfilled when garbage collection happens
+ * between two invocations of {@link SceneLifecycle#onViewRemoved()}.
+ * After the first lifecycle method was invoked all following viewModels could be collected
+ * by the Garbage collector.
+ *
+ *
+ * To reproduce this wrong behavior the viewModels in this test case are invoking
+ * {@link GCVerifier#forceGC()} inside of the {@link SceneLifecycle#onViewRemoved()} methods.
+ * In the previous implementation this resulted in only the first method was invoked.
+ */
+ @Test
+ @TestInJfxThread
+ public void testGcBetweenLifecycleMethods() {
+ LifecycleGCTestRootViewModel.onViewRemovedCalled = 0;
+ LifecycleGCTestSub1ViewModel.onViewRemovedCalled = 0;
+ LifecycleGCTestSub2ViewModel.onViewRemovedCalled = 0;
+
+
+ ViewTuple viewTuple = FluentViewLoader.fxmlView(LifecycleGCTestRootView.class).load();
+
+ VBox subContainer = new VBox();
+ subContainer.getChildren().add(viewTuple.getView());
+
+ VBox container = new VBox();
+
+ Stage stage = new Stage();
+ Scene scene = new Scene(container);
+ stage.setScene(scene);
+
+ viewTuple = null;
+
+ GCVerifier.forceGC();
+
+ assertThat(LifecycleGCTestRootViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleGCTestSub1ViewModel.onViewRemovedCalled).isEqualTo(0);
+ assertThat(LifecycleGCTestSub2ViewModel.onViewRemovedCalled).isEqualTo(0);
+
+ container.getChildren().add(subContainer);
+
+ GCVerifier.forceGC();
+
+ container.getChildren().remove(subContainer);
+
+ assertThat(LifecycleGCTestRootViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleGCTestSub1ViewModel.onViewRemovedCalled).isEqualTo(1);
+ assertThat(LifecycleGCTestSub2ViewModel.onViewRemovedCalled).isEqualTo(1);
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestRootView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestRootView.java
new file mode 100644
index 000000000..0e28ba96e
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestRootView.java
@@ -0,0 +1,20 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+import javafx.fxml.FXML;
+
+public class LifecycleTestRootView implements FxmlView {
+
+ @FXML
+ public LifecycleTestSub1View sub1Controller;
+ @FXML
+ public LifecycleTestSub2View sub2Controller;
+
+ @InjectViewModel
+ private LifecycleTestRootViewModel viewModel;
+
+ public void initialize() {
+
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestRootViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestRootViewModel.java
new file mode 100644
index 000000000..ae056d251
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestRootViewModel.java
@@ -0,0 +1,20 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic;
+
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewModel;
+
+public class LifecycleTestRootViewModel implements ViewModel, SceneLifecycle {
+
+ public static int onViewAddedCalled = 0;
+ public static int onViewRemovedCalled = 0;
+
+ @Override
+ public void onViewAdded() {
+ onViewAddedCalled++;
+ }
+
+ @Override
+ public void onViewRemoved() {
+ onViewRemovedCalled++;
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub1View.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub1View.java
new file mode 100644
index 000000000..db1896afa
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub1View.java
@@ -0,0 +1,14 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+public class LifecycleTestSub1View implements FxmlView {
+
+ @InjectViewModel
+ public LifecycleTestSub1ViewModel viewModel;
+
+ public void initialize() {
+
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub1ViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub1ViewModel.java
new file mode 100644
index 000000000..fc5364f1d
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub1ViewModel.java
@@ -0,0 +1,20 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic;
+
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewModel;
+
+public class LifecycleTestSub1ViewModel implements ViewModel, SceneLifecycle {
+
+ public static int onViewAddedCalled = 0;
+ public static int onViewRemovedCalled = 0;
+
+ @Override
+ public void onViewAdded() {
+ onViewAddedCalled++;
+ }
+
+ @Override
+ public void onViewRemoved() {
+ onViewRemovedCalled++;
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub2View.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub2View.java
new file mode 100644
index 000000000..678949c1d
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub2View.java
@@ -0,0 +1,14 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+public class LifecycleTestSub2View implements FxmlView {
+
+ @InjectViewModel
+ public LifecycleTestSub2ViewModel viewModel;
+
+ public void initialize() {
+
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub2ViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub2ViewModel.java
new file mode 100644
index 000000000..310b6aed7
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_basic/LifecycleTestSub2ViewModel.java
@@ -0,0 +1,20 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_basic;
+
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewModel;
+
+public class LifecycleTestSub2ViewModel implements ViewModel, SceneLifecycle {
+
+ public static int onViewAddedCalled = 0;
+ public static int onViewRemovedCalled = 0;
+
+ @Override
+ public void onViewAdded() {
+ onViewAddedCalled++;
+ }
+
+ @Override
+ public void onViewRemoved() {
+ onViewRemovedCalled++;
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestRootView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestRootView.java
new file mode 100644
index 000000000..7eeeabfd1
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestRootView.java
@@ -0,0 +1,20 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+import javafx.fxml.FXML;
+
+public class LifecycleGCTestRootView implements FxmlView {
+
+ @FXML
+ public LifecycleGCTestSub1View sub1Controller;
+ @FXML
+ public LifecycleGCTestSub2View sub2Controller;
+
+ @InjectViewModel
+ private LifecycleGCTestRootViewModel viewModel;
+
+ public void initialize() {
+
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestRootViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestRootViewModel.java
new file mode 100644
index 000000000..69ce8eb03
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestRootViewModel.java
@@ -0,0 +1,22 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc;
+
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewModel;
+import de.saxsys.mvvmfx.testingutils.GCVerifier;
+
+public class LifecycleGCTestRootViewModel implements ViewModel, SceneLifecycle {
+
+ public static int onViewAddedCalled = 0;
+ public static int onViewRemovedCalled = 0;
+
+ @Override
+ public void onViewAdded() {
+ onViewAddedCalled++;
+ }
+
+ @Override
+ public void onViewRemoved() {
+ onViewRemovedCalled++;
+ GCVerifier.forceGC();
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub1View.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub1View.java
new file mode 100644
index 000000000..9eeafcca8
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub1View.java
@@ -0,0 +1,14 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+public class LifecycleGCTestSub1View implements FxmlView {
+
+ @InjectViewModel
+ public LifecycleGCTestSub1ViewModel viewModel;
+
+ public void initialize() {
+
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub1ViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub1ViewModel.java
new file mode 100644
index 000000000..f887b63f2
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub1ViewModel.java
@@ -0,0 +1,22 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc;
+
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewModel;
+import de.saxsys.mvvmfx.testingutils.GCVerifier;
+
+public class LifecycleGCTestSub1ViewModel implements ViewModel, SceneLifecycle {
+
+ public static int onViewAddedCalled = 0;
+ public static int onViewRemovedCalled = 0;
+
+ @Override
+ public void onViewAdded() {
+ onViewAddedCalled++;
+ }
+
+ @Override
+ public void onViewRemoved() {
+ onViewRemovedCalled++;
+ GCVerifier.forceGC();
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub2View.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub2View.java
new file mode 100644
index 000000000..598bedf2c
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub2View.java
@@ -0,0 +1,14 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+public class LifecycleGCTestSub2View implements FxmlView {
+
+ @InjectViewModel
+ public LifecycleGCTestSub2ViewModel viewModel;
+
+ public void initialize() {
+
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub2ViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub2ViewModel.java
new file mode 100644
index 000000000..7b939f59f
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/LifecycleGCTestSub2ViewModel.java
@@ -0,0 +1,22 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc;
+
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewModel;
+import de.saxsys.mvvmfx.testingutils.GCVerifier;
+
+public class LifecycleGCTestSub2ViewModel implements ViewModel, SceneLifecycle {
+
+ public static int onViewAddedCalled = 0;
+ public static int onViewRemovedCalled = 0;
+
+ @Override
+ public void onViewAdded() {
+ onViewAddedCalled++;
+ }
+
+ @Override
+ public void onViewRemoved() {
+ onViewRemovedCalled++;
+ GCVerifier.forceGC();
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/package-info.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/package-info.java
new file mode 100644
index 000000000..d82cabaa8
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_gc/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * The classes in this package are used to show a scenario were garbage collection
+ * is performed in one of the lifecycle methods.
+ * In the first prove-of-concept implementation of the lifecycle handling
+ * this was producing a misbehavior: After the garbage collection, other following lifecycle methods
+ * were not be invoked.
+ */
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_gc;
\ No newline at end of file
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/LifecycleNotificationView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/LifecycleNotificationView.java
new file mode 100644
index 000000000..04bb2999b
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/LifecycleNotificationView.java
@@ -0,0 +1,10 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+public class LifecycleNotificationView implements FxmlView {
+
+ @InjectViewModel
+ private LifecycleNotificationViewModel viewModel;
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/LifecycleNotificationViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/LifecycleNotificationViewModel.java
new file mode 100644
index 000000000..5d8ca4ac5
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/LifecycleNotificationViewModel.java
@@ -0,0 +1,30 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification;
+
+import de.saxsys.mvvmfx.SceneLifecycle;
+import de.saxsys.mvvmfx.ViewModel;
+import de.saxsys.mvvmfx.utils.notifications.NotificationObserver;
+
+public class LifecycleNotificationViewModel implements ViewModel, SceneLifecycle {
+
+ int notificationCounter = 0;
+
+
+ // to be able to remove the observer we can't define it inline but instead
+ // need to keep a reference
+ private NotificationObserver notificationObserver = (key, payload) -> {
+ notificationCounter++;
+ };
+
+ public void initialize() {
+ subscribe("test", notificationObserver);
+ }
+
+ @Override
+ public void onViewAdded() {
+ }
+
+ @Override
+ public void onViewRemoved() {
+ unsubscribe("test", notificationObserver);
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/package-info.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/package-info.java
new file mode 100644
index 000000000..630b56f1a
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * This package contains example view/viewModel
+ * for a test to reproduce the behavior of the {@link de.saxsys.mvvmfx.SceneLifecycle}
+ * together with garbage collection.
+ *
+ * See {@link de.saxsys.mvvmfx.internal.viewloader.lifecycle.LifecycleTest#testGarbageCollection()}
+ */
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification;
\ No newline at end of file
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/NotificationWithoutLifecycleView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/NotificationWithoutLifecycleView.java
new file mode 100644
index 000000000..7cf65da57
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/NotificationWithoutLifecycleView.java
@@ -0,0 +1,10 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification_without_lifecycle;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+public class NotificationWithoutLifecycleView implements FxmlView {
+
+ @InjectViewModel
+ private NotificationWithoutLifecycleViewModel viewModel;
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/NotificationWithoutLifecycleViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/NotificationWithoutLifecycleViewModel.java
new file mode 100644
index 000000000..0c01ceb3f
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/NotificationWithoutLifecycleViewModel.java
@@ -0,0 +1,15 @@
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification_without_lifecycle;
+
+import de.saxsys.mvvmfx.ViewModel;
+
+public class NotificationWithoutLifecycleViewModel implements ViewModel {
+
+ int notificationCounter = 0;
+
+ public void initialize() {
+ subscribe("test", (key, payload) -> {
+ notificationCounter++;
+ });
+ }
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/package-info.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/package-info.java
new file mode 100644
index 000000000..9c50b96ad
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/lifecycle/example_notification_without_lifecycle/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * This package contains example view/viewModel
+ * for a test to reproduce the behavior of the {@link de.saxsys.mvvmfx.SceneLifecycle}
+ * together with garbage collection.
+ *
+ * See {@link de.saxsys.mvvmfx.internal.viewloader.lifecycle.LifecycleTest#testGarbageCollectionFailed()}
+ */
+package de.saxsys.mvvmfx.internal.viewloader.lifecycle.example_notification_without_lifecycle;
\ No newline at end of file
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/ContextTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/ContextTest.java
new file mode 100644
index 000000000..c3a329119
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/ContextTest.java
@@ -0,0 +1,33 @@
+package de.saxsys.mvvmfx.scopes.context;
+
+import de.saxsys.mvvmfx.FluentViewLoader;
+import de.saxsys.mvvmfx.scopes.context.views.ScopedFxmlView;
+import de.saxsys.mvvmfx.scopes.context.views.ScopedFxmlViewModel;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ContextTest {
+
+
+ /**
+ * Due to a bug (#426)
+ * the context wasn't injected when an existing ViewModel instance was provided to
+ * the {@link FluentViewLoader}.
+ */
+ @Test
+ public void testInjectContextWithProvidedViewModel() {
+
+ ScopedFxmlViewModel viewModel = new ScopedFxmlViewModel();
+
+ ScopedFxmlView codeBehind = FluentViewLoader
+ .fxmlView(ScopedFxmlView.class)
+ .viewModel(viewModel)
+ .load()
+ .getCodeBehind();
+
+ assertThat(codeBehind.context).isNotNull();
+
+ }
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/TestScope.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/TestScope.java
new file mode 100644
index 000000000..75efc5699
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/TestScope.java
@@ -0,0 +1,6 @@
+package de.saxsys.mvvmfx.scopes.context;
+
+import de.saxsys.mvvmfx.Scope;
+
+public class TestScope implements Scope {
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/views/ScopedFxmlView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/views/ScopedFxmlView.java
new file mode 100644
index 000000000..851044d79
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/views/ScopedFxmlView.java
@@ -0,0 +1,12 @@
+package de.saxsys.mvvmfx.scopes.context.views;
+
+import de.saxsys.mvvmfx.Context;
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectContext;
+
+public class ScopedFxmlView implements FxmlView {
+
+ @InjectContext
+ public Context context;
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/views/ScopedFxmlViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/views/ScopedFxmlViewModel.java
new file mode 100644
index 000000000..eb08babcb
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/context/views/ScopedFxmlViewModel.java
@@ -0,0 +1,6 @@
+package de.saxsys.mvvmfx.scopes.context.views;
+
+import de.saxsys.mvvmfx.ViewModel;
+
+public class ScopedFxmlViewModel implements ViewModel {
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/Example5App.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/Example5App.java
new file mode 100644
index 000000000..9992d89af
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/Example5App.java
@@ -0,0 +1,25 @@
+package de.saxsys.mvvmfx.scopes.example6;
+
+import de.saxsys.mvvmfx.FluentViewLoader;
+import javafx.application.Application;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+public class Example5App extends Application {
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(Stage primaryStage) throws Exception {
+
+ Parent parent = FluentViewLoader.fxmlView(ParentView.class)
+ .load()
+ .getView();
+
+ primaryStage.setScene(new Scene(parent));
+ primaryStage.show();
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/ParentView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/ParentView.java
new file mode 100644
index 000000000..afcb5f0e7
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/ParentView.java
@@ -0,0 +1,41 @@
+package de.saxsys.mvvmfx.scopes.example6;
+
+import de.saxsys.mvvmfx.FluentViewLoader;
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.Parent;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+import javafx.scene.control.TextField;
+
+public class ParentView implements FxmlView {
+
+ @FXML
+ public TextField newTabContent;
+ @FXML
+ public TabPane tabPane;
+
+ @InjectViewModel
+ private ParentViewModel viewModel;
+
+ public void initialize() {
+ newTabContent.textProperty().bindBidirectional(viewModel.inputProperty());
+
+ viewModel.setNewTabAction(scopes -> {
+ Parent tabView = FluentViewLoader.fxmlView(TabView.class)
+ .providedScopes(scopes)
+ .load()
+ .getView();
+
+ int nextIndex = tabPane.getTabs().size();
+ tabPane.getTabs().add(new Tab("Tab" + nextIndex, tabView));
+ });
+
+ }
+
+ public void addTab() {
+ viewModel.addTab();
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/ParentViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/ParentViewModel.java
new file mode 100644
index 000000000..11cecb1dd
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/ParentViewModel.java
@@ -0,0 +1,42 @@
+package de.saxsys.mvvmfx.scopes.example6;
+
+import de.saxsys.mvvmfx.Scope;
+import de.saxsys.mvvmfx.ViewModel;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class ParentViewModel implements ViewModel {
+
+ private StringProperty input = new SimpleStringProperty();
+
+
+ private Consumer> newTabAction;
+
+ public void addTab() {
+ if(newTabAction != null) {
+
+ String value = input.getValue();
+
+ if (value != null && !value.trim().isEmpty()) {
+
+ TabScope tabScope = new TabScope();
+ tabScope.contentProperty().set(value);
+
+ newTabAction.accept(Collections.singletonList(tabScope));
+ input.setValue("");
+ }
+ }
+ }
+
+ public void setNewTabAction(Consumer> newTabAction) {
+ this.newTabAction = newTabAction;
+ }
+
+ public StringProperty inputProperty() {
+ return input;
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabScope.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabScope.java
new file mode 100644
index 000000000..b2d93475a
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabScope.java
@@ -0,0 +1,15 @@
+package de.saxsys.mvvmfx.scopes.example6;
+
+import de.saxsys.mvvmfx.Scope;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+
+public class TabScope implements Scope {
+
+ private StringProperty content = new SimpleStringProperty();
+
+ public StringProperty contentProperty() {
+ return content;
+ }
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabView.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabView.java
new file mode 100644
index 000000000..fc4654499
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabView.java
@@ -0,0 +1,18 @@
+package de.saxsys.mvvmfx.scopes.example6;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+
+public class TabView implements FxmlView {
+ @FXML
+ public Label content;
+
+ @InjectViewModel
+ private TabViewModel viewModel;
+
+ public void initialize() {
+ content.textProperty().bind(viewModel.labelTextProperty());
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabViewModel.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabViewModel.java
new file mode 100644
index 000000000..f424b4dd3
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/scopes/example6/TabViewModel.java
@@ -0,0 +1,16 @@
+package de.saxsys.mvvmfx.scopes.example6;
+
+import de.saxsys.mvvmfx.InjectScope;
+import de.saxsys.mvvmfx.ViewModel;
+import javafx.beans.property.StringProperty;
+
+public class TabViewModel implements ViewModel {
+
+ @InjectScope
+ private TabScope scope;
+
+ public StringProperty labelTextProperty() {
+ return scope.contentProperty();
+ }
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/DelegateCommandTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/DelegateCommandTest.java
index 9fca9e374..63db62463 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/DelegateCommandTest.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/commands/DelegateCommandTest.java
@@ -20,17 +20,12 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+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;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javafx.application.Platform;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.value.ChangeListener;
-import javafx.concurrent.Worker.State;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
import org.junit.Rule;
import org.junit.Test;
@@ -38,7 +33,14 @@
import com.cedarsoft.test.utils.CatchAllExceptionsRule;
+import de.saxsys.mvvmfx.testingutils.FxTestingUtils;
import de.saxsys.mvvmfx.testingutils.jfxrunner.JfxRunner;
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.concurrent.Service;
+import javafx.concurrent.Task;
@@ -73,14 +75,13 @@ protected void action() {
assertFalse(delegateCommand.isNotExecutable());
}
-
@Test
public void executeSynchronousSucceeded() throws Exception {
BooleanProperty condition = new SimpleBooleanProperty(true);
BooleanProperty called = new SimpleBooleanProperty();
BooleanProperty succeeded = new SimpleBooleanProperty();
BooleanProperty failed = new SimpleBooleanProperty();
-
+
DelegateCommand delegateCommand = new DelegateCommand(() -> new Action() {
@Override
protected void action() {
@@ -95,7 +96,7 @@ protected void action() {
delegateCommand.setOnFailed(workerStateEvent -> {
failed.set(true);
});
-
+
// given
assertThat(called.get()).isFalse();
assertThat(succeeded.get()).isFalse();
@@ -109,42 +110,48 @@ protected void action() {
assertThat(succeeded.get()).isTrue();
assertThat(failed.get()).isFalse();
}
-
+
@Test
public void executeSynchronousFailed() throws Exception {
BooleanProperty condition = new SimpleBooleanProperty(true);
BooleanProperty called = new SimpleBooleanProperty();
BooleanProperty succeeded = new SimpleBooleanProperty();
BooleanProperty failed = new SimpleBooleanProperty();
-
+ final String exceptionReason = "Some reason";
+
DelegateCommand delegateCommand = new DelegateCommand(() -> new Action() {
@Override
protected void action() {
called.set(true);
- throw new RuntimeException("Some reason");
+ throw new RuntimeException(exceptionReason);
}
}, condition);
-
+
delegateCommand.setOnSucceeded(workerStateEvent -> {
succeeded.set(true);
});
-
+
delegateCommand.setOnFailed(workerStateEvent -> {
failed.set(true);
});
-
+
// given
assertThat(called.get()).isFalse();
assertThat(succeeded.get()).isFalse();
assertThat(failed.get()).isFalse();
-
+
// when
delegateCommand.execute();
-
+
// then
assertThat(called.get()).isTrue();
assertThat(succeeded.get()).isFalse();
assertThat(failed.get()).isTrue();
+
+ assertThat(delegateCommand.exceptionProperty().get())
+ .isNotNull()
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage(exceptionReason);
}
@Test(expected = RuntimeException.class)
@@ -230,26 +237,176 @@ protected void action() throws Exception {
command.execute();
stepOne.get(1, TimeUnit.SECONDS);
- Platform.runLater(() ->
- assertThat(command.getProgress()).isEqualTo(0.0));
+ Platform.runLater(() -> assertThat(command.getProgress()).isEqualTo(0.0));
stepTwo.get(1, TimeUnit.SECONDS);
- Platform.runLater(() ->
- assertThat(command.getProgress()).isEqualTo(0.3, offset(0.1)));
+ Platform.runLater(() -> assertThat(command.getProgress()).isEqualTo(0.3, offset(0.1)));
stepThree.get(1, TimeUnit.SECONDS);
- Platform.runLater(() ->
- assertThat(command.getProgress()).isEqualTo(0.6, offset(0.1)));
+ Platform.runLater(() -> assertThat(command.getProgress()).isEqualTo(0.6, offset(0.1)));
stepFour.get(1, TimeUnit.SECONDS);
- Platform.runLater(() ->
- assertThat(command.getProgress()).isEqualTo(1, offset(0.1)));
+ Platform.runLater(() -> assertThat(command.getProgress()).isEqualTo(1, offset(0.1)));
// sleep to prevent the Junit thread from exiting
// before eventual assertion errors from the JavaFX Thread are detected
sleep(500);
}
+ /**
+ * This test verifies the behaviour when the delegate command is restarted.
+ *
+ * It defines a test that is both executed with a pure JavaFX {@link Service} and the mvvmFX
+ * {@link DelegateCommand}. This way we can verify that both implementations are behaving equally.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testRestartAsync() throws Exception {
+ // these lists are used to keep track which actions are called, succeeded etc.
+ // each action has a number that will be added to these lists
+ List called = new ArrayList<>();
+ List succeeded = new ArrayList<>();
+ List cancelled = new ArrayList<>();
+ List interrupted = new ArrayList<>();
+
+
+ // A special action that will do nothing but wait for 500 ms when called.
+ class MyAction extends Action {
+
+ private final int number;
+
+ MyAction(int number) {
+ this.number = number;
+ }
+
+ @Override
+ protected void action() throws Exception {
+ called.add(this.number);
+
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ // keep track if this action is interrupted
+ interrupted.add(this.number);
+ }
+ }
+
+ @Override
+ protected void succeeded() {
+ succeeded.add(this.number);
+ }
+
+ @Override
+ protected void cancelled() {
+ cancelled.add(this.number);
+ }
+ }
+
+ // A pure JavaFX service that used our test action class as task
+ Service jfxService = new Service() {
+ int counter = 0;
+
+ @Override
+ protected Task createTask() {
+ // as "Action" extends from "Task" we can use it here
+ MyAction myAction = new MyAction(counter);
+ counter++;
+ return myAction;
+ }
+ };
+
+
+ // A mvvmFX command that uses the action class as task
+ DelegateCommand command = new DelegateCommand(new Supplier() {
+ int counter = 0;
+
+ @Override
+ public Action get() {
+ MyAction myAction = new MyAction(counter);
+ counter++;
+ return myAction;
+ }
+ }, true);
+
+
+ // The actual testing steps are encapsulated in a function (BiConsumer) that
+ // takes two runnables as argument. The first runnable starts the service for the first time.
+ // The second runnable restarts the service.
+ // This way we can reuse the same testing steps on both variants.
+ BiConsumer test = (startService, restartService) -> {
+ called.clear();
+ succeeded.clear();
+ cancelled.clear();
+ interrupted.clear();
+
+ startService.run();
+ sleep(100);
+ FxTestingUtils.waitForUiThread();
+
+ assertThat(called).containsExactly(0); // the first action is called
+ assertThat(succeeded).isEmpty();
+ assertThat(cancelled).isEmpty();
+ assertThat(interrupted).isEmpty();
+
+
+ // restart and all other interactions with the service have to be done
+ // on the UI-thread. Therefore we use Platform.runLater
+ Platform.runLater(restartService);
+ sleep(300);
+ // We need to wait for the UI-Thread to execute all enqueued runnables
+ FxTestingUtils.waitForUiThread();
+
+ assertThat(called).containsExactly(0, 1); // now the second action is called too
+ assertThat(succeeded).isEmpty();
+ assertThat(cancelled).containsExactly(0); // the first one is cancelled ...
+ assertThat(interrupted).containsExactly(0); // and interrupted
+
+ // the normal execution of the action takes 500 ms so we need to wait a little longer
+ sleep(1000);
+ FxTestingUtils.waitForUiThread();
+
+ assertThat(called).containsExactly(0, 1);
+ assertThat(succeeded).containsExactly(1); // now the second action was finished successfully
+ assertThat(cancelled).containsExactly(0);
+ assertThat(interrupted).containsExactly(0);
+ };
+
+ // run the test on both the pure JavaFX service and the delegate command
+
+ // JavaFX Service uses "start" for initial startup and "restart" for the second start
+ test.accept(jfxService::start, jfxService::restart);
+
+ // DelegateCommand uses "execute" both times
+ test.accept(command::execute, command::execute);
+ }
+
+ @Test
+ public void testCheckExceptionProperty() {
+ DelegateCommand delegateCommand = new DelegateCommand(() -> {
+ return null;
+ });
+ DelegateCommand.checkExceptionProperty(delegateCommand.exceptionProperty());
+ }
+
+ @Test
+ public void testSynchronousActionException() {
+ final String exceptionReason = "Some reason";
+
+ Action action = new Action() {
+ @Override
+ protected void action() throws Exception {
+ throw new RuntimeException(exceptionReason);
+ }
+ };
+
+ DelegateCommand delegateCommand = new DelegateCommand(() -> action);
+ delegateCommand.execute();
+ assertThat(action.getException())
+ .isNotNull()
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage(exceptionReason);
+ }
private void sleep(long millis) {
try {
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/itemlist/ListTransformationTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/itemlist/ListTransformationTest.java
new file mode 100644
index 000000000..ebc1cc039
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/itemlist/ListTransformationTest.java
@@ -0,0 +1,29 @@
+package de.saxsys.mvvmfx.utils.itemlist;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ListTransformationTest {
+
+ @Test
+ public void testListTransformation() {
+ ListTransformation transformation = new ListTransformation<>(String::length);
+
+ assertThat(transformation.getSourceList()).isEmpty();
+ assertThat(transformation.getTargetList()).isEmpty();
+
+ transformation.getSourceList().add("hello");
+
+ assertThat(transformation.getTargetList()).contains(5);
+
+
+ transformation.getSourceList().add("world");
+ assertThat(transformation.getTargetList()).contains(5, 5);
+
+
+ transformation.getSourceList().add("test");
+ assertThat(transformation.getTargetList()).contains(5, 5, 4);
+ }
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/FieldMethodOverloadingTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/FieldMethodOverloadingTest.java
new file mode 100644
index 000000000..06db12d7e
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/FieldMethodOverloadingTest.java
@@ -0,0 +1,287 @@
+/*******************************************************************************
+ * Copyright 2015 Alexander Casall, Manuel Mauky
+ *
+ * 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 de.saxsys.mvvmfx.utils.mapping;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.FloatProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.LongProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.Property;
+import javafx.beans.property.StringProperty;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * This test class is not indented to verify the behaviour of the {@link ModelWrapper} class in detail with all
+ * possible edge cases. For this purpose there is the test class {@link ModelWrapperTest} that has detailed test cases
+ * for the behaviour of the wrapper.
+ *
+ * The intention of this test class is instead to verify the correct overloading of the field-methods for all property types.
+ * This is needed because most of the fields-methods are more or less the same and therefor are copy+pasted.
+ * However, due to the poor type system of Java this copy+paste work is inevitable.
+ * As most of the implementation code for most of the actual features is reused internally it is safe to only test
+ * the implementation details for some examples like it is done in {@link ModelWrapperTest}.
+ * The overloaded methods mostly pass through the given arguments.
+ * However it is possible that sometimes
+ *
+ * There are 2 use cases that we are checking here:
+ * 1. Verify that all overloaded methods have the correct return type.
+ * Because of Javas poor type system this isn't always as easy as it sounds.
+ * 2. Verify that the basic handling of default values and identifiers works for all overloaded methods.
+ * The overloaded methods pass through most of the arguments but it can happen that for one of the methods an argument
+ * is missing due to copy+paste errors (see issue 393 for an example)
+ * or due to a refactoring. This test class should point at such "simple" errors.
+ *
+ */
+public class FieldMethodOverloadingTest {
+
+ private ModelWrapper wrapper;
+
+ @Before
+ public void setup() {
+ wrapper = new ModelWrapper<>();
+ }
+
+
+ /**
+ * A test helper method that verifies some default behaviour of all properties.
+ */
+ private void verify(T defaultValue,
+ T alternativeValue,
+ Property beanField,
+ Property fxField,
+ Property beanFieldDefault,
+ Property fxFieldDefault,
+ Property idBeanField,
+ Property idFxField,
+ Property idBeanFieldDefault,
+ Property idFxFieldDefault) {
+
+ verifyDefaultValue(beanFieldDefault, defaultValue, alternativeValue);
+ verifyDefaultValue(fxFieldDefault, defaultValue, alternativeValue);
+ verifyDefaultValue(idBeanFieldDefault, defaultValue, alternativeValue);
+ verifyDefaultValue(idFxFieldDefault, defaultValue, alternativeValue);
+
+ verifyId(idBeanField, "idBeanField");
+ verifyId(idFxField, "idFxField");
+ verifyId(idBeanFieldDefault, "idBeanFieldDefault");
+ verifyId(idFxFieldDefault, "idFxFieldDefault");
+
+ }
+
+ private void verifyDefaultValue(Property property, T defaultValue, T otherValue) {
+ property.setValue(otherValue);
+
+ wrapper.commit();
+ assertThat(property.getValue()).isEqualTo(otherValue);
+
+ wrapper.reset();
+ assertThat(property.getValue()).isEqualTo(defaultValue);
+ }
+
+ private void verifyId(Property property, String id) {
+ assertThat(property.getName()).isEqualTo(id);
+ }
+
+
+ @Test
+ public void integerProperty() {
+ int defaultValue = 5;
+
+ final IntegerProperty beanField = wrapper.field(ExampleModel::getInteger, ExampleModel::setInteger);
+ final IntegerProperty fxField = wrapper.field(ExampleModel::integerProperty);
+ final IntegerProperty beanFieldDefault = wrapper.field(ExampleModel::getInteger, ExampleModel::setInteger, defaultValue);
+ final IntegerProperty fxFieldDefault = wrapper.field(ExampleModel::integerProperty, defaultValue);
+
+ final IntegerProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getInteger, ExampleModel::setInteger);
+ final IntegerProperty idFxField = wrapper.field("idFxField", ExampleModel::integerProperty);
+ final IntegerProperty idBeanFieldDefault = wrapper.field("idBeanFieldDefault", ExampleModel::getInteger,
+ ExampleModel::setInteger, defaultValue);
+ final IntegerProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::integerProperty, defaultValue);
+
+ verify(defaultValue, 10, beanField, fxField, beanFieldDefault, fxFieldDefault, idBeanField, idFxField, idBeanFieldDefault, idFxFieldDefault);
+ }
+
+ @Test
+ public void doubleProperty() {
+ double defaultValue = 5.1;
+
+ final DoubleProperty beanField = wrapper.field(ExampleModel::getDouble, ExampleModel::setDouble);
+ final DoubleProperty fxField = wrapper.field(ExampleModel::doubleProperty);
+ final DoubleProperty beanFieldDefault = wrapper.field(ExampleModel::getDouble, ExampleModel::setDouble, defaultValue);
+ final DoubleProperty fxFieldDefault = wrapper.field(ExampleModel::doubleProperty, defaultValue);
+
+ final DoubleProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getDouble, ExampleModel::setDouble);
+ final DoubleProperty idFxField = wrapper.field("idFxField", ExampleModel::doubleProperty);
+ final DoubleProperty idBeanFieldDefault = wrapper.field("idBeanFieldDefault", ExampleModel::getDouble,
+ ExampleModel::setDouble, defaultValue);
+ final DoubleProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::doubleProperty, defaultValue);
+
+ verify(defaultValue, 10.71, beanField, fxField, beanFieldDefault, fxFieldDefault, idBeanField, idFxField, idBeanFieldDefault, idFxFieldDefault);
+ }
+
+
+ @Test
+ public void longProperty() {
+ long defaultValue = 5L;
+
+ final LongProperty beanField = wrapper.field(ExampleModel::getLong, ExampleModel::setLong);
+ final LongProperty fxField = wrapper.field(ExampleModel::longProperty);
+ final LongProperty beanFieldDefault = wrapper.field(ExampleModel::getLong, ExampleModel::setLong, defaultValue);
+ final LongProperty fxFieldDefault = wrapper.field(ExampleModel::longProperty, defaultValue);
+
+ final LongProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getLong, ExampleModel::setLong);
+ final LongProperty idFxField = wrapper.field("idFxField", ExampleModel::longProperty);
+ final LongProperty idBeanFieldDefault = wrapper
+ .field("idBeanFieldDefault", ExampleModel::getLong, ExampleModel::setLong, defaultValue);
+ final LongProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::longProperty, defaultValue);
+
+ verify(defaultValue, 10L, beanField, fxField, beanFieldDefault, fxFieldDefault, idBeanField, idFxField, idBeanFieldDefault, idFxFieldDefault);
+ }
+
+
+ @Test
+ public void floatProperty() {
+ float defaultValue = 5.1F;
+
+ final FloatProperty beanField = wrapper.field(ExampleModel::getFloat, ExampleModel::setFloat);
+ final FloatProperty fxField = wrapper.field(ExampleModel::floatProperty);
+ final FloatProperty beanFieldDefault = wrapper.field(ExampleModel::getFloat, ExampleModel::setFloat, defaultValue);
+ final FloatProperty fxFieldDefault = wrapper.field(ExampleModel::floatProperty, defaultValue);
+
+ final FloatProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getFloat, ExampleModel::setFloat);
+ final FloatProperty idFxField = wrapper.field("idFxField", ExampleModel::floatProperty);
+ final FloatProperty idBeanFieldDefault = wrapper.field("idBeanFieldDefault", ExampleModel::getFloat,
+ ExampleModel::setFloat, defaultValue);
+ final FloatProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::floatProperty, defaultValue);
+
+ verify(defaultValue, 10.52F, beanField, fxField, beanFieldDefault, fxFieldDefault, idBeanField, idFxField, idBeanFieldDefault, idFxFieldDefault);
+ }
+
+
+ @Test
+ public void booleanProperty() {
+ boolean defaultValue = true;
+
+ final BooleanProperty beanField = wrapper.field(ExampleModel::getBoolean, ExampleModel::setBoolean);
+ final BooleanProperty fxField = wrapper.field(ExampleModel::booleanProperty);
+ final BooleanProperty beanFieldDefault = wrapper
+ .field(ExampleModel::getBoolean, ExampleModel::setBoolean, defaultValue);
+ final BooleanProperty fxFieldDefault = wrapper.field(ExampleModel::booleanProperty, defaultValue);
+
+ final BooleanProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getBoolean, ExampleModel::setBoolean);
+ final BooleanProperty idFxField = wrapper.field("idFxField", ExampleModel::booleanProperty);
+ final BooleanProperty idBeanFieldDefault = wrapper.field("idBeanFieldDefault", ExampleModel::getBoolean,
+ ExampleModel::setBoolean, defaultValue);
+ final BooleanProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::booleanProperty, defaultValue);
+
+ verify(defaultValue, false, beanField, fxField, beanFieldDefault, fxFieldDefault, idBeanField, idFxField, idBeanFieldDefault, idFxFieldDefault);
+ }
+
+
+ @Test
+ public void stringProperty() {
+ String defaultValue = "test";
+
+ final StringProperty beanField = wrapper.field(ExampleModel::getString, ExampleModel::setString);
+ final StringProperty fxField = wrapper.field(ExampleModel::stringProperty);
+ final StringProperty beanFieldDefault = wrapper.field(ExampleModel::getString, ExampleModel::setString, defaultValue);
+ final StringProperty fxFieldDefault = wrapper.field(ExampleModel::stringProperty, defaultValue);
+
+ final StringProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getString, ExampleModel::setString);
+ final StringProperty idFxField = wrapper.field("idFxField", ExampleModel::stringProperty);
+ final StringProperty idBeanFieldDefault = wrapper.field("idBeanFieldDefault", ExampleModel::getString,
+ ExampleModel::setString, defaultValue);
+ final StringProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::stringProperty, defaultValue);
+
+ verify(defaultValue, "hello world", beanField, fxField, beanFieldDefault, fxFieldDefault, idBeanField, idFxField, idBeanFieldDefault, idFxFieldDefault);
+
+ }
+
+ @Test
+ public void objectProperty() {
+ Person defaultValue = new Person();
+ defaultValue.setName("Luise");
+
+ final ObjectProperty beanField = wrapper.field(ExampleModel::getObject, ExampleModel::setObject);
+ final ObjectProperty fxField = wrapper.field(ExampleModel::objectProperty);
+ final ObjectProperty beanFieldDefault = wrapper.field(ExampleModel::getObject, ExampleModel::setObject,
+ defaultValue);
+ final ObjectProperty fxFieldDefault = wrapper.field(ExampleModel::objectProperty, defaultValue);
+
+ final ObjectProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getObject,
+ ExampleModel::setObject);
+ final ObjectProperty idFxField = wrapper.field("idFxField", ExampleModel::objectProperty);
+ final ObjectProperty idBeanFieldDefault = wrapper.field("idBeanFieldDefault", ExampleModel::getObject,
+ ExampleModel::setObject, defaultValue);
+ final ObjectProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::objectProperty,
+ defaultValue);
+
+ Person alternativeValue = new Person();
+ alternativeValue.setName("Horst");
+
+ verify(defaultValue, alternativeValue, beanField, fxField, beanFieldDefault, fxFieldDefault, idBeanField, idFxField, idBeanFieldDefault, idFxFieldDefault);
+ }
+
+ @Test
+ public void listProperty() {
+ List defaultValue = Collections.emptyList();
+ final ListProperty beanField = wrapper.field(ExampleModel::getList, ExampleModel::setList);
+ final ListProperty fxField = wrapper.field(ExampleModel::listProperty);
+ final ListProperty beanFieldDefault = wrapper.field(ExampleModel::getList, ExampleModel::setList,
+ defaultValue);
+ final ListProperty fxFieldDefault = wrapper.field(ExampleModel::listProperty, defaultValue);
+
+ final ListProperty idBeanField = wrapper.field("idBeanField", ExampleModel::getList, ExampleModel::setList);
+ final ListProperty idFxField = wrapper.field("idFxField", ExampleModel::listProperty);
+ final ListProperty idBeanFieldDefault = wrapper.field("idBeanFieldDefault", ExampleModel::getList,
+ ExampleModel::setList, defaultValue);
+ final ListProperty idFxFieldDefault = wrapper.field("idFxFieldDefault", ExampleModel::listProperty,
+ defaultValue);
+
+
+ // for listProperty we can't use the other "verify" method because of type mismatch.
+ verifyId(idBeanField, "idBeanField");
+ verifyId(idFxField, "idFxField");
+ verifyId(idBeanFieldDefault, "idBeanFieldDefault");
+ verifyId(idFxFieldDefault, "idFxFieldDefault");
+
+ List alternativeValue = Arrays.asList("1", "2");
+ verifyDefaultValueList(beanFieldDefault, defaultValue, alternativeValue);
+ verifyDefaultValueList(fxFieldDefault, defaultValue, alternativeValue);
+ verifyDefaultValueList(idBeanFieldDefault, defaultValue, alternativeValue);
+ verifyDefaultValueList(idFxFieldDefault, defaultValue, alternativeValue);
+ }
+
+ private void verifyDefaultValueList(ListProperty property, List defaultValue, List otherValue) {
+ property.addAll(otherValue);
+
+ wrapper.commit();
+ assertThat(property).containsAll(otherValue);
+
+ wrapper.reset();
+ assertThat(property).containsAll(defaultValue);
+ }
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java
index 3bc2b2fe4..45a6d674b 100644
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java
@@ -15,22 +15,64 @@
******************************************************************************/
package de.saxsys.mvvmfx.utils.mapping;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.setAllowExtractingPrivateFields;
-
-import javafx.beans.property.SimpleIntegerProperty;
-import org.junit.Test;
-
-import java.util.Arrays;
-
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
public class ModelWrapperTest {
+ @Test
+ public void testCopyValuesTo() {
+ // given
+ Person personA = new Person();
+ personA.setName("horst");
+ personA.setAge(32);
+ personA.setNicknames(Collections.singletonList("captain"));
+
+ ModelWrapper personWrapper = new ModelWrapper<>(personA);
+
+ final StringProperty nameProperty = personWrapper.field(Person::getName, Person::setName);
+ final IntegerProperty ageProperty = personWrapper.field(Person::getAge, Person::setAge);
+ final ListProperty nicknamesProperty = personWrapper.field(Person::getNicknames, Person::setNicknames);
+
+ assertThat(nameProperty.getValue()).isEqualTo("horst");
+ assertThat(ageProperty.getValue()).isEqualTo(32);
+ assertThat(nicknamesProperty.getValue()).containsOnly("captain");
+
+ // when
+ Person personB = new Person();
+ personB.setName("Luise");
+ personB.setAge(23);
+ personB.setNicknames(Collections.singletonList("lui"));
+
+ personWrapper.copyValuesTo(personB);
+
+ // then
+ // person b has new values
+ assertThat(personB.getName()).isEqualTo("horst");
+ assertThat(personB.getAge()).isEqualTo(32);
+ assertThat(personB.getNicknames()).containsExactly("captain");
+
+ // the properties have still the old values
+ assertThat(nameProperty.getValue()).isEqualTo("horst");
+ assertThat(ageProperty.getValue()).isEqualTo(32);
+ assertThat(nicknamesProperty.getValue()).containsOnly("captain");
+
+ // and of cause the old person a has it's old values too
+ assertThat(personA.getName()).isEqualTo("horst");
+ assertThat(personA.getAge()).isEqualTo(32);
+ assertThat(personA.getNicknames()).containsExactly("captain");
+
+ }
+
@Test
public void testWithGetterAndSetter() {
@@ -236,6 +278,13 @@ public void testIdentifiedFields() {
assertThat(nameProperty).isSameAs(nameProperty2);
assertThat(ageProperty).isSameAs(ageProperty2);
assertThat(nicknamesProperty).isSameAs(nicknamesProperty2);
+
+
+ // with identified fields the "name" of the created properties should be set
+ assertThat(nameProperty.getName()).isEqualTo("name");
+ assertThat(ageProperty.getName()).isEqualTo("age");
+ assertThat(nicknamesProperty.getName()).isEqualTo("nicknames");
+
}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ReturnTypeTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ReturnTypeTest.java
deleted file mode 100644
index 6512cc890..000000000
--- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ReturnTypeTest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*******************************************************************************
- * Copyright 2015 Alexander Casall, Manuel Mauky
- *
- * 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 de.saxsys.mvvmfx.utils.mapping;
-
-import javafx.beans.property.*;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-/**
- * This test is used to check the return values when fields are mapped. See Issue 211 https://github.com/sialcasa/mvvmFX/issues/211
- */
-public class ReturnTypeTest {
-
- private ModelWrapper wrapper;
-
- private ExampleModel model;
-
- @Before
- public void setup() {
- wrapper = new ModelWrapper<>();
- model = new ExampleModel();
- }
-
-
- @Test
- public void integerProperty() {
- final IntegerProperty beanField = wrapper.field(ExampleModel::getInteger, ExampleModel::setInteger);
- final IntegerProperty fxField = wrapper.field(ExampleModel::integerProperty);
- final IntegerProperty beanFieldDefault = wrapper.field(ExampleModel::getInteger, ExampleModel::setInteger, 5);
- final IntegerProperty fxFieldDefault = wrapper.field(ExampleModel::integerProperty, 5);
-
- final IntegerProperty idBeanField = wrapper.field("int1", ExampleModel::getInteger, ExampleModel::setInteger);
- final IntegerProperty idFxField = wrapper.field("int2", ExampleModel::integerProperty);
- final IntegerProperty idBeanFieldDefault = wrapper.field("int3", ExampleModel::getInteger,
- ExampleModel::setInteger, 5);
- final IntegerProperty idFxFieldDefault = wrapper.field("int4", ExampleModel::integerProperty, 5);
-
-
- }
-
- @Test
- public void doubleProperty() {
- final DoubleProperty beanField = wrapper.field(ExampleModel::getDouble, ExampleModel::setDouble);
- final DoubleProperty fxField = wrapper.field(ExampleModel::doubleProperty);
- final DoubleProperty beanFieldDefault = wrapper.field(ExampleModel::getDouble, ExampleModel::setDouble, 5.1);
- final DoubleProperty fxFieldDefault = wrapper.field(ExampleModel::doubleProperty, 5.1);
-
- final DoubleProperty idBeanField = wrapper.field("double1", ExampleModel::getDouble, ExampleModel::setDouble);
- final DoubleProperty idFxField = wrapper.field("double2", ExampleModel::doubleProperty);
- final DoubleProperty idBeanFieldDefault = wrapper.field("double3", ExampleModel::getDouble,
- ExampleModel::setDouble, 5.1);
- final DoubleProperty idFxFieldDefault = wrapper.field("double4", ExampleModel::doubleProperty, 5.1);
- }
-
-
- @Test
- public void longProperty() {
- final LongProperty beanField = wrapper.field(ExampleModel::getLong, ExampleModel::setLong);
- final LongProperty fxField = wrapper.field(ExampleModel::longProperty);
- final LongProperty beanFieldDefault = wrapper.field(ExampleModel::getLong, ExampleModel::setLong, 5l);
- final LongProperty fxFieldDefault = wrapper.field(ExampleModel::longProperty, 5l);
-
- final LongProperty idBeanField = wrapper.field("long1", ExampleModel::getLong, ExampleModel::setLong);
- final LongProperty idFxField = wrapper.field("long2", ExampleModel::longProperty);
- final LongProperty idBeanFieldDefault = wrapper
- .field("long3", ExampleModel::getLong, ExampleModel::setLong, 5l);
- final LongProperty idFxFieldDefault = wrapper.field("long4", ExampleModel::longProperty, 5l);
- }
-
-
- @Test
- public void floatProperty() {
- final FloatProperty beanField = wrapper.field(ExampleModel::getFloat, ExampleModel::setFloat);
- final FloatProperty fxField = wrapper.field(ExampleModel::floatProperty);
- final FloatProperty beanFieldDefault = wrapper.field(ExampleModel::getFloat, ExampleModel::setFloat, 5.1f);
- final FloatProperty fxFieldDefault = wrapper.field(ExampleModel::floatProperty, 5.1f);
-
- final FloatProperty idBeanField = wrapper.field("float1", ExampleModel::getFloat, ExampleModel::setFloat);
- final FloatProperty idFxField = wrapper.field("float2", ExampleModel::floatProperty);
- final FloatProperty idBeanFieldDefault = wrapper.field("float3", ExampleModel::getFloat,
- ExampleModel::setFloat, 5.1f);
- final FloatProperty idFxFieldDefault = wrapper.field("float4", ExampleModel::floatProperty, 5.1f);
- }
-
-
- @Test
- public void booleanProperty() {
- final BooleanProperty beanField = wrapper.field(ExampleModel::getBoolean, ExampleModel::setBoolean);
- final BooleanProperty fxField = wrapper.field(ExampleModel::booleanProperty);
- final BooleanProperty beanFieldDefault = wrapper
- .field(ExampleModel::getBoolean, ExampleModel::setBoolean, true);
- final BooleanProperty fxFieldDefault = wrapper.field(ExampleModel::booleanProperty, true);
-
- final BooleanProperty idBeanField = wrapper.field("bool1", ExampleModel::getBoolean, ExampleModel::setBoolean);
- final BooleanProperty idFxField = wrapper.field("bool2", ExampleModel::booleanProperty);
- final BooleanProperty idBeanFieldDefault = wrapper.field("bool3", ExampleModel::getBoolean,
- ExampleModel::setBoolean, true);
- final BooleanProperty idFxFieldDefault = wrapper.field("bool4", ExampleModel::booleanProperty, true);
- }
-
-
- @Test
- public void stringProperty() {
- final StringProperty beanField = wrapper.field(ExampleModel::getString, ExampleModel::setString);
- final StringProperty fxField = wrapper.field(ExampleModel::stringProperty);
- final StringProperty beanFieldDefault = wrapper.field(ExampleModel::getString, ExampleModel::setString, "test");
- final StringProperty fxFieldDefault = wrapper.field(ExampleModel::stringProperty, "test");
-
- final StringProperty idBeanField = wrapper.field("string1", ExampleModel::getString, ExampleModel::setString);
- final StringProperty idFxField = wrapper.field("string2", ExampleModel::stringProperty);
- final StringProperty idBeanFieldDefault = wrapper.field("string3", ExampleModel::getString,
- ExampleModel::setString, "test");
- final StringProperty idFxFieldDefault = wrapper.field("string4", ExampleModel::stringProperty, "test");
- }
-
- @Test
- public void objectProperty() {
- final ObjectProperty beanField = wrapper.field(ExampleModel::getObject, ExampleModel::setObject);
- final ObjectProperty fxField = wrapper.field(ExampleModel::objectProperty);
- final ObjectProperty beanFieldDefault = wrapper.field(ExampleModel::getObject, ExampleModel::setObject,
- new Person());
- final ObjectProperty fxFieldDefault = wrapper.field(ExampleModel::objectProperty, new Person());
-
- final ObjectProperty idBeanField = wrapper.field("obj1", ExampleModel::getObject,
- ExampleModel::setObject);
- final ObjectProperty idFxField = wrapper.field("obj2", ExampleModel::objectProperty);
- final ObjectProperty idBeanFieldDefault = wrapper.field("obj3", ExampleModel::getObject,
- ExampleModel::setObject, new Person());
- final ObjectProperty idFxFieldDefault = wrapper.field("obj4", ExampleModel::objectProperty,
- new Person());
- }
-
- @Test
- public void listProperty() {
- final ListProperty beanField = wrapper.field(ExampleModel::getList, ExampleModel::setList);
- final ListProperty fxField = wrapper.field(ExampleModel::listProperty);
- final ListProperty beanFieldDefault = wrapper.field(ExampleModel::getList, ExampleModel::setList,
- Collections.emptyList());
- final ListProperty fxFieldDefault = wrapper.field(ExampleModel::listProperty, Arrays.asList());
-
- final ListProperty idBeanField = wrapper.field("list1", ExampleModel::getList, ExampleModel::setList);
- final ListProperty idFxField = wrapper.field("list2", ExampleModel::listProperty);
- final ListProperty