Discover custom ViewModels and validate Computed bindings in shared hosts
This commit is contained in:
@@ -11,6 +11,7 @@ final class BindingBrowser extends VBox {
|
||||
final ComboBox<BindingMetadata.Attribute> paths = new ComboBox<>();
|
||||
final ComboBox<String> directions = new ComboBox<>();
|
||||
final TableView<BindingMetadata.Target> targets = new TableView<>();
|
||||
final ListView<BindingMetadata.Computed> methods = new ListView<>();
|
||||
final Label status = new Label("Build and Refresh, then discover binding targets.");
|
||||
private boolean updating;
|
||||
|
||||
@@ -21,6 +22,8 @@ final class BindingBrowser extends VBox {
|
||||
directions.setId("binding-direction");
|
||||
targets.setId("binding-targets");
|
||||
status.setId("binding-status");
|
||||
methods.setId("computed-methods");
|
||||
methods.setPrefHeight(100);
|
||||
paths.setPrefWidth(320);
|
||||
directions.getItems().setAll("view", "model", "both");
|
||||
directions.setValue("both");
|
||||
@@ -62,7 +65,9 @@ final class BindingBrowser extends VBox {
|
||||
8, refresh, new Label("Model path"), paths, new Label("Direction"), directions),
|
||||
status,
|
||||
targets,
|
||||
use);
|
||||
use,
|
||||
new Label("Computed sources (public Controller methods)"),
|
||||
methods);
|
||||
}
|
||||
|
||||
private String path() {
|
||||
@@ -77,8 +82,16 @@ final class BindingBrowser extends VBox {
|
||||
}
|
||||
|
||||
void invalidate(String message) {
|
||||
targets.getItems().clear();
|
||||
status.setText(message);
|
||||
updating = true;
|
||||
try {
|
||||
paths.getItems().clear();
|
||||
paths.setValue(null);
|
||||
targets.getItems().clear();
|
||||
methods.getItems().clear();
|
||||
status.setText(message);
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
void show(BindingMetadata metadata, String provenance) {
|
||||
@@ -92,9 +105,14 @@ final class BindingBrowser extends VBox {
|
||||
.orElse(null));
|
||||
directions.setValue(metadata.direction());
|
||||
targets.getItems().setAll(metadata.targets());
|
||||
methods.getItems().setAll(metadata.computed());
|
||||
status.setText(
|
||||
"Model: "
|
||||
+ metadata.model()
|
||||
+ " · ViewModel: "
|
||||
+ metadata.viewModel()
|
||||
+ " · Controller: "
|
||||
+ metadata.controller()
|
||||
+ " · "
|
||||
+ provenance
|
||||
+ "\n"
|
||||
|
||||
@@ -748,17 +748,22 @@ public final class DesignerPane extends BorderPane implements AutoCloseable {
|
||||
withDraft(
|
||||
() -> {
|
||||
long ticket = ++bindingRequest;
|
||||
validatingSource = null;
|
||||
validationPreview = false;
|
||||
|
||||
bindingBrowser.invalidate("Discovering declared types in target runtime…");
|
||||
var captured = draft;
|
||||
validatingSource = captured.text();
|
||||
bindingWorkflow
|
||||
.bindings(last, captured, path, direction)
|
||||
.whenComplete(
|
||||
(metadata, error) ->
|
||||
fx(
|
||||
() -> {
|
||||
if (closed || ticket != bindingRequest) return;
|
||||
if (closed
|
||||
|| ticket != bindingRequest
|
||||
|| draft.revision() != captured.revision()) return;
|
||||
validatingSource = null;
|
||||
boolean resumePreview = validationPreview;
|
||||
validationPreview = false;
|
||||
if (error != null) {
|
||||
bindingBrowser.invalidate(
|
||||
"Discovery unavailable: "
|
||||
@@ -766,6 +771,8 @@ public final class DesignerPane extends BorderPane implements AutoCloseable {
|
||||
+ ". Build and Refresh, then retry.");
|
||||
return;
|
||||
}
|
||||
validatedSource = captured.text();
|
||||
bindingProblems = metadata.problems();
|
||||
bindingBrowser.show(
|
||||
metadata,
|
||||
(last
|
||||
@@ -773,6 +780,11 @@ public final class DesignerPane extends BorderPane implements AutoCloseable {
|
||||
: "current successful build")
|
||||
+ " · draft revision "
|
||||
+ captured.revision());
|
||||
bindingEditor.metadata(
|
||||
metadata,
|
||||
null,
|
||||
"Discovery · draft revision " + captured.revision());
|
||||
if (resumePreview) render();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,6 +190,10 @@ final class DirectBindingEditor extends VBox {
|
||||
BindingMetadata metadata, DirectBindingDocument.Values requested, String provenance) {
|
||||
status.setText(
|
||||
provenance
|
||||
+ " · ViewModel: "
|
||||
+ metadata.viewModel()
|
||||
+ " · Controller: "
|
||||
+ metadata.controller()
|
||||
+ "\n"
|
||||
+ (metadata.problems().isEmpty()
|
||||
? "No detected binding errors."
|
||||
|
||||
@@ -482,6 +482,84 @@ public final class ProjectionHostChecks {
|
||||
await(() -> fx(() -> source(pane).getText().equals(full)));
|
||||
}
|
||||
|
||||
private static void viewModelBindings(
|
||||
DesignerPane pane, DocumentSession document, String original) throws Exception {
|
||||
bindingTab(pane, "Discover targets");
|
||||
click(pane, "Discover bindings");
|
||||
await(
|
||||
() ->
|
||||
fx(
|
||||
() ->
|
||||
((ComboBox<de.neitzel.fxdesigner.protocol.BindingMetadata.Attribute>)
|
||||
pane.lookup("#binding-path"))
|
||||
.getItems().stream().anyMatch(a -> a.path().equals("badge"))));
|
||||
check(
|
||||
fx(
|
||||
() ->
|
||||
((ListView<de.neitzel.fxdesigner.protocol.BindingMetadata.Computed>)
|
||||
pane.lookup("#computed-methods"))
|
||||
.getItems().stream().anyMatch(m -> m.method().equals("#greeting"))),
|
||||
"Computed inventory missing");
|
||||
check(documentText(document).equals(original), "ViewModel discovery altered FXML");
|
||||
String invalid = original.replace("method=\"#greeting\"", "method=\"#invalidComputed\"");
|
||||
check(!invalid.equals(original), "Computed fixture missing");
|
||||
set(document, invalid);
|
||||
await(() -> fx(() -> source(pane).getText().equals(invalid)));
|
||||
bindingTab(pane, "Direct bindings");
|
||||
click(pane, "Render drafts");
|
||||
await(() -> bindingStatus(pane, "Computed return type"));
|
||||
await(
|
||||
() ->
|
||||
fx(
|
||||
() ->
|
||||
pane.lookupAll(".label").stream()
|
||||
.anyMatch(
|
||||
n ->
|
||||
n instanceof Label l
|
||||
&& l.getText() != null
|
||||
&& l.getText().startsWith("Preview blocked:"))));
|
||||
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 Computed draft could not be saved");
|
||||
click(pane, "Undo");
|
||||
await(() -> documentText(document).equals(original));
|
||||
click(pane, "Redo");
|
||||
await(() -> documentText(document).equals(invalid));
|
||||
click(pane, "Undo");
|
||||
await(() -> documentText(document).equals(original));
|
||||
String replacement =
|
||||
java.nio.file.Files.readString(
|
||||
java.nio.file.Path.of(document.snapshot().toCompletableFuture().get().document())
|
||||
.resolveSibling("Replacement.fxml"));
|
||||
set(document, replacement);
|
||||
await(() -> fx(() -> source(pane).getText().equals(replacement)));
|
||||
bindingTab(pane, "Discover targets");
|
||||
click(pane, "Discover bindings");
|
||||
await(
|
||||
() ->
|
||||
fx(
|
||||
() ->
|
||||
((Label) pane.lookup("#binding-status"))
|
||||
.getText()
|
||||
.contains("fixture.components.ReplacementViewModel")));
|
||||
check(
|
||||
fx(
|
||||
() ->
|
||||
((ComboBox<de.neitzel.fxdesigner.protocol.BindingMetadata.Attribute>)
|
||||
pane.lookup("#binding-path"))
|
||||
.getItems().stream().noneMatch(a -> a.path().equals("name"))),
|
||||
"Replacement inherited Model attributes");
|
||||
check(documentText(document).equals(replacement), "Replacement discovery altered FXML");
|
||||
set(document, original);
|
||||
await(() -> fx(() -> source(pane).getText().equals(original)));
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -810,6 +888,7 @@ public final class ProjectionHostChecks {
|
||||
|
||||
set(document, original);
|
||||
await(() -> fx(() -> source(pane).getText().equals(original) && object(pane, "card") != null));
|
||||
viewModelBindings(pane, document, original);
|
||||
document
|
||||
.save(document.snapshot().toCompletableFuture().get().revision())
|
||||
.toCompletableFuture()
|
||||
|
||||
Reference in New Issue
Block a user