package qualification; import com.intellij.openapi.application.*; import com.intellij.openapi.fileEditor.*; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import com.intellij.openapi.vfs.*; import com.oracle.javafx.scenebuilder.kit.fxom.*; import de.neitzel.fxdesigner.kit.*; import java.nio.file.*; import java.util.concurrent.*; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.scene.control.Button; /** Disposable companion plugin, never included in the delivered designer plugin. */ public final class WorkflowProbe implements StartupActivity.DumbAware { static final Path output = Path.of(System.getProperty("designer.check.output")); static DesignerPane active; static final java.util.concurrent.atomic.AtomicBoolean started = new java.util.concurrent.atomic.AtomicBoolean(); static T edt(Callable action) throws Exception { var f = new CompletableFuture(); ApplicationManager.getApplication() .invokeLater( () -> WriteIntentReadAction.run( () -> { try { f.complete(action.call()); } catch (Throwable t) { f.completeExceptionally(t); } }), ModalityState.nonModal()); return f.get(20, TimeUnit.SECONDS); } static T fx(Callable action) throws Exception { var f = new CompletableFuture(); Platform.runLater( () -> { try { f.complete(action.call()); } catch (Throwable t) { f.completeExceptionally(t); } }); return f.get(20, TimeUnit.SECONDS); } static JFXPanel bridge(java.awt.Component c) { if (c instanceof JFXPanel p) return p; if (c instanceof java.awt.Container parent) for (var child : parent.getComponents()) { var p = bridge(child); if (p != null) return p; } return null; } static void await(Callable condition) throws Exception { long until = System.nanoTime() + TimeUnit.SECONDS.toNanos(120); while (System.nanoTime() < until) { if (condition.call()) return; Thread.sleep(100); } throw new AssertionError("IDE workflow timed out"); } static void click(DesignerPane pane, String text) throws Exception { fx( () -> { ((Button) pane.lookupAll(".button").stream() .filter(n -> n instanceof Button b && b.getText().equals(text)) .findFirst() .orElseThrow()) .fire(); return null; }); } private static void checkProblems( Project project, VirtualFile file, DesignerPane pane, de.neitzel.fxdesigner.host.DocumentSession document) throws Exception { var original = document.snapshot().toCompletableFuture().get(); String invalid = original.text().replace("path=\"home\"", "path=\"problemMissing\""); if (invalid.equals(original.text())) throw new AssertionError("Missing fixture binding"); document .apply( new de.neitzel.fxdesigner.contracts.EditBatch( original.revision(), "Problem fixture", java.util.List.of( new de.neitzel.fxdesigner.contracts.EditBatch.Replacement( 0, original.text().length(), invalid)))) .toCompletableFuture() .get(); await( () -> fx( () -> pane.lookupAll(".text-area").stream() .anyMatch( n -> n instanceof javafx.scene.control.TextArea t && t.getText().equals(invalid)))); fx( () -> { pane.lookupAll(".titled-pane").stream() .filter( n -> n instanceof javafx.scene.control.TitledPane t && "Bindings".equals(t.getText())) .forEach(n -> ((javafx.scene.control.TitledPane) n).setExpanded(true)); pane.lookupAll(".tab-pane").stream() .filter(n -> n instanceof javafx.scene.control.TabPane) .map(n -> (javafx.scene.control.TabPane) n) .forEach( t -> t.getTabs().stream() .filter(tab -> "Problems".equals(tab.getText())) .findFirst() .ifPresent(tab -> t.getSelectionModel().select(tab))); return null; }); click(pane, "Validate View"); var collector = com.intellij.analysis.problemsView.ProblemsCollector.getInstance(project); await( () -> edt( () -> collector.getFileProblems(file).stream() .anyMatch(p -> p.getText().contains("problemMissing")))); var problem = edt( () -> (com.intellij.analysis.problemsView.FileProblem) collector.getFileProblems(file).stream() .filter(p -> p.getText().contains("problemMissing")) .findFirst() .orElseThrow()); int expectedOffset = edt( () -> { var d = FileDocumentManager.getInstance().getDocument(file); return d.getLineStartOffset(problem.getLine()) + problem.getColumn(); }); String declaration = invalid.substring(expectedOffset, invalid.indexOf('>', expectedOffset) + 1); if (!declaration.startsWith(" { var window = com.intellij.openapi.wm.ToolWindowManager.getInstance(project) .getToolWindow("Problems View"); if (window == null) throw new AssertionError("Native Problems tool window missing"); window.show(); new OpenFileDescriptor(project, problem.getFile(), problem.getLine(), problem.getColumn()) .navigate(true); return null; }); await( () -> edt( () -> { var textEditor = FileEditorManager.getInstance(project).getSelectedTextEditor(); return textEditor != null && textEditor.getCaretModel().getOffset() == expectedOffset; })); if (!document.snapshot().toCompletableFuture().get().text().equals(invalid)) throw new AssertionError("Problems navigation mutated source"); document.undo().toCompletableFuture().get(); await( () -> edt( () -> collector.getFileProblems(file).stream() .noneMatch(p -> p.getText().contains("problemMissing")))); if (!document.snapshot().toCompletableFuture().get().text().equals(original.text())) throw new AssertionError("Problems navigation changed undo history"); // Observe the shared editor's Undo revision before issuing Redo; its previous invalid // text must not satisfy the Redo wait while the Undo notification is still queued. await( () -> fx( () -> pane.lookupAll(".text-area").stream() .anyMatch( n -> n instanceof javafx.scene.control.TextArea t && t.getText().equals(original.text())))); document.redo().toCompletableFuture().get(); await( () -> fx( () -> pane.lookupAll(".text-area").stream() .anyMatch( n -> n instanceof javafx.scene.control.TextArea t && t.getText().equals(invalid)))); click(pane, "Validate View"); await( () -> edt( () -> collector.getFileProblems(file).stream() .anyMatch(p -> p.getText().contains("problemMissing")))); edt( () -> { FileEditorManager.getInstance(project).closeFile(file); return null; }); await( () -> edt( () -> collector.getFileProblems(file).stream() .noneMatch(p -> "Neitzel FX Designer".equals(p.getGroup())))); System.out.println( "PASS native Problems: current validation, FXML origin/navigation, correction via Undo," + " redo validation and editor-close cleanup"); } public void runActivity(Project project) { if (!started.compareAndSet(false, true)) return; ApplicationManager.getApplication() .executeOnPooledThread( () -> { try { Path path = Path.of( project.getBasePath(), "app/src/main/resources/fixture/components/Person.fxml"); VirtualFile file = edt(() -> LocalFileSystem.getInstance().refreshAndFindFileByNioFile(path)); FileEditor editor = edt( () -> { var manager = FileEditorManager.getInstance(project); var all = manager.openFile(file, true); manager.setSelectedEditor(file, "neitzel-fx-designer"); return java.util.Arrays.stream(all) .filter(e -> e.getName().equals("Neitzel Designer")) .findFirst() .orElseThrow(); }); await(() -> edt(() -> bridge(editor.getComponent()) != null)); JFXPanel bridge = edt(() -> bridge(editor.getComponent())); await( () -> fx( () -> bridge.getScene() != null && bridge.getScene().getRoot() instanceof DesignerPane)); DesignerPane pane = fx(() -> (DesignerPane) bridge.getScene().getRoot()); active = pane; await(() -> fx(() -> pane.kit().editor().getFxomDocument() != null)); click(pane, "Build and Refresh"); await( () -> fx( () -> java.util.List.of("card", "second").stream() .allMatch( id -> { var item = new FXOMFxIdIndex( pane.kit().editor().getFxomDocument()) .lookup(id); return item instanceof FXOMInstance instance && instance.getSceneGraphObject() instanceof SnapshotRegion region && region.image() != null && region.statusText().equals("Target runtime"); }))); fx( () -> { var kit = pane.kit().editor(); var card = (FXOMInstance) new FXOMFxIdIndex(kit.getFxomDocument()).lookup("card"); kit.getSelection().select(card); var property = (FXOMPropertyT) card.getProperties() .get( new com.oracle.javafx.scenebuilder.kit.metadata.util .PropertyName("caption")); String old = property.getValue(); kit.getJobManager() .push( new com.oracle.javafx.scenebuilder.kit.editor.job.Job(kit) { public boolean isExecutable() { return true; } public String getDescription() { return "IDE caption check"; } public void execute() { set("IDE canvas draft"); } public void undo() { set(old); } public void redo() { execute(); } void set(String text) { var doc = kit.getFxomDocument(); doc.beginUpdate(); try { property.setValue(text); } finally { doc.endUpdate(); } } }); return null; }); await( () -> edt( () -> FileDocumentManager.getInstance() .getDocument(file) .getText() .contains("IDE canvas draft"))); click(pane, "Undo"); await( () -> edt( () -> FileDocumentManager.getInstance() .getDocument(file) .getText() .contains("FXML caption"))); click(pane, "Redo"); await( () -> edt( () -> FileDocumentManager.getInstance() .getDocument(file) .getText() .contains("IDE canvas draft"))); click(pane, "Render drafts"); await( () -> fx( () -> java.util.List.of("card", "second").stream() .allMatch( id -> { var item = new FXOMFxIdIndex( pane.kit().editor().getFxomDocument()) .lookup(id); return item instanceof FXOMInstance instance && instance.getSceneGraphObject() instanceof SnapshotRegion region && region.image() != null && region.statusText().equals("Target runtime"); }))); fx( () -> { javax.imageio.ImageIO.write( javafx.embed.swing.SwingFXUtils.fromFXImage( pane.snapshot(null, null), null), "png", output.resolve("intellij.png").toFile()); return null; }); try (var document = pane.kit().host().documents().open(path.toUri()).toCompletableFuture().get()) { de.neitzel.fxdesigner.kit.ProjectionHostChecks.run(pane, document); checkProblems(project, file, pane, document); } String version = fx(() -> System.getProperty("javafx.runtime.version")); if (!version.startsWith("26")) throw new AssertionError("Wrong host JavaFX: " + version); Files.writeString( output.resolve("SUCCESS.txt"), "PASS registered IntelliJ editor, shared Kit, real Build and Refresh, target" + " pixels, native document edits, undo/redo, draft render," + " source-preserving projection and extended Component scenarios; JavaFX " + version + "\n"); } catch (Throwable e) { try { if (active != null) fx( () -> { Files.writeString( output.resolve("ui-state.txt"), active.lookupAll(".label").stream() .filter(n -> n instanceof javafx.scene.control.Label) .map( n -> String.valueOf( ((javafx.scene.control.Label) n).getText())) .toList() .toString()); javax.imageio.ImageIO.write( javafx.embed.swing.SwingFXUtils.fromFXImage( active.snapshot(null, null), null), "png", output.resolve("failed.png").toFile()); return null; }); var w = new java.io.StringWriter(); e.printStackTrace(new java.io.PrintWriter(w)); Files.writeString(output.resolve("FAILURE.txt"), w.toString()); } catch (Exception ignored) { } } }); } }