|
37 | 37 | import java.util.ArrayList; |
38 | 38 | import java.util.List; |
39 | 39 | import java.util.Map; |
| 40 | +import java.util.Objects; |
| 41 | +import java.util.stream.Stream; |
| 42 | +import java.util.concurrent.CountDownLatch; |
| 43 | +import java.util.concurrent.TimeUnit; |
40 | 44 | import java.util.concurrent.atomic.AtomicReference; |
41 | 45 |
|
42 | 46 | import static org.junit.jupiter.api.Assertions.*; |
@@ -438,6 +442,138 @@ void testBuildSplitPane() { |
438 | 442 | SplitPane splitPane = (SplitPane) view; |
439 | 443 | assertEquals(2, splitPane.getItems().size()); |
440 | 444 | } |
| 445 | + @Test |
| 446 | + void testHorizontalSplitPaneHasDockStyleAndDividerNode() { |
| 447 | + DockSplitPane root = new DockSplitPane(Orientation.HORIZONTAL); |
| 448 | + root.addChild(new DockNode(new Label("Left"), "Left")); |
| 449 | + root.addChild(new DockNode(new Label("Right"), "Right")); |
| 450 | + dockGraph.setRoot(root); |
| 451 | + |
| 452 | + SplitPane splitPane = assertInstanceOf(SplitPane.class, layoutEngine.buildSceneGraph()); |
| 453 | + Scene scene = createStyledScene(splitPane, 900, 600); |
| 454 | + scene.getRoot().applyCss(); |
| 455 | + scene.getRoot().layout(); |
| 456 | + waitForFxEvents(); |
| 457 | + |
| 458 | + assertTrue(splitPane.getStyleClass().contains(DockThemeStyleClasses.DOCK_SPLIT_PANE)); |
| 459 | + assertTrue(countDirectDividerNodes(splitPane) >= 1, "Expected at least one direct divider node"); |
| 460 | + } |
| 461 | + |
| 462 | + @Test |
| 463 | + void testVerticalSplitPaneHasDockStyleAndDividerNode() { |
| 464 | + DockSplitPane root = new DockSplitPane(Orientation.VERTICAL); |
| 465 | + root.addChild(new DockNode(new Label("Top"), "Top")); |
| 466 | + root.addChild(new DockNode(new Label("Bottom"), "Bottom")); |
| 467 | + dockGraph.setRoot(root); |
| 468 | + |
| 469 | + SplitPane splitPane = assertInstanceOf(SplitPane.class, layoutEngine.buildSceneGraph()); |
| 470 | + Scene scene = createStyledScene(splitPane, 900, 600); |
| 471 | + scene.getRoot().applyCss(); |
| 472 | + scene.getRoot().layout(); |
| 473 | + waitForFxEvents(); |
| 474 | + |
| 475 | + assertTrue(splitPane.getStyleClass().contains(DockThemeStyleClasses.DOCK_SPLIT_PANE)); |
| 476 | + assertTrue(countDirectDividerNodes(splitPane) >= 1, "Expected at least one direct divider node"); |
| 477 | + } |
| 478 | + |
| 479 | + @Test |
| 480 | + void testVerticalSplitPaneDividerNodesUpdateAfterAddingChild() { |
| 481 | + DockSplitPane root = new DockSplitPane(Orientation.VERTICAL); |
| 482 | + root.addChild(new DockNode(new Label("Top"), "Top")); |
| 483 | + root.addChild(new DockNode(new Label("Middle"), "Middle")); |
| 484 | + dockGraph.setRoot(root); |
| 485 | + |
| 486 | + SplitPane splitPane = assertInstanceOf(SplitPane.class, layoutEngine.buildSceneGraph()); |
| 487 | + Scene scene = createStyledScene(splitPane, 900, 700); |
| 488 | + scene.getRoot().applyCss(); |
| 489 | + scene.getRoot().layout(); |
| 490 | + waitForFxEvents(); |
| 491 | + |
| 492 | + assertTrue(splitPane.getStyleClass().contains(DockThemeStyleClasses.DOCK_SPLIT_PANE)); |
| 493 | + assertTrue(countDirectDividerNodes(splitPane) >= 1, "Expected one divider node for two split items"); |
| 494 | + |
| 495 | + root.addChild(new DockNode(new Label("Bottom"), "Bottom")); |
| 496 | + scene.getRoot().applyCss(); |
| 497 | + scene.getRoot().layout(); |
| 498 | + waitForFxEvents(); |
| 499 | + |
| 500 | + assertTrue(splitPane.getDividers().size() >= 2, "Expected an additional divider after adding third child"); |
| 501 | + assertTrue(countDirectDividerNodes(splitPane) >= 2, "Expected direct divider nodes to match multi-split layout"); |
| 502 | + } |
| 503 | + |
| 504 | + @Test |
| 505 | + void testDividerNodesRemainPresentAfterMovingPropertiesNodeToBottomAcrossFullWidth() { |
| 506 | + DockNode projectNode = new DockNode(new Label("Project"), "Project Explorer"); |
| 507 | + DockNode editorNode = new DockNode(new Label("Editor"), "Main.java"); |
| 508 | + DockNode propertiesNode = new DockNode(new Label("Properties"), "Properties"); |
| 509 | + DockNode consoleNode = new DockNode(new Label("Console"), "Console"); |
| 510 | + DockNode tasksNode = new DockNode(new Label("Tasks"), "Tasks"); |
| 511 | + |
| 512 | + dockGraph.setRoot(projectNode); |
| 513 | + dockGraph.dock(editorNode, projectNode, DockPosition.RIGHT); |
| 514 | + dockGraph.dock(propertiesNode, editorNode, DockPosition.RIGHT); |
| 515 | + dockGraph.dock(consoleNode, editorNode, DockPosition.BOTTOM); |
| 516 | + dockGraph.dock(tasksNode, consoleNode, DockPosition.CENTER); |
| 517 | + |
| 518 | + DockElement initialRoot = dockGraph.getRoot(); |
| 519 | + assertNotNull(initialRoot); |
| 520 | + dockGraph.move(propertiesNode, initialRoot, DockPosition.BOTTOM); |
| 521 | + |
| 522 | + SplitPane rootSplit = assertInstanceOf(SplitPane.class, layoutEngine.buildSceneGraph()); |
| 523 | + assertEquals(Orientation.VERTICAL, rootSplit.getOrientation()); |
| 524 | + |
| 525 | + Scene scene = createStyledScene(rootSplit, 1200, 800); |
| 526 | + scene.getRoot().applyCss(); |
| 527 | + scene.getRoot().layout(); |
| 528 | + waitForFxEvents(); |
| 529 | + |
| 530 | + assertTrue(rootSplit.getStyleClass().contains(DockThemeStyleClasses.DOCK_SPLIT_PANE)); |
| 531 | + assertTrue(countDirectDividerNodes(rootSplit) >= 1, "Expected root split to expose divider node"); |
| 532 | + |
| 533 | + List<SplitPane> nestedVerticalSplits = rootSplit.lookupAll(".split-pane").stream() |
| 534 | + .filter(SplitPane.class::isInstance) |
| 535 | + .map(SplitPane.class::cast) |
| 536 | + .filter(split -> split != rootSplit && split.getOrientation() == Orientation.VERTICAL) |
| 537 | + .toList(); |
| 538 | + nestedVerticalSplits.forEach(split -> { |
| 539 | + assertTrue(split.getStyleClass().contains(DockThemeStyleClasses.DOCK_SPLIT_PANE)); |
| 540 | + if (split.getItems().size() > 1) { |
| 541 | + assertTrue(countDirectDividerNodes(split) >= 1, "Expected divider nodes for nested multi-item split"); |
| 542 | + } |
| 543 | + }); |
| 544 | + } |
| 545 | + |
| 546 | + @Test |
| 547 | + void testAllDockSplitPaneViewsRetainStyleClassAfterDnDRebuild() { |
| 548 | + DockNode projectNode = new DockNode(new Label("Project"), "Project Explorer"); |
| 549 | + DockNode editorNode = new DockNode(new Label("Editor"), "Main.java"); |
| 550 | + DockNode propertiesNode = new DockNode(new Label("Properties"), "Properties"); |
| 551 | + DockNode consoleNode = new DockNode(new Label("Console"), "Console"); |
| 552 | + DockNode tasksNode = new DockNode(new Label("Tasks"), "Tasks"); |
| 553 | + |
| 554 | + dockGraph.setRoot(projectNode); |
| 555 | + dockGraph.dock(editorNode, projectNode, DockPosition.RIGHT); |
| 556 | + dockGraph.dock(propertiesNode, editorNode, DockPosition.RIGHT); |
| 557 | + dockGraph.dock(consoleNode, editorNode, DockPosition.BOTTOM); |
| 558 | + dockGraph.dock(tasksNode, consoleNode, DockPosition.CENTER); |
| 559 | + |
| 560 | + DockElement rootBeforeMove = dockGraph.getRoot(); |
| 561 | + assertNotNull(rootBeforeMove); |
| 562 | + dockGraph.move(propertiesNode, rootBeforeMove, DockPosition.BOTTOM); |
| 563 | + |
| 564 | + SplitPane rootSplit = assertInstanceOf(SplitPane.class, layoutEngine.buildSceneGraph()); |
| 565 | + Scene scene = createStyledScene(rootSplit, 1200, 800); |
| 566 | + scene.getRoot().applyCss(); |
| 567 | + scene.getRoot().layout(); |
| 568 | + waitForFxEvents(); |
| 569 | + |
| 570 | + List<SplitPane> splitPanes = collectDockSplitPanes(rootSplit); |
| 571 | + assertFalse(splitPanes.isEmpty(), "Expected at least one split pane in dock layout"); |
| 572 | + splitPanes.forEach(splitPane -> assertTrue( |
| 573 | + splitPane.getStyleClass().contains(DockThemeStyleClasses.DOCK_SPLIT_PANE), |
| 574 | + "Expected split pane to contain style class '" + DockThemeStyleClasses.DOCK_SPLIT_PANE + "'" |
| 575 | + )); |
| 576 | + } |
441 | 577 |
|
442 | 578 | @Test |
443 | 579 | void testAddElementZonesHandlesSmallLeafBoundsWithoutClampException() { |
@@ -836,7 +972,46 @@ void testLargeLayoutCacheCleanupDuringDetachAttachCycles() { |
836 | 972 | } |
837 | 973 | } |
838 | 974 | } |
| 975 | + private List<Node> getDirectDividerNodes(SplitPane splitPane) { |
| 976 | + return splitPane.getChildrenUnmodifiable().stream() |
| 977 | + .filter(child -> child.getStyleClass().contains("split-pane-divider")) |
| 978 | + .toList(); |
| 979 | + } |
| 980 | + |
| 981 | + private int countDirectDividerNodes(SplitPane splitPane) { |
| 982 | + return getDirectDividerNodes(splitPane).size(); |
| 983 | + } |
839 | 984 |
|
| 985 | + private Scene createStyledScene(Node contentNode, double width, double height) { |
| 986 | + Scene scene = new Scene(new StackPane(contentNode), width, height); |
| 987 | + String stylesheetUrl = Objects.requireNonNull( |
| 988 | + DockLayoutEngineTest.class.getResource("/snapfx.css"), |
| 989 | + "Expected snapfx.css test resource to be available" |
| 990 | + ).toExternalForm(); |
| 991 | + scene.getStylesheets().add(stylesheetUrl); |
| 992 | + return scene; |
| 993 | + } |
| 994 | + |
| 995 | + private List<SplitPane> collectDockSplitPanes(SplitPane rootSplit) { |
| 996 | + return Stream.concat( |
| 997 | + Stream.of(rootSplit), |
| 998 | + rootSplit.lookupAll(".split-pane").stream() |
| 999 | + .filter(SplitPane.class::isInstance) |
| 1000 | + .map(SplitPane.class::cast) |
| 1001 | + ) |
| 1002 | + .distinct() |
| 1003 | + .toList(); |
| 1004 | + } |
| 1005 | + private void waitForFxEvents() { |
| 1006 | + CountDownLatch latch = new CountDownLatch(1); |
| 1007 | + Platform.runLater(latch::countDown); |
| 1008 | + try { |
| 1009 | + assertTrue(latch.await(5, TimeUnit.SECONDS), "Timed out waiting for JavaFX events"); |
| 1010 | + } catch (InterruptedException interruptedException) { |
| 1011 | + Thread.currentThread().interrupt(); |
| 1012 | + fail("Interrupted while waiting for JavaFX events", interruptedException); |
| 1013 | + } |
| 1014 | + } |
840 | 1015 | private List<DockNode> buildLargeLayout(int nodeCount) { |
841 | 1016 | List<DockNode> nodes = new ArrayList<>(nodeCount); |
842 | 1017 | DockPosition[] positions = { |
|
0 commit comments