Discover custom ViewModels and validate Computed bindings in shared hosts
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
@de.neitzel.fx.view.ViewModelClass(BadgeViewModel.class)
|
||||
public class BadgeCard extends javafx.scene.layout.VBox {
|
||||
static {
|
||||
System.setProperty("fixture.badgecard.initialized", "yes");
|
||||
}
|
||||
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String caption() {
|
||||
throw new AssertionError("Metadata must not invoke Component method");
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package fixture.components;
|
||||
|
||||
public class BadgeViewModel extends de.neitzel.fx.view.ViewModel {
|
||||
public BadgeViewModel(fixture.model.Address model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
public javafx.beans.property.StringProperty badgeProperty() {
|
||||
throw new AssertionError("Metadata must not invoke accessor");
|
||||
}
|
||||
}
|
||||
+12
@@ -3,6 +3,18 @@ package fixture.components;
|
||||
@jakarta.inject.Singleton
|
||||
@de.neitzel.fx.view.ViewModelClass(PersonViewModel.class)
|
||||
public class PersonController implements de.neitzel.fx.view.HasViewModel {
|
||||
static {
|
||||
System.setProperty("fixture.controller.initialized", "yes");
|
||||
}
|
||||
|
||||
public Integer invalidComputed() {
|
||||
throw new AssertionError("Validation must not invoke methods");
|
||||
}
|
||||
|
||||
public Object uncertainComputed() {
|
||||
return "Uncertain";
|
||||
}
|
||||
|
||||
public final Greeting greeting;
|
||||
public de.neitzel.fx.view.ViewModel received;
|
||||
|
||||
|
||||
+15
@@ -1,6 +1,21 @@
|
||||
package fixture.components;
|
||||
|
||||
public class PersonViewModel extends de.neitzel.fx.view.ViewModel {
|
||||
static {
|
||||
System.setProperty("fixture.viewmodel.initialized", "yes");
|
||||
}
|
||||
|
||||
private final javafx.beans.property.StringProperty badge =
|
||||
new javafx.beans.property.SimpleStringProperty("Custom badge");
|
||||
|
||||
public javafx.beans.property.StringProperty badgeProperty() {
|
||||
return badge;
|
||||
}
|
||||
|
||||
public javafx.beans.property.IntegerProperty nameProperty() {
|
||||
throw new AssertionError("Model name must win");
|
||||
}
|
||||
|
||||
public PersonViewModel(Object model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
@de.neitzel.fx.view.ViewModelClass(ReplacementViewModel.class)
|
||||
public class ReplacementController {
|
||||
public String caption() {
|
||||
return "Replacement computed";
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package fixture.components;
|
||||
|
||||
public class ReplacementViewModel {
|
||||
static {
|
||||
System.setProperty("fixture.replacement.initialized", "yes");
|
||||
}
|
||||
|
||||
private final javafx.beans.property.StringProperty badge =
|
||||
new javafx.beans.property.SimpleStringProperty("Replacement badge");
|
||||
|
||||
public ReplacementViewModel(fixture.model.Person model) {}
|
||||
|
||||
public javafx.beans.property.StringProperty badgeProperty() {
|
||||
return badge;
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<?neitzel-model fixture.model.Address?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="javafx.scene.layout.VBox"><fx:define><de.neitzel.fx.view.BindingList><de.neitzel.fx.view.DirectBinding control="badge" property="text" path="badge" direction="view"/><de.neitzel.fx.view.ComputedBinding control="caption" property="text" method="#caption"/></de.neitzel.fx.view.BindingList></fx:define><Label fx:id="badge"/><Label fx:id="caption"/></fx:root>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?neitzel-model fixture.model.Person?>
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.TextField?><?import javafx.scene.control.Label?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fixture.components.PersonController">
|
||||
<fx:define><de.neitzel.fx.view.BindingList>
|
||||
<de.neitzel.fx.view.DirectBinding control="badge" property="text" path="badge" direction="both"/>
|
||||
<de.neitzel.fx.view.ComputedBinding control="computed" property="text" method="#greeting"/>
|
||||
</de.neitzel.fx.view.BindingList></fx:define>
|
||||
<TextField fx:id="badge"/><Label fx:id="computed"/>
|
||||
</VBox>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?neitzel-model fixture.model.Person?>
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.TextField?><?import javafx.scene.control.Label?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fixture.components.ReplacementController">
|
||||
<fx:define><de.neitzel.fx.view.BindingList>
|
||||
<de.neitzel.fx.view.DirectBinding control="badge" property="text" path="badge" direction="both"/>
|
||||
<de.neitzel.fx.view.ComputedBinding control="computed" property="text" method="#caption"/>
|
||||
</de.neitzel.fx.view.BindingList></fx:define>
|
||||
<TextField fx:id="badge"/><Label fx:id="computed"/>
|
||||
</VBox>
|
||||
+49
@@ -41,6 +41,10 @@ public final class PreviewChecks {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String mode = args[0];
|
||||
if (mode.equals("viewmodels")) {
|
||||
ViewModelRuntimeChecks.run();
|
||||
return;
|
||||
}
|
||||
if (mode.equals("unstable")
|
||||
|| mode.equals("full")
|
||||
|| mode.equals("full-empty")
|
||||
@@ -129,6 +133,51 @@ public final class PreviewChecks {
|
||||
System.getProperty("fixture.converter.initialized") == null,
|
||||
"Discovery initialized Converter");
|
||||
}
|
||||
check(
|
||||
metadata.viewModel().equals("fixture.components.PersonViewModel"),
|
||||
"Explicit custom ViewModel");
|
||||
check(
|
||||
metadata.attributes().stream().anyMatch(a -> a.path().equals("badge")),
|
||||
"Custom property absent");
|
||||
check(
|
||||
metadata.computed().stream().anyMatch(m -> m.method().equals("#greeting")),
|
||||
"Computed method absent");
|
||||
for (String resource : java.util.List.of("Replacement", "BadgeCard")) {
|
||||
String path = "fixture/components/" + resource + ".fxml";
|
||||
try (var in = loader.getResourceAsStream(path)) {
|
||||
var result =
|
||||
BindingDiscovery.inspect(
|
||||
new String(in.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8),
|
||||
"badge",
|
||||
"view",
|
||||
"badge",
|
||||
"text",
|
||||
path,
|
||||
loader);
|
||||
check(result.problems().isEmpty(), "Custom resource rejected: " + result.problems());
|
||||
check(
|
||||
result
|
||||
.controller()
|
||||
.equals(
|
||||
"fixture.components."
|
||||
+ (resource.equals("BadgeCard") ? "BadgeCard" : "ReplacementController")),
|
||||
"Wrong resource Controller");
|
||||
check(
|
||||
result.attributes().stream().anyMatch(a -> a.path().equals("badge")),
|
||||
"Missing custom resource property");
|
||||
if (resource.equals("Replacement"))
|
||||
check(
|
||||
result.attributes().stream().noneMatch(a -> a.path().equals("name")),
|
||||
"Replacement inherited Model");
|
||||
}
|
||||
}
|
||||
for (String key :
|
||||
java.util.List.of(
|
||||
"fixture.viewmodel.initialized",
|
||||
"fixture.controller.initialized",
|
||||
"fixture.replacement.initialized",
|
||||
"fixture.badgecard.initialized"))
|
||||
check(System.getProperty(key) == null, "Discovery initialized " + key);
|
||||
System.out.println("PASS bindings " + checks);
|
||||
return;
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package fixture.checks;
|
||||
|
||||
import de.neitzel.fx.view.*;
|
||||
import de.neitzel.injection.ApplicationContext;
|
||||
import fixture.model.Person;
|
||||
import java.util.concurrent.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
|
||||
public final class ViewModelRuntimeChecks {
|
||||
public static void run() throws Exception {
|
||||
var ready = new CountDownLatch(1);
|
||||
Platform.startup(
|
||||
() -> {
|
||||
Platform.setImplicitExit(false);
|
||||
ready.countDown();
|
||||
});
|
||||
if (!ready.await(10, TimeUnit.SECONDS)) throw new AssertionError("Toolkit startup");
|
||||
try {
|
||||
PreviewChecks.fx(
|
||||
() -> {
|
||||
var loader = ViewModelRuntimeChecks.class.getClassLoader();
|
||||
for (String name : java.util.List.of("Custom", "Replacement")) {
|
||||
var model = new Person();
|
||||
var context = new ApplicationContext("fixture.components");
|
||||
var view =
|
||||
ViewLoader.load(
|
||||
loader.getResource("fixture/components/" + name + ".fxml"), model, context);
|
||||
var field = (TextField) view.getRoot().lookup("#badge");
|
||||
PreviewChecks.check(
|
||||
field
|
||||
.getText()
|
||||
.equals(name.equals("Custom") ? "Custom badge" : "Replacement badge"),
|
||||
"Custom property initial value");
|
||||
field.setText("Edited badge");
|
||||
PreviewChecks.check(
|
||||
view.property("badge").getValue().equals("Edited badge"),
|
||||
"Custom property writeback");
|
||||
PreviewChecks.check(
|
||||
!((Label) view.getRoot().lookup("#computed")).getText().isEmpty(),
|
||||
"Computed method did not run");
|
||||
if (name.equals("Custom")) {
|
||||
PreviewChecks.check(
|
||||
view.property("name").getValue().equals(model.getName()),
|
||||
"Subclass accessor shadowed Model attribute");
|
||||
PreviewChecks.check(
|
||||
view.property("latestMessage") != null, "Missing reserved property");
|
||||
} else
|
||||
PreviewChecks.check(
|
||||
view.property("name") == null, "Replacement inherited Model attribute");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
System.out.println("PASS viewmodels " + PreviewChecks.checks);
|
||||
} finally {
|
||||
Platform.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
@de.neitzel.fx.view.ViewModelClass(BadgeViewModel.class)
|
||||
public class BadgeCard extends javafx.scene.layout.VBox {
|
||||
static {
|
||||
System.setProperty("fixture.badgecard.initialized", "yes");
|
||||
}
|
||||
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String caption() {
|
||||
throw new AssertionError("Metadata must not invoke Component method");
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package fixture.components;
|
||||
|
||||
public class BadgeViewModel extends de.neitzel.fx.view.ViewModel {
|
||||
public BadgeViewModel(fixture.model.Address model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
public javafx.beans.property.StringProperty badgeProperty() {
|
||||
throw new AssertionError("Metadata must not invoke accessor");
|
||||
}
|
||||
}
|
||||
+12
@@ -3,6 +3,18 @@ package fixture.components;
|
||||
@jakarta.inject.Singleton
|
||||
@de.neitzel.fx.view.ViewModelClass(PersonViewModel.class)
|
||||
public class PersonController implements de.neitzel.fx.view.HasViewModel {
|
||||
static {
|
||||
System.setProperty("fixture.controller.initialized", "yes");
|
||||
}
|
||||
|
||||
public Integer invalidComputed() {
|
||||
throw new AssertionError("Validation must not invoke methods");
|
||||
}
|
||||
|
||||
public Object uncertainComputed() {
|
||||
return "Uncertain";
|
||||
}
|
||||
|
||||
public final Greeting greeting;
|
||||
public de.neitzel.fx.view.ViewModel received;
|
||||
|
||||
|
||||
+15
@@ -1,6 +1,21 @@
|
||||
package fixture.components;
|
||||
|
||||
public class PersonViewModel extends de.neitzel.fx.view.ViewModel {
|
||||
static {
|
||||
System.setProperty("fixture.viewmodel.initialized", "yes");
|
||||
}
|
||||
|
||||
private final javafx.beans.property.StringProperty badge =
|
||||
new javafx.beans.property.SimpleStringProperty("Custom badge");
|
||||
|
||||
public javafx.beans.property.StringProperty badgeProperty() {
|
||||
return badge;
|
||||
}
|
||||
|
||||
public javafx.beans.property.IntegerProperty nameProperty() {
|
||||
throw new AssertionError("Model name must win");
|
||||
}
|
||||
|
||||
public PersonViewModel(Object model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
@de.neitzel.fx.view.ViewModelClass(ReplacementViewModel.class)
|
||||
public class ReplacementController {
|
||||
public String caption() {
|
||||
return "Replacement computed";
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package fixture.components;
|
||||
|
||||
public class ReplacementViewModel {
|
||||
static {
|
||||
System.setProperty("fixture.replacement.initialized", "yes");
|
||||
}
|
||||
|
||||
private final javafx.beans.property.StringProperty badge =
|
||||
new javafx.beans.property.SimpleStringProperty("Replacement badge");
|
||||
|
||||
public ReplacementViewModel(fixture.model.Person model) {}
|
||||
|
||||
public javafx.beans.property.StringProperty badgeProperty() {
|
||||
return badge;
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<?neitzel-model fixture.model.Address?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="javafx.scene.layout.VBox"><fx:define><de.neitzel.fx.view.BindingList><de.neitzel.fx.view.DirectBinding control="badge" property="text" path="badge" direction="view"/><de.neitzel.fx.view.ComputedBinding control="caption" property="text" method="#caption"/></de.neitzel.fx.view.BindingList></fx:define><Label fx:id="badge"/><Label fx:id="caption"/></fx:root>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?neitzel-model fixture.model.Person?>
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.TextField?><?import javafx.scene.control.Label?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fixture.components.PersonController">
|
||||
<fx:define><de.neitzel.fx.view.BindingList>
|
||||
<de.neitzel.fx.view.DirectBinding control="badge" property="text" path="badge" direction="both"/>
|
||||
<de.neitzel.fx.view.ComputedBinding control="computed" property="text" method="#greeting"/>
|
||||
</de.neitzel.fx.view.BindingList></fx:define>
|
||||
<TextField fx:id="badge"/><Label fx:id="computed"/>
|
||||
</VBox>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?neitzel-model fixture.model.Person?>
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.TextField?><?import javafx.scene.control.Label?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fixture.components.ReplacementController">
|
||||
<fx:define><de.neitzel.fx.view.BindingList>
|
||||
<de.neitzel.fx.view.DirectBinding control="badge" property="text" path="badge" direction="both"/>
|
||||
<de.neitzel.fx.view.ComputedBinding control="computed" property="text" method="#caption"/>
|
||||
</de.neitzel.fx.view.BindingList></fx:define>
|
||||
<TextField fx:id="badge"/><Label fx:id="computed"/>
|
||||
</VBox>
|
||||
+49
@@ -41,6 +41,10 @@ public final class PreviewChecks {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String mode = args[0];
|
||||
if (mode.equals("viewmodels")) {
|
||||
ViewModelRuntimeChecks.run();
|
||||
return;
|
||||
}
|
||||
if (mode.equals("unstable")
|
||||
|| mode.equals("full")
|
||||
|| mode.equals("full-empty")
|
||||
@@ -129,6 +133,51 @@ public final class PreviewChecks {
|
||||
System.getProperty("fixture.converter.initialized") == null,
|
||||
"Discovery initialized Converter");
|
||||
}
|
||||
check(
|
||||
metadata.viewModel().equals("fixture.components.PersonViewModel"),
|
||||
"Explicit custom ViewModel");
|
||||
check(
|
||||
metadata.attributes().stream().anyMatch(a -> a.path().equals("badge")),
|
||||
"Custom property absent");
|
||||
check(
|
||||
metadata.computed().stream().anyMatch(m -> m.method().equals("#greeting")),
|
||||
"Computed method absent");
|
||||
for (String resource : java.util.List.of("Replacement", "BadgeCard")) {
|
||||
String path = "fixture/components/" + resource + ".fxml";
|
||||
try (var in = loader.getResourceAsStream(path)) {
|
||||
var result =
|
||||
BindingDiscovery.inspect(
|
||||
new String(in.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8),
|
||||
"badge",
|
||||
"view",
|
||||
"badge",
|
||||
"text",
|
||||
path,
|
||||
loader);
|
||||
check(result.problems().isEmpty(), "Custom resource rejected: " + result.problems());
|
||||
check(
|
||||
result
|
||||
.controller()
|
||||
.equals(
|
||||
"fixture.components."
|
||||
+ (resource.equals("BadgeCard") ? "BadgeCard" : "ReplacementController")),
|
||||
"Wrong resource Controller");
|
||||
check(
|
||||
result.attributes().stream().anyMatch(a -> a.path().equals("badge")),
|
||||
"Missing custom resource property");
|
||||
if (resource.equals("Replacement"))
|
||||
check(
|
||||
result.attributes().stream().noneMatch(a -> a.path().equals("name")),
|
||||
"Replacement inherited Model");
|
||||
}
|
||||
}
|
||||
for (String key :
|
||||
java.util.List.of(
|
||||
"fixture.viewmodel.initialized",
|
||||
"fixture.controller.initialized",
|
||||
"fixture.replacement.initialized",
|
||||
"fixture.badgecard.initialized"))
|
||||
check(System.getProperty(key) == null, "Discovery initialized " + key);
|
||||
System.out.println("PASS bindings " + checks);
|
||||
return;
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package fixture.checks;
|
||||
|
||||
import de.neitzel.fx.view.*;
|
||||
import de.neitzel.injection.ApplicationContext;
|
||||
import fixture.model.Person;
|
||||
import java.util.concurrent.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
|
||||
public final class ViewModelRuntimeChecks {
|
||||
public static void run() throws Exception {
|
||||
var ready = new CountDownLatch(1);
|
||||
Platform.startup(
|
||||
() -> {
|
||||
Platform.setImplicitExit(false);
|
||||
ready.countDown();
|
||||
});
|
||||
if (!ready.await(10, TimeUnit.SECONDS)) throw new AssertionError("Toolkit startup");
|
||||
try {
|
||||
PreviewChecks.fx(
|
||||
() -> {
|
||||
var loader = ViewModelRuntimeChecks.class.getClassLoader();
|
||||
for (String name : java.util.List.of("Custom", "Replacement")) {
|
||||
var model = new Person();
|
||||
var context = new ApplicationContext("fixture.components");
|
||||
var view =
|
||||
ViewLoader.load(
|
||||
loader.getResource("fixture/components/" + name + ".fxml"), model, context);
|
||||
var field = (TextField) view.getRoot().lookup("#badge");
|
||||
PreviewChecks.check(
|
||||
field
|
||||
.getText()
|
||||
.equals(name.equals("Custom") ? "Custom badge" : "Replacement badge"),
|
||||
"Custom property initial value");
|
||||
field.setText("Edited badge");
|
||||
PreviewChecks.check(
|
||||
view.property("badge").getValue().equals("Edited badge"),
|
||||
"Custom property writeback");
|
||||
PreviewChecks.check(
|
||||
!((Label) view.getRoot().lookup("#computed")).getText().isEmpty(),
|
||||
"Computed method did not run");
|
||||
if (name.equals("Custom")) {
|
||||
PreviewChecks.check(
|
||||
view.property("name").getValue().equals(model.getName()),
|
||||
"Subclass accessor shadowed Model attribute");
|
||||
PreviewChecks.check(
|
||||
view.property("latestMessage") != null, "Missing reserved property");
|
||||
} else
|
||||
PreviewChecks.check(
|
||||
view.property("name") == null, "Replacement inherited Model attribute");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
System.out.println("PASS viewmodels " + PreviewChecks.checks);
|
||||
} finally {
|
||||
Platform.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user