Implement retained target sessions, runtime sizing and Component navigation

This commit is contained in:
2026-11-12 14:20:26 +01:00
parent e41b276b13
commit 6e25cf3ac6
71 changed files with 2538 additions and 120 deletions
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
@@ -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);
}
}
@@ -0,0 +1,7 @@
package fixture.components;
public class UnstableLayout extends javafx.scene.layout.VBox {
protected void layoutChildren() {
requestLayout();
}
}
@@ -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();
@@ -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>
@@ -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>
@@ -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>
@@ -0,0 +1 @@
<fixture.components.UnstableLayout/>
@@ -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>
@@ -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");
@@ -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);
}
}
@@ -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";
@@ -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
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
@@ -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);
}
}
@@ -0,0 +1,7 @@
package fixture.components;
public class UnstableLayout extends javafx.scene.layout.VBox {
protected void layoutChildren() {
requestLayout();
}
}
@@ -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();
@@ -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>
@@ -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>
@@ -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>
@@ -0,0 +1 @@
<fixture.components.UnstableLayout/>
@@ -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>
@@ -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");
@@ -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;
@@ -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";
@@ -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