Add shared source-preserving Computed binding inspector

This commit is contained in:
2026-11-13 13:42:19 +01:00
parent 99d93a11fc
commit 510ce35b60
12 changed files with 639 additions and 224 deletions
@@ -482,6 +482,168 @@ public final class ProjectionHostChecks {
await(() -> fx(() -> source(pane).getText().equals(full)));
}
private static void computedField(DesignerPane pane, String field, String value)
throws Exception {
fx(
() -> {
((TextField) pane.lookup("#computed-" + field)).setText(value);
return null;
});
}
private static void selectComputed(DesignerPane pane, String control) throws Exception {
fx(
() -> {
var list = (ListView<Object>) pane.lookup("#computed-bindings");
list.getSelectionModel()
.select(
list.getItems().stream()
.filter(e -> e.toString().startsWith(control + "."))
.findFirst()
.orElseThrow());
return null;
});
}
private static void computedBindingEdits(DesignerPane pane, DocumentSession document, String full)
throws Exception {
bindingTab(pane, "Computed bindings");
click(pane, "New Computed binding");
computedField(pane, "control", "action");
computedField(pane, "property", "text");
click(pane, "Find methods / Validate");
await(() -> fx(() -> !((ComboBox<?>) pane.lookup("#computed-sources")).getItems().isEmpty()));
fx(
() -> {
var methods =
(ComboBox<de.neitzel.fxdesigner.protocol.BindingMetadata.Computed>)
pane.lookup("#computed-sources");
var greeting =
methods.getItems().stream()
.filter(m -> m.method().equals("#greeting"))
.findFirst()
.orElseThrow();
check(greeting.status().equals("COMPATIBLE"), "Computed method type comparison failed");
methods.setValue(greeting);
return null;
});
check(documentText(document).equals(full), "Method choice changed source before Apply");
click(pane, "Apply Computed binding");
await(() -> !documentText(document).equals(full));
String created = documentText(document);
check(
created.contains(
"ComputedBinding control=\"action\" property=\"text\" method=\"#greeting\""),
"Computed creation failed");
click(pane, "Undo");
await(() -> documentText(document).equals(full));
click(pane, "Redo");
await(() -> documentText(document).equals(created));
await(() -> fx(() -> source(pane).getText().equals(created)));
selectComputed(pane, "action");
computedField(pane, "property", "accessibleText");
computedField(pane, "method", "greeting");
click(pane, "Apply Computed binding");
String changed =
created.replace(
"property=\"text\" method=\"#greeting\"",
"property=\"accessibleText\" method=\"greeting\"");
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),
"Computed save changed source");
click(pane, "Render drafts");
await(
() ->
fx(
() ->
object(pane, "home").getSceneGraphObject() instanceof SnapshotRegion r
&& r.image() != null
&& r.statusText().equals("Target runtime")));
selectComputed(pane, "action");
click(pane, "Remove Computed binding");
await(() -> !documentText(document).equals(changed));
String removed = documentText(document);
check(!removed.contains("ComputedBinding"), "Computed removal failed");
click(pane, "Undo");
await(() -> documentText(document).equals(changed));
click(pane, "Redo");
await(() -> documentText(document).equals(removed));
await(() -> fx(() -> source(pane).getText().equals(removed)));
click(pane, "New Computed binding");
computedField(pane, "control", "rankInput");
computedField(pane, "property", "text");
computedField(pane, "method", "#greeting");
click(pane, "Apply Computed binding");
await(
() ->
fx(
() ->
((Label) pane.lookup("#computed-status"))
.getText()
.contains("Replace conflicting bindings")));
check(documentText(document).equals(removed), "Computed silently replaced Direct binding");
fx(
() -> {
((CheckBox) pane.lookup("#computed-replace")).setSelected(true);
return null;
});
click(pane, "Apply Computed binding");
await(() -> !documentText(document).equals(removed));
String replaced = documentText(document);
check(
directDeclarations(replaced).stream()
.noneMatch(e -> e.getAttribute("control").equals("rankInput")),
"Computed replacement retained Direct conflict");
click(pane, "Undo");
await(() -> documentText(document).equals(removed));
click(pane, "Redo");
await(() -> documentText(document).equals(replaced));
await(() -> fx(() -> source(pane).getText().equals(replaced)));
selectComputed(pane, "rankInput");
computedField(pane, "method", "#invalidComputed");
click(pane, "Apply Computed binding");
await(() -> documentText(document).contains("method=\"#invalidComputed\""));
String invalid = documentText(document);
document
.save(document.snapshot().toCompletableFuture().get().revision())
.toCompletableFuture()
.get();
click(pane, "Render drafts");
await(
() ->
fx(
() ->
pane.lookupAll(".label").stream()
.anyMatch(
n ->
n instanceof Label l
&& l.getText() != null
&& l.getText().startsWith("Preview blocked:"))));
check(
java.nio.file.Files.readString(
java.nio.file.Path.of(document.snapshot().toCompletableFuture().get().document()))
.equals(invalid),
"Invalid Computed draft not saved");
click(pane, "Undo");
await(() -> documentText(document).equals(replaced));
click(pane, "Redo");
await(() -> documentText(document).equals(invalid));
set(document, full);
await(() -> fx(() -> source(pane).getText().equals(full)));
}
private static void viewModelBindings(
DesignerPane pane, DocumentSession document, String original) throws Exception {
bindingTab(pane, "Discover targets");
@@ -859,6 +1021,7 @@ public final class ProjectionHostChecks {
check(documentText(document).equals(full), "Discovery handoff changed source before Apply");
converterEdits(pane, document, full);
directBindingEdits(pane, document, full);
computedBindingEdits(pane, document, full);
document
.save(document.snapshot().toCompletableFuture().get().revision())
.toCompletableFuture()