Add shared source-preserving DirectBinding inspector
This commit is contained in:
@@ -207,6 +207,199 @@ public final class ProjectionHostChecks {
|
||||
await(() -> fx(() -> source(pane).getText().equals(before)));
|
||||
}
|
||||
|
||||
private static void bindingTab(DesignerPane pane, String name) throws Exception {
|
||||
fx(
|
||||
() -> {
|
||||
pane.lookupAll(".tab-pane").stream()
|
||||
.filter(n -> n instanceof TabPane)
|
||||
.map(n -> (TabPane) n)
|
||||
.forEach(
|
||||
t ->
|
||||
t.getTabs().stream()
|
||||
.filter(tab -> tab.getText().equals(name))
|
||||
.findFirst()
|
||||
.ifPresent(tab -> t.getSelectionModel().select(tab)));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static void directField(DesignerPane pane, String field, String value) throws Exception {
|
||||
fx(
|
||||
() -> {
|
||||
((TextField) pane.lookup("#direct-" + field)).setText(value);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static void selectDirect(DesignerPane pane, String control) throws Exception {
|
||||
fx(
|
||||
() -> {
|
||||
var list = (ListView<Object>) pane.lookup("#direct-bindings");
|
||||
list.getSelectionModel()
|
||||
.select(
|
||||
list.getItems().stream()
|
||||
.filter(e -> e.toString().startsWith(control + "."))
|
||||
.findFirst()
|
||||
.orElseThrow());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// The live IDE probe uses a separate plugin loader: assert exported UI and XML, not internals.
|
||||
private static List<org.w3c.dom.Element> directDeclarations(String text) throws Exception {
|
||||
var factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
var parsed =
|
||||
factory
|
||||
.newDocumentBuilder()
|
||||
.parse(new org.xml.sax.InputSource(new java.io.StringReader(text)));
|
||||
var nodes = parsed.getElementsByTagName("*");
|
||||
var result = new ArrayList<org.w3c.dom.Element>();
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
var element = (org.w3c.dom.Element) nodes.item(i);
|
||||
if (Set.of("DirectBinding", "de.neitzel.fx.view.DirectBinding")
|
||||
.contains(element.getTagName())) result.add(element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String documentText(DocumentSession doc) throws Exception {
|
||||
return doc.snapshot().toCompletableFuture().get().text();
|
||||
}
|
||||
|
||||
private static void directBindingEdits(DesignerPane pane, DocumentSession document, String full)
|
||||
throws Exception {
|
||||
bindingTab(pane, "Direct bindings");
|
||||
click(pane, "New binding");
|
||||
directField(pane, "control", "action");
|
||||
directField(pane, "property", "text");
|
||||
directField(pane, "path", "name");
|
||||
fx(
|
||||
() -> {
|
||||
((ComboBox<String>) pane.lookup("#direct-direction")).setValue("view");
|
||||
return null;
|
||||
});
|
||||
click(pane, "Apply binding");
|
||||
await(() -> !documentText(document).equals(full));
|
||||
String created = documentText(document);
|
||||
check(
|
||||
directDeclarations(created).stream()
|
||||
.anyMatch(
|
||||
e ->
|
||||
e.getAttribute("control").equals("action")
|
||||
&& e.getAttribute("path").equals("name")),
|
||||
"Create binding failed");
|
||||
click(pane, "Undo");
|
||||
await(() -> documentText(document).equals(full));
|
||||
click(pane, "Redo");
|
||||
await(() -> documentText(document).equals(created));
|
||||
await(() -> fx(() -> source(pane).getText().equals(created)));
|
||||
selectDirect(pane, "action");
|
||||
directField(pane, "path", "home.street");
|
||||
click(pane, "Apply binding");
|
||||
String changed =
|
||||
created.replace(
|
||||
"path=\"name\" direction=\"view\"", "path=\"home.street\" direction=\"view\"");
|
||||
await(() -> documentText(document).equals(changed));
|
||||
click(pane, "Undo");
|
||||
await(() -> documentText(document).equals(created));
|
||||
click(pane, "Redo");
|
||||
await(() -> documentText(document).equals(changed));
|
||||
await(() -> fx(() -> source(pane).getText().equals(changed)));
|
||||
document
|
||||
.save(document.snapshot().toCompletableFuture().get().revision())
|
||||
.toCompletableFuture()
|
||||
.get();
|
||||
check(
|
||||
java.nio.file.Files.readString(
|
||||
java.nio.file.Path.of(document.snapshot().toCompletableFuture().get().document()))
|
||||
.equals(changed),
|
||||
"Binding save changed source");
|
||||
click(pane, "Render drafts");
|
||||
await(
|
||||
() ->
|
||||
fx(
|
||||
() ->
|
||||
object(pane, "home").getSceneGraphObject() instanceof SnapshotRegion r
|
||||
&& r.image() != null
|
||||
&& r.statusText().equals("Target runtime")));
|
||||
selectDirect(pane, "action");
|
||||
click(pane, "Remove binding");
|
||||
await(() -> !documentText(document).equals(changed));
|
||||
String removed = documentText(document);
|
||||
check(
|
||||
directDeclarations(removed).stream()
|
||||
.noneMatch(e -> e.getAttribute("control").equals("action")),
|
||||
"Remove binding failed");
|
||||
click(pane, "Undo");
|
||||
await(() -> documentText(document).equals(changed));
|
||||
click(pane, "Redo");
|
||||
await(() -> documentText(document).equals(removed));
|
||||
await(() -> fx(() -> source(pane).getText().equals(removed)));
|
||||
// Conflicting creation requires explicit replacement and remains one history entry.
|
||||
click(pane, "New binding");
|
||||
directField(pane, "control", "rankInput");
|
||||
directField(pane, "property", "text");
|
||||
directField(pane, "path", "name");
|
||||
fx(
|
||||
() -> {
|
||||
((ComboBox<String>) pane.lookup("#direct-direction")).setValue("view");
|
||||
return null;
|
||||
});
|
||||
click(pane, "Apply binding");
|
||||
check(documentText(document).equals(removed), "Conflict silently overwrote a binding");
|
||||
fx(
|
||||
() -> {
|
||||
((CheckBox) pane.lookup("#direct-replace")).setSelected(true);
|
||||
return null;
|
||||
});
|
||||
click(pane, "Apply binding");
|
||||
await(() -> !documentText(document).equals(removed));
|
||||
String replaced = documentText(document);
|
||||
var rank =
|
||||
directDeclarations(replaced).stream()
|
||||
.filter(e -> e.getAttribute("control").equals("rankInput"))
|
||||
.toList();
|
||||
check(
|
||||
rank.size() == 1 && rank.getFirst().getAttribute("path").equals("name"),
|
||||
"Explicit replace failed");
|
||||
click(pane, "Undo");
|
||||
await(() -> documentText(document).equals(removed));
|
||||
click(pane, "Redo");
|
||||
await(() -> documentText(document).equals(replaced));
|
||||
await(() -> fx(() -> source(pane).getText().equals(replaced)));
|
||||
selectDirect(pane, "rankInput");
|
||||
directField(pane, "path", "invalid path");
|
||||
click(pane, "Apply binding");
|
||||
await(() -> documentText(document).contains("path=\"invalid path\""));
|
||||
String invalid = documentText(document);
|
||||
await(() -> fx(() -> source(pane).getText().equals(invalid)));
|
||||
document
|
||||
.save(document.snapshot().toCompletableFuture().get().revision())
|
||||
.toCompletableFuture()
|
||||
.get();
|
||||
check(
|
||||
java.nio.file.Files.readString(
|
||||
java.nio.file.Path.of(document.snapshot().toCompletableFuture().get().document()))
|
||||
.equals(invalid),
|
||||
"Invalid binding could not be saved");
|
||||
click(pane, "Render drafts");
|
||||
await(
|
||||
() ->
|
||||
fx(
|
||||
() ->
|
||||
pane.lookupAll(".label").stream()
|
||||
.anyMatch(
|
||||
n ->
|
||||
n instanceof Label l
|
||||
&& l.getText() != null
|
||||
&& l.getText().startsWith("Preview blocked:"))));
|
||||
click(pane, "Undo");
|
||||
await(() -> documentText(document).equals(replaced));
|
||||
set(document, full);
|
||||
await(() -> fx(() -> source(pane).getText().equals(full)));
|
||||
}
|
||||
|
||||
public static void run(DesignerPane pane, DocumentSession document) throws Exception {
|
||||
String original = document.snapshot().toCompletableFuture().get().text();
|
||||
// The same real application, now with nested, fully-qualified Component declarations.
|
||||
@@ -429,11 +622,12 @@ public final class ProjectionHostChecks {
|
||||
fx(
|
||||
() -> {
|
||||
pane.lookupAll(".titled-pane").stream()
|
||||
.filter(n -> n instanceof TitledPane t && t.getText().equals("Bindings — discovery"))
|
||||
.filter(n -> n instanceof TitledPane t && t.getText().equals("Bindings"))
|
||||
.map(n -> (TitledPane) n)
|
||||
.forEach(t -> t.setExpanded(true));
|
||||
return null;
|
||||
});
|
||||
bindingTab(pane, "Discover targets");
|
||||
click(pane, "Discover bindings");
|
||||
await(
|
||||
() ->
|
||||
@@ -481,6 +675,29 @@ public final class ProjectionHostChecks {
|
||||
check(
|
||||
document.snapshot().toCompletableFuture().get().text().equals(full),
|
||||
"Binding discovery altered source");
|
||||
fx(
|
||||
() -> {
|
||||
var targets =
|
||||
(TableView<de.neitzel.fxdesigner.protocol.BindingMetadata.Target>)
|
||||
pane.lookup("#binding-targets");
|
||||
targets
|
||||
.getSelectionModel()
|
||||
.select(
|
||||
targets.getItems().stream()
|
||||
.filter(t -> t.control().equals("rankInput") && t.property().equals("text"))
|
||||
.findFirst()
|
||||
.orElseThrow());
|
||||
return null;
|
||||
});
|
||||
click(pane, "Use selected target");
|
||||
check(
|
||||
fx(
|
||||
() ->
|
||||
((TextField) pane.lookup("#direct-control")).getText().equals("rankInput")
|
||||
&& ((TextField) pane.lookup("#direct-path")).getText().equals("rank")),
|
||||
"Discovery did not populate binding form");
|
||||
check(documentText(document).equals(full), "Discovery handoff changed source before Apply");
|
||||
directBindingEdits(pane, document, full);
|
||||
document
|
||||
.save(document.snapshot().toCompletableFuture().get().revision())
|
||||
.toCompletableFuture()
|
||||
|
||||
Reference in New Issue
Block a user