263 lines
12 KiB
Java
263 lines
12 KiB
Java
package qualification;
|
|
|
|
import com.oracle.javafx.scenebuilder.kit.fxom.*;
|
|
import de.neitzel.fxdesigner.contracts.*;
|
|
import de.neitzel.fxdesigner.host.*;
|
|
import de.neitzel.fxdesigner.kit.*;
|
|
import de.neitzel.fxdesigner.workflow.*;
|
|
import java.lang.instrument.Instrumentation;
|
|
import java.nio.file.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.*;
|
|
import javafx.application.Platform;
|
|
import javafx.scene.control.Button;
|
|
import javafx.stage.*;
|
|
|
|
/** External probe for the actual library example, never shipped in a host or application. */
|
|
public final class FxExampleProbe {
|
|
static <T> T fx(Callable<T> action) throws Exception {
|
|
var answer = new CompletableFuture<T>();
|
|
Platform.runLater(
|
|
() -> {
|
|
try {
|
|
answer.complete(action.call());
|
|
} catch (Throwable e) {
|
|
answer.completeExceptionally(e);
|
|
}
|
|
});
|
|
return answer.get(20, TimeUnit.SECONDS);
|
|
}
|
|
|
|
static void await(Callable<Boolean> condition) throws Exception {
|
|
long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(150);
|
|
while (System.nanoTime() < end) {
|
|
if (condition.call()) return;
|
|
Thread.sleep(100);
|
|
}
|
|
throw new AssertionError("fx-example condition timed out");
|
|
}
|
|
|
|
static void check(boolean condition, String message) {
|
|
if (!condition) throw new AssertionError(message);
|
|
}
|
|
|
|
public static void premain(String arg, Instrumentation ignored) {
|
|
Thread.ofPlatform()
|
|
.daemon()
|
|
.start(
|
|
() -> {
|
|
Path output = Path.of(System.getProperty("example.output")),
|
|
library = Path.of(System.getProperty("example.library"));
|
|
try {
|
|
for (int i = 0; i < 200; i++) {
|
|
try {
|
|
fx(() -> null);
|
|
break;
|
|
} catch (IllegalStateException notStarted) {
|
|
Thread.sleep(100);
|
|
}
|
|
}
|
|
await(
|
|
() ->
|
|
fx(
|
|
() ->
|
|
Window.getWindows().stream()
|
|
.anyMatch(
|
|
w ->
|
|
w instanceof Stage s
|
|
&& s.getScene() != null
|
|
&& s.getScene().getRoot()
|
|
instanceof DesignerPane)));
|
|
var pane =
|
|
fx(
|
|
() ->
|
|
(DesignerPane)
|
|
Window.getWindows().stream()
|
|
.filter(
|
|
w ->
|
|
w instanceof Stage s
|
|
&& s.getScene() != null
|
|
&& s.getScene().getRoot() instanceof DesignerPane)
|
|
.map(w -> ((Stage) w).getScene().getRoot())
|
|
.findFirst()
|
|
.orElseThrow());
|
|
await(() -> fx(() -> pane.kit().editor().getFxomDocument() != null));
|
|
fx(
|
|
() -> {
|
|
pane.lookupAll(".button").stream()
|
|
.filter(
|
|
n -> n instanceof Button b && b.getText().equals("Build and Refresh"))
|
|
.map(n -> (Button) n)
|
|
.findFirst()
|
|
.orElseThrow()
|
|
.fire();
|
|
return null;
|
|
});
|
|
await(
|
|
() ->
|
|
fx(
|
|
() -> {
|
|
var item =
|
|
new FXOMFxIdIndex(pane.kit().editor().getFxomDocument())
|
|
.lookup("homeForm");
|
|
return item instanceof FXOMInstance i
|
|
&& i.getSceneGraphObject() instanceof SnapshotRegion r
|
|
&& r.image() != null
|
|
&& r.statusText().equals("Target runtime");
|
|
}));
|
|
System.out.println(
|
|
"PASS real fx-example Build and Refresh and canvas Component pixels");
|
|
var member = DesignerPane.class.getDeclaredField("launch");
|
|
member.setAccessible(true);
|
|
var launch = fx(() -> (HostLaunch) member.get(pane));
|
|
Path desk =
|
|
library.resolve(
|
|
"fx-example/src/main/resources/de/neitzel/fx/example/view/Desk.fxml");
|
|
var host = pane.kit().host();
|
|
try (var workflow =
|
|
new DesignerWorkflow(
|
|
host,
|
|
launch.configuration(),
|
|
launch.context(),
|
|
launch.maven(),
|
|
launch.tools(),
|
|
System.out::println);
|
|
var document =
|
|
host.documents().open(desk.toUri()).toCompletableFuture().get()) {
|
|
var original = document.snapshot().toCompletableFuture().get();
|
|
var metadata =
|
|
workflow
|
|
.bindings(false, original, "name", "both")
|
|
.toCompletableFuture()
|
|
.get(60, TimeUnit.SECONDS);
|
|
check(
|
|
metadata.model().equals("de.neitzel.fx.example.model.Person"),
|
|
"Wrong declared Model");
|
|
check(
|
|
metadata.diagnostics().stream()
|
|
.anyMatch(s -> s.contains("Generated Model metadata")),
|
|
"Generated metadata not used");
|
|
check(
|
|
metadata.problems().stream().noneMatch(p -> p.severity().equals("ERROR")),
|
|
"Binding errors: " + metadata.problems());
|
|
String edited = original.text() + "\n<!-- documented-example edit -->\n";
|
|
document
|
|
.apply(
|
|
new EditBatch(
|
|
original.revision(),
|
|
"Reference example edit",
|
|
List.of(
|
|
new EditBatch.Replacement(0, original.text().length(), edited))))
|
|
.toCompletableFuture()
|
|
.get();
|
|
check(
|
|
document.undo().toCompletableFuture().get().text().equals(original.text()),
|
|
"Undo changed source");
|
|
check(
|
|
document.redo().toCompletableFuture().get().text().equals(edited),
|
|
"Redo changed source");
|
|
var draft = document.snapshot().toCompletableFuture().get();
|
|
document.save(draft.revision()).toCompletableFuture().get();
|
|
check(Files.readString(desk).equals(edited), "Save changed source");
|
|
var restored = document.undo().toCompletableFuture().get();
|
|
document.save(restored.revision()).toCompletableFuture().get();
|
|
var examples =
|
|
workflow
|
|
.examplesFor(false, desk.toUri())
|
|
.toCompletableFuture()
|
|
.get(60, TimeUnit.SECONDS);
|
|
check(
|
|
examples.size() == 1 && examples.getFirst().id().equals("desk"),
|
|
"Wrong desk samples: " + examples);
|
|
workflow
|
|
.interactive(
|
|
false, "desk", List.of(document.snapshot().toCompletableFuture().get()))
|
|
.toCompletableFuture()
|
|
.get(60, TimeUnit.SECONDS);
|
|
workflow.cancel();
|
|
int windows = fx(() -> Window.getWindows().size());
|
|
var child =
|
|
workflow
|
|
.componentSource("de.neitzel.fx.example.view.AddressForm")
|
|
.toCompletableFuture()
|
|
.get(60, TimeUnit.SECONDS);
|
|
host.navigation()
|
|
.open(new HostServices.Location(child, 0))
|
|
.toCompletableFuture()
|
|
.get();
|
|
await(() -> fx(() -> Window.getWindows().size() == windows + 1));
|
|
host.navigation()
|
|
.open(new HostServices.Location(child, 0))
|
|
.toCompletableFuture()
|
|
.get();
|
|
check(
|
|
fx(() -> Window.getWindows().size()) == windows + 1,
|
|
"Duplicate Component editor");
|
|
for (var spec :
|
|
List.of(
|
|
new String[] {"AddressForm", "address"},
|
|
new String[] {"VisitForm", "visit"},
|
|
new String[] {"VisitRow", "row"},
|
|
new String[] {"NoteForm", "note"})) {
|
|
var path = desk.resolveSibling(spec[0] + ".fxml");
|
|
var choices =
|
|
workflow
|
|
.examplesFor(false, path.toUri())
|
|
.toCompletableFuture()
|
|
.get(60, TimeUnit.SECONDS);
|
|
check(
|
|
choices.size() == 1 && choices.getFirst().id().equals(spec[1]),
|
|
"Wrong component samples: " + choices);
|
|
try (var component =
|
|
host.documents().open(path.toUri()).toCompletableFuture().get()) {
|
|
workflow
|
|
.interactive(
|
|
false,
|
|
spec[1],
|
|
List.of(
|
|
document.snapshot().toCompletableFuture().get(),
|
|
component.snapshot().toCompletableFuture().get()))
|
|
.toCompletableFuture()
|
|
.get(60, TimeUnit.SECONDS);
|
|
workflow.cancel();
|
|
}
|
|
}
|
|
System.out.println(
|
|
"PASS real fx-example metadata, source history/save, navigation and all five"
|
|
+ " independent interactive samples");
|
|
}
|
|
Files.writeString(
|
|
output.resolve("workers.txt"),
|
|
String.join(
|
|
"\n",
|
|
ProcessHandle.current()
|
|
.descendants()
|
|
.map(p -> Long.toString(p.pid()))
|
|
.toList()));
|
|
fx(
|
|
() -> {
|
|
for (var window : List.copyOf(Window.getWindows()))
|
|
if (window instanceof Stage stage) {
|
|
stage.setOnCloseRequest(null);
|
|
stage.close();
|
|
}
|
|
return null;
|
|
});
|
|
Files.writeString(
|
|
output.resolve("SUCCESS.txt"),
|
|
"PASS packaged designer with real NeitzelLib fx-example\n");
|
|
Platform.exit();
|
|
} catch (Throwable e) {
|
|
try (var out =
|
|
new java.io.PrintWriter(
|
|
Files.newBufferedWriter(output.resolve("FAILURE.txt")))) {
|
|
e.printStackTrace(out);
|
|
} catch (Exception ignoredWrite) {
|
|
}
|
|
e.printStackTrace();
|
|
System.exit(1);
|
|
}
|
|
});
|
|
}
|
|
}
|