Implement retained target sessions, runtime sizing and Component navigation
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class AddressForm extends javafx.scene.layout.VBox {
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
public void setModel(Object value) {
|
||||
model.set(value);
|
||||
}
|
||||
|
||||
@javafx.fxml.FXML private NoteForm nested;
|
||||
|
||||
public void initialize() {
|
||||
nested.modelProperty().bind(model);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class NoteForm extends javafx.scene.layout.VBox {
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
public void setModel(Object value) {
|
||||
model.set(value);
|
||||
}
|
||||
}
|
||||
+4
@@ -15,6 +15,10 @@ public class PersonController implements de.neitzel.fx.view.HasViewModel {
|
||||
received = vm;
|
||||
}
|
||||
|
||||
public void action() {
|
||||
((javafx.beans.property.Property) received.property("name")).setValue("Controller action");
|
||||
}
|
||||
|
||||
public String greeting() {
|
||||
if (received == null) throw new IllegalStateException("No ViewModel before bindings");
|
||||
return greeting.text();
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class RankConverter implements de.neitzel.fx.view.Converter {
|
||||
public Object toControlValue(Object value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
public Object toAttributeValue(Object value) {
|
||||
return Integer.valueOf((String) value);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package fixture.components;
|
||||
|
||||
public class UnstableLayout extends javafx.scene.layout.VBox {
|
||||
protected void layoutChildren() {
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class VisitRow extends javafx.scene.layout.HBox {
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
public void setModel(Object value) {
|
||||
model.set(value);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,26 @@
|
||||
package fixture.model;
|
||||
|
||||
public class Person {
|
||||
private java.util.List<Address> visits = new java.util.ArrayList<>();
|
||||
|
||||
public java.util.List<Address> getVisits() {
|
||||
return visits;
|
||||
}
|
||||
|
||||
public void setVisits(java.util.List<Address> value) {
|
||||
visits = value;
|
||||
}
|
||||
|
||||
private int rank = 7;
|
||||
|
||||
public int getRank() {
|
||||
return rank;
|
||||
}
|
||||
|
||||
public void setRank(int value) {
|
||||
rank = value;
|
||||
}
|
||||
|
||||
private String name = "Ada";
|
||||
private Address home = new Address();
|
||||
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.Label?><?import fixture.components.NoteForm?>
|
||||
<fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml/1"><Label text="A long address explanation that should wrap across several lines when the allocated width is narrow" wrapText="true"/><NoteForm fx:id="nested"/></fx:root>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.TextField?><?import javafx.scene.control.Button?><?import fixture.components.AddressForm?><?import de.neitzel.fx.view.BindingList?><?import de.neitzel.fx.view.DirectBinding?><?import de.neitzel.fx.view.Repeat?><?import de.neitzel.fx.view.Table?><?import de.neitzel.fx.view.Column?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fixture.components.PersonController" prefWidth="640" prefHeight="700">
|
||||
<fx:define><BindingList><DirectBinding control="rankInput" property="text" path="rank" direction="both" converter="fixture.components.RankConverter"/><DirectBinding control="home" property="model" path="home" direction="both"/><Repeat pane="visits" component="fixture.components.AddressForm" collection="visits"/></BindingList></fx:define>
|
||||
<AddressForm fx:id="home" id="different-css-id"/>
|
||||
<VBox fx:id="visits"/>
|
||||
<Table fx:id="table" collection="visits" row="fixture.components.VisitRow" prefHeight="250"><Column header="Street" attribute="street"/></Table>
|
||||
<TextField fx:id="rankInput"/><Button fx:id="action" text="Run action" onAction="#action"/></VBox>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.Label?><?import de.neitzel.fx.view.BindingList?><?import de.neitzel.fx.view.DirectBinding?>
|
||||
<fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml/1"><fx:define><BindingList><DirectBinding control="note" property="text" path="street" direction="both"/></BindingList></fx:define><Label fx:id="note" wrapText="true" text="Note"/></fx:root>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<fixture.components.UnstableLayout/>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<?import javafx.scene.layout.HBox?><?import javafx.scene.control.TextField?><?import de.neitzel.fx.view.BindingList?><?import de.neitzel.fx.view.DirectBinding?>
|
||||
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml/1"><fx:define><BindingList><DirectBinding control="street" property="text" path="street" direction="both"/></BindingList></fx:define><TextField fx:id="street"/></fx:root>
|
||||
+8
-1
@@ -41,9 +41,16 @@ public final class PreviewChecks {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String mode = args[0];
|
||||
if (mode.equals("unstable")
|
||||
|| mode.equals("full")
|
||||
|| mode.equals("full-empty")
|
||||
|| mode.equals("card")) {
|
||||
StageChecks.run(mode);
|
||||
return;
|
||||
}
|
||||
ClassLoader loader = PreviewChecks.class.getClassLoader();
|
||||
var descriptors = ExampleCatalog.discover(loader);
|
||||
check(descriptors.size() == 9, "Descriptor discovery");
|
||||
check(descriptors.size() == 15, "Descriptor discovery");
|
||||
check(
|
||||
System.getProperty("fixture.provider.initialized") == null,
|
||||
"Discovery initialized provider");
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package fixture.checks;
|
||||
|
||||
import de.neitzel.fxdesigner.target.*;
|
||||
import fixture.components.*;
|
||||
import fixture.preview.Examples;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
public final class StageChecks {
|
||||
public static void run(String mode) throws Exception {
|
||||
var started = new CountDownLatch(1);
|
||||
Platform.startup(
|
||||
() -> {
|
||||
Platform.setImplicitExit(false);
|
||||
started.countDown();
|
||||
});
|
||||
if (!started.await(10, TimeUnit.SECONDS)) throw new AssertionError("FX startup");
|
||||
var descriptor =
|
||||
ExampleCatalog.discover(StageChecks.class.getClassLoader()).stream()
|
||||
.filter(e -> e.id().equals(mode.equals("unstable") ? "full" : mode))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
if (mode.equals("unstable"))
|
||||
descriptor =
|
||||
new de.neitzel.fxdesigner.preview.ExampleDescriptor(
|
||||
descriptor.id(),
|
||||
descriptor.label(),
|
||||
descriptor.modelType(),
|
||||
descriptor.providerClass(),
|
||||
"fixture/components/Unstable.fxml",
|
||||
descriptor.componentPackage());
|
||||
var preview =
|
||||
TargetPreview.open(descriptor, StageChecks.class.getClassLoader(), Duration.ofSeconds(15));
|
||||
try {
|
||||
if (mode.equals("unstable")) {
|
||||
try {
|
||||
preview.ready().toCompletableFuture().get(20, TimeUnit.SECONDS);
|
||||
throw new AssertionError("Unstable layout accepted");
|
||||
} catch (ExecutionException expected) {
|
||||
Throwable error = expected;
|
||||
boolean found = false;
|
||||
while (error != null) {
|
||||
if (String.valueOf(error.getMessage()).contains("did not converge")) found = true;
|
||||
error = error.getCause();
|
||||
}
|
||||
PreviewChecks.check(found, "Missing non-convergence diagnostic");
|
||||
}
|
||||
System.out.println("PASS unstable " + PreviewChecks.checks);
|
||||
return;
|
||||
}
|
||||
var ready = preview.ready().toCompletableFuture().get(20, TimeUnit.SECONDS);
|
||||
PreviewChecks.fx(
|
||||
() -> {
|
||||
var root = ready.loadedView().getRoot();
|
||||
if (mode.equals("card")) {
|
||||
var card = (Card) root;
|
||||
PreviewChecks.check(
|
||||
card.street().getText().equals("Independent Card sample"),
|
||||
"Independent Component Model");
|
||||
card.street().setText("Edited independently");
|
||||
PreviewChecks.check(
|
||||
((fixture.model.Address) card.getModel())
|
||||
.getStreet()
|
||||
.equals("Edited independently"),
|
||||
"Independent write-back");
|
||||
} else if (mode.equals("full-empty")) {
|
||||
PreviewChecks.check(
|
||||
((AddressForm) root.lookup("#different-css-id")).getModel() == null,
|
||||
"Missing nested Model");
|
||||
PreviewChecks.check(
|
||||
((VBox) root.lookup("#visits")).getChildren().isEmpty(), "Empty repeats");
|
||||
PreviewChecks.check(
|
||||
ready.occurrences().values().stream().noneMatch(VisitRow.class.getName()::equals),
|
||||
"Empty table");
|
||||
} else {
|
||||
var rank = (TextField) root.lookup("#rankInput");
|
||||
rank.setText("invalid");
|
||||
PreviewChecks.check(
|
||||
Examples.person.getRank() == 7, "Conversion failure changed Model");
|
||||
rank.setText("42");
|
||||
PreviewChecks.check(Examples.person.getRank() == 42, "Conversion recovery");
|
||||
((Button) root.lookup("#action")).fire();
|
||||
PreviewChecks.check(
|
||||
Examples.person.getName().equals("Controller action"), "Controller action");
|
||||
var home = (AddressForm) root.lookup("#different-css-id");
|
||||
Occurrences.setKey(home, "home");
|
||||
var note = (NoteForm) home.lookup("#nested");
|
||||
PreviewChecks.check(
|
||||
home.getModel() == Examples.person.getHome()
|
||||
&& note.getModel() == home.getModel(),
|
||||
"Nested Model identity");
|
||||
var visits = (VBox) root.lookup("#visits");
|
||||
PreviewChecks.check(visits.getChildren().size() == 2, "Repeat instances");
|
||||
PreviewChecks.check(
|
||||
((AddressForm) visits.getChildren().getFirst()).getModel() == home.getModel(),
|
||||
"Repeated shared Model");
|
||||
var narrow = ready.measure("home", 100, 150);
|
||||
var wide = ready.measure("home", 600, 150);
|
||||
PreviewChecks.check(
|
||||
narrow.prefHeight() > wide.prefHeight(), "Width-dependent wrapping");
|
||||
PreviewChecks.check(
|
||||
narrow.occurrence().equals(wide.occurrence()),
|
||||
"Stable occurrence across measurements");
|
||||
PreviewChecks.check(
|
||||
ready.renderNode(narrow.occurrence(), 100, 150).length == 60000,
|
||||
"Occurrence rendering");
|
||||
PreviewChecks.check(
|
||||
home.getWidth() == 100 && home.getHeight() == 150,
|
||||
"Allocated target size lost during capture");
|
||||
PreviewChecks.check(
|
||||
home.getId().equals("different-css-id"), "CSS identity overwritten");
|
||||
var metadata = ready.occurrences();
|
||||
long notes =
|
||||
metadata.entrySet().stream()
|
||||
.filter(
|
||||
e ->
|
||||
e.getKey().endsWith(".type")
|
||||
&& e.getValue().equals(NoteForm.class.getName()))
|
||||
.count();
|
||||
PreviewChecks.check(notes == 3, "Nested repeated occurrence discovery");
|
||||
long rows =
|
||||
metadata.entrySet().stream()
|
||||
.filter(
|
||||
e ->
|
||||
e.getKey().endsWith(".type")
|
||||
&& e.getValue().equals(VisitRow.class.getName()))
|
||||
.count();
|
||||
PreviewChecks.check(rows == 2, "Table row occurrences");
|
||||
var table = (de.neitzel.fx.view.Table) root.lookup("#table");
|
||||
table.setSelection(Examples.person.getVisits().get(1));
|
||||
PreviewChecks.check(
|
||||
table.getSelection() == Examples.person.getVisits().get(1), "Table selection");
|
||||
var firstRow =
|
||||
(VisitRow)
|
||||
table.lookupAll("*").stream()
|
||||
.filter(
|
||||
n ->
|
||||
n instanceof VisitRow r
|
||||
&& r.getModel() == Examples.person.getHome())
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
((TextField) firstRow.lookup("#street")).setText("Changed through Table");
|
||||
PreviewChecks.check(
|
||||
Examples.person.getHome().getStreet().equals("Changed through Table"),
|
||||
"Table write-back");
|
||||
PreviewChecks.check(
|
||||
((Label) note.lookup("#note")).getText().equals("Changed through Table"),
|
||||
"Shared nested binding refresh");
|
||||
var body = (Pane) firstRow.getParent();
|
||||
((Button) table.lookup(".column-opener")).fire();
|
||||
table.lookupAll(".button").stream()
|
||||
.filter(n -> n instanceof Button b && b.getText().equals("Descending"))
|
||||
.map(n -> (Button) n)
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.fire();
|
||||
PreviewChecks.check(
|
||||
((VisitRow) body.getChildren().getFirst()).getModel()
|
||||
== Examples.person.getVisits().get(1),
|
||||
"Interactive Table sorting");
|
||||
((Button) table.lookup(".column-opener")).fire();
|
||||
table.lookupAll(".check-box").stream()
|
||||
.filter(n -> n instanceof CheckBox c && c.getText().equals("Second visit"))
|
||||
.map(n -> (CheckBox) n)
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.setSelected(false);
|
||||
table.lookupAll(".button").stream()
|
||||
.filter(n -> n instanceof Button b && b.getText().equals("Confirm"))
|
||||
.map(n -> (Button) n)
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.fire();
|
||||
PreviewChecks.check(
|
||||
body.getChildren().size() == 1
|
||||
&& ((VisitRow) body.getChildren().getFirst()).getModel()
|
||||
== Examples.person.getHome(),
|
||||
"Interactive Table filtering");
|
||||
PreviewChecks.check(
|
||||
Examples.person.getVisits().size() == 2, "Filtering changed Model collection");
|
||||
}
|
||||
ready.showInteractive("Stage 3 automated interactive fixture", () -> {});
|
||||
PreviewChecks.check(
|
||||
javafx.stage.Window.getWindows().stream().anyMatch(javafx.stage.Window::isShowing),
|
||||
"Interactive window");
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
preview.close();
|
||||
preview.cleanup().toCompletableFuture().get(5, TimeUnit.SECONDS);
|
||||
Platform.exit();
|
||||
}
|
||||
System.out.println("PASS " + mode + " " + PreviewChecks.checks);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package fixture.preview;
|
||||
|
||||
import de.neitzel.fxdesigner.preview.*;
|
||||
import de.neitzel.injection.ApplicationContext;
|
||||
import fixture.model.Address;
|
||||
|
||||
public class CardExamples implements PreviewProvider<Address, ApplicationContext> {
|
||||
public Class<Address> modelType() {
|
||||
return Address.class;
|
||||
}
|
||||
|
||||
public PreviewSession<Address, ApplicationContext> open(String id) {
|
||||
var model = new Address();
|
||||
model.setStreet("Independent Card sample");
|
||||
var context = new ApplicationContext("fixture.components");
|
||||
return new PreviewSession<>() {
|
||||
public Address model() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public ApplicationContext context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void close() {}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,14 @@ public final class Examples implements PreviewProvider<Person, ApplicationContex
|
||||
throw new AssertionError("Provider not on FX");
|
||||
System.out.println("Application console: NFX1 is ordinary text, not a wire frame");
|
||||
person = new Person();
|
||||
person.getVisits().add(person.getHome());
|
||||
var other = new fixture.model.Address();
|
||||
other.setStreet("Second visit");
|
||||
person.getVisits().add(other);
|
||||
if (id.equals("full-empty")) {
|
||||
person.setHome(null);
|
||||
person.getVisits().clear();
|
||||
}
|
||||
context = new ApplicationContext("fixture.components");
|
||||
service = context.getComponent(Greeting.class);
|
||||
service.prefix = "configured";
|
||||
|
||||
+43
-1
@@ -1,5 +1,5 @@
|
||||
format=1
|
||||
count=9
|
||||
count=15
|
||||
example.0.id=standard
|
||||
example.0.label=standard
|
||||
example.0.modelType=fixture.model.Person
|
||||
@@ -54,3 +54,45 @@ example.8.modelType=fixture.model.Person
|
||||
example.8.providerClass=fixture.preview.Examples
|
||||
example.8.viewResource=fixture/components/Person.fxml
|
||||
example.8.componentPackage=fixture.components
|
||||
|
||||
example.9.id=full
|
||||
example.9.label=full
|
||||
example.9.modelType=fixture.model.Person
|
||||
example.9.providerClass=fixture.preview.Examples
|
||||
example.9.viewResource=fixture/components/Full.fxml
|
||||
example.9.componentPackage=fixture.components
|
||||
|
||||
example.10.id=card
|
||||
example.10.label=card
|
||||
example.10.modelType=fixture.model.Address
|
||||
example.10.providerClass=fixture.preview.CardExamples
|
||||
example.10.viewResource=fixture/components/Card.fxml
|
||||
example.10.componentPackage=fixture.components
|
||||
|
||||
example.11.id=full-empty
|
||||
example.11.label=full-empty
|
||||
example.11.modelType=fixture.model.Person
|
||||
example.11.providerClass=fixture.preview.Examples
|
||||
example.11.viewResource=fixture/components/Full.fxml
|
||||
example.11.componentPackage=fixture.components
|
||||
|
||||
example.12.id=address
|
||||
example.12.label=address
|
||||
example.12.modelType=fixture.model.Address
|
||||
example.12.providerClass=fixture.preview.CardExamples
|
||||
example.12.viewResource=fixture/components/AddressForm.fxml
|
||||
example.12.componentPackage=fixture.components
|
||||
|
||||
example.13.id=note
|
||||
example.13.label=note
|
||||
example.13.modelType=fixture.model.Address
|
||||
example.13.providerClass=fixture.preview.CardExamples
|
||||
example.13.viewResource=fixture/components/NoteForm.fxml
|
||||
example.13.componentPackage=fixture.components
|
||||
|
||||
example.14.id=row
|
||||
example.14.label=row
|
||||
example.14.modelType=fixture.model.Address
|
||||
example.14.providerClass=fixture.preview.CardExamples
|
||||
example.14.viewResource=fixture/components/VisitRow.fxml
|
||||
example.14.componentPackage=fixture.components
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class AddressForm extends javafx.scene.layout.VBox {
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
public void setModel(Object value) {
|
||||
model.set(value);
|
||||
}
|
||||
|
||||
@javafx.fxml.FXML private NoteForm nested;
|
||||
|
||||
public void initialize() {
|
||||
nested.modelProperty().bind(model);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class NoteForm extends javafx.scene.layout.VBox {
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
public void setModel(Object value) {
|
||||
model.set(value);
|
||||
}
|
||||
}
|
||||
+4
@@ -15,6 +15,10 @@ public class PersonController implements de.neitzel.fx.view.HasViewModel {
|
||||
received = vm;
|
||||
}
|
||||
|
||||
public void action() {
|
||||
((javafx.beans.property.Property) received.property("name")).setValue("Controller action");
|
||||
}
|
||||
|
||||
public String greeting() {
|
||||
if (received == null) throw new IllegalStateException("No ViewModel before bindings");
|
||||
return greeting.text();
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class RankConverter implements de.neitzel.fx.view.Converter {
|
||||
public Object toControlValue(Object value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
public Object toAttributeValue(Object value) {
|
||||
return Integer.valueOf((String) value);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package fixture.components;
|
||||
|
||||
public class UnstableLayout extends javafx.scene.layout.VBox {
|
||||
protected void layoutChildren() {
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package fixture.components;
|
||||
|
||||
@jakarta.inject.Singleton
|
||||
public class VisitRow extends javafx.scene.layout.HBox {
|
||||
private final javafx.beans.property.ObjectProperty<Object> model =
|
||||
new javafx.beans.property.SimpleObjectProperty<>();
|
||||
|
||||
public javafx.beans.property.ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
public void setModel(Object value) {
|
||||
model.set(value);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,26 @@
|
||||
package fixture.model;
|
||||
|
||||
public class Person {
|
||||
private java.util.List<Address> visits = new java.util.ArrayList<>();
|
||||
|
||||
public java.util.List<Address> getVisits() {
|
||||
return visits;
|
||||
}
|
||||
|
||||
public void setVisits(java.util.List<Address> value) {
|
||||
visits = value;
|
||||
}
|
||||
|
||||
private int rank = 7;
|
||||
|
||||
public int getRank() {
|
||||
return rank;
|
||||
}
|
||||
|
||||
public void setRank(int value) {
|
||||
rank = value;
|
||||
}
|
||||
|
||||
private String name = "Ada";
|
||||
private Address home = new Address();
|
||||
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.Label?><?import fixture.components.NoteForm?>
|
||||
<fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml/1"><Label text="A long address explanation that should wrap across several lines when the allocated width is narrow" wrapText="true"/><NoteForm fx:id="nested"/></fx:root>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.TextField?><?import javafx.scene.control.Button?><?import fixture.components.AddressForm?><?import de.neitzel.fx.view.BindingList?><?import de.neitzel.fx.view.DirectBinding?><?import de.neitzel.fx.view.Repeat?><?import de.neitzel.fx.view.Table?><?import de.neitzel.fx.view.Column?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fixture.components.PersonController" prefWidth="640" prefHeight="700">
|
||||
<fx:define><BindingList><DirectBinding control="rankInput" property="text" path="rank" direction="both" converter="fixture.components.RankConverter"/><DirectBinding control="home" property="model" path="home" direction="both"/><Repeat pane="visits" component="fixture.components.AddressForm" collection="visits"/></BindingList></fx:define>
|
||||
<AddressForm fx:id="home" id="different-css-id"/>
|
||||
<VBox fx:id="visits"/>
|
||||
<Table fx:id="table" collection="visits" row="fixture.components.VisitRow" prefHeight="250"><Column header="Street" attribute="street"/></Table>
|
||||
<TextField fx:id="rankInput"/><Button fx:id="action" text="Run action" onAction="#action"/></VBox>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<?import javafx.scene.layout.VBox?><?import javafx.scene.control.Label?><?import de.neitzel.fx.view.BindingList?><?import de.neitzel.fx.view.DirectBinding?>
|
||||
<fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml/1"><fx:define><BindingList><DirectBinding control="note" property="text" path="street" direction="both"/></BindingList></fx:define><Label fx:id="note" wrapText="true" text="Note"/></fx:root>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<fixture.components.UnstableLayout/>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<?import javafx.scene.layout.HBox?><?import javafx.scene.control.TextField?><?import de.neitzel.fx.view.BindingList?><?import de.neitzel.fx.view.DirectBinding?>
|
||||
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml/1"><fx:define><BindingList><DirectBinding control="street" property="text" path="street" direction="both"/></BindingList></fx:define><TextField fx:id="street"/></fx:root>
|
||||
+8
-1
@@ -41,9 +41,16 @@ public final class PreviewChecks {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String mode = args[0];
|
||||
if (mode.equals("unstable")
|
||||
|| mode.equals("full")
|
||||
|| mode.equals("full-empty")
|
||||
|| mode.equals("card")) {
|
||||
StageChecks.run(mode);
|
||||
return;
|
||||
}
|
||||
ClassLoader loader = PreviewChecks.class.getClassLoader();
|
||||
var descriptors = ExampleCatalog.discover(loader);
|
||||
check(descriptors.size() == 9, "Descriptor discovery");
|
||||
check(descriptors.size() == 15, "Descriptor discovery");
|
||||
check(
|
||||
System.getProperty("fixture.provider.initialized") == null,
|
||||
"Discovery initialized provider");
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package fixture.checks;
|
||||
|
||||
import de.neitzel.fxdesigner.target.*;
|
||||
import fixture.components.*;
|
||||
import fixture.preview.Examples;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
public final class StageChecks {
|
||||
public static void run(String mode) throws Exception {
|
||||
var started = new CountDownLatch(1);
|
||||
Platform.startup(
|
||||
() -> {
|
||||
Platform.setImplicitExit(false);
|
||||
started.countDown();
|
||||
});
|
||||
if (!started.await(10, TimeUnit.SECONDS)) throw new AssertionError("FX startup");
|
||||
var descriptor =
|
||||
ExampleCatalog.discover(StageChecks.class.getClassLoader()).stream()
|
||||
.filter(e -> e.id().equals(mode.equals("unstable") ? "full" : mode))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
if (mode.equals("unstable"))
|
||||
descriptor =
|
||||
new de.neitzel.fxdesigner.preview.ExampleDescriptor(
|
||||
descriptor.id(),
|
||||
descriptor.label(),
|
||||
descriptor.modelType(),
|
||||
descriptor.providerClass(),
|
||||
"fixture/components/Unstable.fxml",
|
||||
descriptor.componentPackage());
|
||||
var preview =
|
||||
TargetPreview.open(descriptor, StageChecks.class.getClassLoader(), Duration.ofSeconds(15));
|
||||
try {
|
||||
if (mode.equals("unstable")) {
|
||||
try {
|
||||
preview.ready().toCompletableFuture().get(20, TimeUnit.SECONDS);
|
||||
throw new AssertionError("Unstable layout accepted");
|
||||
} catch (ExecutionException expected) {
|
||||
Throwable error = expected;
|
||||
boolean found = false;
|
||||
while (error != null) {
|
||||
if (String.valueOf(error.getMessage()).contains("did not converge")) found = true;
|
||||
error = error.getCause();
|
||||
}
|
||||
PreviewChecks.check(found, "Missing non-convergence diagnostic");
|
||||
}
|
||||
System.out.println("PASS unstable " + PreviewChecks.checks);
|
||||
return;
|
||||
}
|
||||
var ready = preview.ready().toCompletableFuture().get(20, TimeUnit.SECONDS);
|
||||
PreviewChecks.fx(
|
||||
() -> {
|
||||
var root = ready.loadedView().getRoot();
|
||||
if (mode.equals("card")) {
|
||||
var card = (Card) root;
|
||||
PreviewChecks.check(
|
||||
card.street().getText().equals("Independent Card sample"),
|
||||
"Independent Component Model");
|
||||
card.street().setText("Edited independently");
|
||||
PreviewChecks.check(
|
||||
((fixture.model.Address) card.getModel())
|
||||
.getStreet()
|
||||
.equals("Edited independently"),
|
||||
"Independent write-back");
|
||||
} else if (mode.equals("full-empty")) {
|
||||
PreviewChecks.check(
|
||||
((AddressForm) root.lookup("#different-css-id")).getModel() == null,
|
||||
"Missing nested Model");
|
||||
PreviewChecks.check(
|
||||
((VBox) root.lookup("#visits")).getChildren().isEmpty(), "Empty repeats");
|
||||
PreviewChecks.check(
|
||||
ready.occurrences().values().stream().noneMatch(VisitRow.class.getName()::equals),
|
||||
"Empty table");
|
||||
} else {
|
||||
var rank = (TextField) root.lookup("#rankInput");
|
||||
rank.setText("invalid");
|
||||
PreviewChecks.check(
|
||||
Examples.person.getRank() == 7, "Conversion failure changed Model");
|
||||
rank.setText("42");
|
||||
PreviewChecks.check(Examples.person.getRank() == 42, "Conversion recovery");
|
||||
((Button) root.lookup("#action")).fire();
|
||||
PreviewChecks.check(
|
||||
Examples.person.getName().equals("Controller action"), "Controller action");
|
||||
var home = (AddressForm) root.lookup("#different-css-id");
|
||||
Occurrences.setKey(home, "home");
|
||||
var note = (NoteForm) home.lookup("#nested");
|
||||
PreviewChecks.check(
|
||||
home.getModel() == Examples.person.getHome()
|
||||
&& note.getModel() == home.getModel(),
|
||||
"Nested Model identity");
|
||||
var visits = (VBox) root.lookup("#visits");
|
||||
PreviewChecks.check(visits.getChildren().size() == 2, "Repeat instances");
|
||||
PreviewChecks.check(
|
||||
((AddressForm) visits.getChildren().getFirst()).getModel() == home.getModel(),
|
||||
"Repeated shared Model");
|
||||
var narrow = ready.measure("home", 100, 150);
|
||||
var wide = ready.measure("home", 600, 150);
|
||||
PreviewChecks.check(
|
||||
narrow.prefHeight() > wide.prefHeight(), "Width-dependent wrapping");
|
||||
PreviewChecks.check(
|
||||
narrow.occurrence().equals(wide.occurrence()),
|
||||
"Stable occurrence across measurements");
|
||||
PreviewChecks.check(
|
||||
ready.renderNode(narrow.occurrence(), 100, 150).length == 60000,
|
||||
"Occurrence rendering");
|
||||
PreviewChecks.check(
|
||||
home.getWidth() == 100 && home.getHeight() == 150,
|
||||
"Allocated target size lost during capture");
|
||||
PreviewChecks.check(
|
||||
home.getId().equals("different-css-id"), "CSS identity overwritten");
|
||||
var metadata = ready.occurrences();
|
||||
long notes =
|
||||
metadata.entrySet().stream()
|
||||
.filter(
|
||||
e ->
|
||||
e.getKey().endsWith(".type")
|
||||
&& e.getValue().equals(NoteForm.class.getName()))
|
||||
.count();
|
||||
PreviewChecks.check(notes == 3, "Nested repeated occurrence discovery");
|
||||
long rows =
|
||||
metadata.entrySet().stream()
|
||||
.filter(
|
||||
e ->
|
||||
e.getKey().endsWith(".type")
|
||||
&& e.getValue().equals(VisitRow.class.getName()))
|
||||
.count();
|
||||
PreviewChecks.check(rows == 2, "Table row occurrences");
|
||||
var table = (de.neitzel.fx.view.Table) root.lookup("#table");
|
||||
table.setSelection(Examples.person.getVisits().get(1));
|
||||
PreviewChecks.check(
|
||||
table.getSelection() == Examples.person.getVisits().get(1), "Table selection");
|
||||
var firstRow =
|
||||
(VisitRow)
|
||||
table.lookupAll("*").stream()
|
||||
.filter(
|
||||
n ->
|
||||
n instanceof VisitRow r
|
||||
&& r.getModel() == Examples.person.getHome())
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
((TextField) firstRow.lookup("#street")).setText("Changed through Table");
|
||||
PreviewChecks.check(
|
||||
Examples.person.getHome().getStreet().equals("Changed through Table"),
|
||||
"Table write-back");
|
||||
PreviewChecks.check(
|
||||
((Label) note.lookup("#note")).getText().equals("Changed through Table"),
|
||||
"Shared nested binding refresh");
|
||||
var body = (Pane) firstRow.getParent();
|
||||
((Button) table.lookup(".column-opener")).fire();
|
||||
table.lookupAll(".button").stream()
|
||||
.filter(n -> n instanceof Button b && b.getText().equals("Descending"))
|
||||
.map(n -> (Button) n)
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.fire();
|
||||
PreviewChecks.check(
|
||||
((VisitRow) body.getChildren().getFirst()).getModel()
|
||||
== Examples.person.getVisits().get(1),
|
||||
"Interactive Table sorting");
|
||||
((Button) table.lookup(".column-opener")).fire();
|
||||
table.lookupAll(".check-box").stream()
|
||||
.filter(n -> n instanceof CheckBox c && c.getText().equals("Second visit"))
|
||||
.map(n -> (CheckBox) n)
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.setSelected(false);
|
||||
table.lookupAll(".button").stream()
|
||||
.filter(n -> n instanceof Button b && b.getText().equals("Confirm"))
|
||||
.map(n -> (Button) n)
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.fire();
|
||||
PreviewChecks.check(
|
||||
body.getChildren().size() == 1
|
||||
&& ((VisitRow) body.getChildren().getFirst()).getModel()
|
||||
== Examples.person.getHome(),
|
||||
"Interactive Table filtering");
|
||||
PreviewChecks.check(
|
||||
Examples.person.getVisits().size() == 2, "Filtering changed Model collection");
|
||||
}
|
||||
ready.showInteractive("Stage 3 automated interactive fixture", () -> {});
|
||||
PreviewChecks.check(
|
||||
javafx.stage.Window.getWindows().stream().anyMatch(javafx.stage.Window::isShowing),
|
||||
"Interactive window");
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
preview.close();
|
||||
preview.cleanup().toCompletableFuture().get(5, TimeUnit.SECONDS);
|
||||
Platform.exit();
|
||||
}
|
||||
System.out.println("PASS " + mode + " " + PreviewChecks.checks);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ module fixture.checks {
|
||||
requires fixture.app;
|
||||
requires fixture.preview;
|
||||
requires de.neitzel.fxdesigner.target;
|
||||
requires de.neitzel.fxdesigner.contracts;
|
||||
requires de.neitzel.fxdesigner.preview;
|
||||
requires fx;
|
||||
requires injection;
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package fixture.preview;
|
||||
|
||||
import de.neitzel.fxdesigner.preview.*;
|
||||
import de.neitzel.injection.ApplicationContext;
|
||||
import fixture.model.Address;
|
||||
|
||||
public class CardExamples implements PreviewProvider<Address, ApplicationContext> {
|
||||
public Class<Address> modelType() {
|
||||
return Address.class;
|
||||
}
|
||||
|
||||
public PreviewSession<Address, ApplicationContext> open(String id) {
|
||||
var model = new Address();
|
||||
model.setStreet("Independent Card sample");
|
||||
var context = new ApplicationContext("fixture.components");
|
||||
return new PreviewSession<>() {
|
||||
public Address model() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public ApplicationContext context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void close() {}
|
||||
};
|
||||
}
|
||||
}
|
||||
+8
@@ -26,6 +26,14 @@ public final class Examples implements PreviewProvider<Person, ApplicationContex
|
||||
throw new AssertionError("Provider not on FX");
|
||||
System.out.println("Application console: NFX1 is ordinary text, not a wire frame");
|
||||
person = new Person();
|
||||
person.getVisits().add(person.getHome());
|
||||
var other = new fixture.model.Address();
|
||||
other.setStreet("Second visit");
|
||||
person.getVisits().add(other);
|
||||
if (id.equals("full-empty")) {
|
||||
person.setHome(null);
|
||||
person.getVisits().clear();
|
||||
}
|
||||
context = new ApplicationContext("fixture.components");
|
||||
service = context.getComponent(Greeting.class);
|
||||
service.prefix = "configured";
|
||||
|
||||
+43
-1
@@ -1,5 +1,5 @@
|
||||
format=1
|
||||
count=9
|
||||
count=15
|
||||
example.0.id=standard
|
||||
example.0.label=standard
|
||||
example.0.modelType=fixture.model.Person
|
||||
@@ -54,3 +54,45 @@ example.8.modelType=fixture.model.Person
|
||||
example.8.providerClass=fixture.preview.Examples
|
||||
example.8.viewResource=fixture/components/Person.fxml
|
||||
example.8.componentPackage=fixture.components
|
||||
|
||||
example.9.id=full
|
||||
example.9.label=full
|
||||
example.9.modelType=fixture.model.Person
|
||||
example.9.providerClass=fixture.preview.Examples
|
||||
example.9.viewResource=fixture/components/Full.fxml
|
||||
example.9.componentPackage=fixture.components
|
||||
|
||||
example.10.id=card
|
||||
example.10.label=card
|
||||
example.10.modelType=fixture.model.Address
|
||||
example.10.providerClass=fixture.preview.CardExamples
|
||||
example.10.viewResource=fixture/components/Card.fxml
|
||||
example.10.componentPackage=fixture.components
|
||||
|
||||
example.11.id=full-empty
|
||||
example.11.label=full-empty
|
||||
example.11.modelType=fixture.model.Person
|
||||
example.11.providerClass=fixture.preview.Examples
|
||||
example.11.viewResource=fixture/components/Full.fxml
|
||||
example.11.componentPackage=fixture.components
|
||||
|
||||
example.12.id=address
|
||||
example.12.label=address
|
||||
example.12.modelType=fixture.model.Address
|
||||
example.12.providerClass=fixture.preview.CardExamples
|
||||
example.12.viewResource=fixture/components/AddressForm.fxml
|
||||
example.12.componentPackage=fixture.components
|
||||
|
||||
example.13.id=note
|
||||
example.13.label=note
|
||||
example.13.modelType=fixture.model.Address
|
||||
example.13.providerClass=fixture.preview.CardExamples
|
||||
example.13.viewResource=fixture/components/NoteForm.fxml
|
||||
example.13.componentPackage=fixture.components
|
||||
|
||||
example.14.id=row
|
||||
example.14.label=row
|
||||
example.14.modelType=fixture.model.Address
|
||||
example.14.providerClass=fixture.preview.CardExamples
|
||||
example.14.viewResource=fixture/components/VisitRow.fxml
|
||||
example.14.componentPackage=fixture.components
|
||||
|
||||
Reference in New Issue
Block a user