1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package test.robot.javafx.scene;
  26 
  27 import javafx.application.Application;
  28 import javafx.application.Platform;
  29 import javafx.collections.ListChangeListener;
  30 import javafx.scene.Scene;
  31 import javafx.scene.control.Tab;
  32 import javafx.scene.control.TabPane;
  33 import javafx.scene.input.MouseButton;
  34 import javafx.scene.robot.Robot;
  35 import javafx.stage.Stage;
  36 import javafx.stage.StageStyle;
  37 import javafx.stage.WindowEvent;
  38 import javafx.geometry.Side;
  39 
  40 import java.util.concurrent.CountDownLatch;
  41 import java.util.concurrent.TimeUnit;
  42 
  43 import org.junit.After;
  44 import org.junit.AfterClass;
  45 import org.junit.Assert;
  46 import org.junit.Before;
  47 import org.junit.BeforeClass;
  48 import org.junit.Test;
  49 import static org.junit.Assert.fail;
  50 
  51 import test.util.Util;
  52 
  53 /*
  54  * Unit test for verifying DragPolicies.
  55  *
  56  * There are 8 tests in this file.
  57  * Steps of 4 tests for DragPolicy.REORDER
  58  * 1. Create TabPane with 4 tabs.
  59  * 2. Drag tab0 to last after tab3.
  60  * 3. Verify that tab1 is the first tab after reorder.
  61  * 4. Verify that a correct permutation change event is received.
  62  * 5. Verify that getTabs() is also reordered correctly.
  63  * Repeat the test for four Sides.
  64  *
  65  * Steps of 4 tests for DragPolicy.FIXED
  66  * 1. Create TabPane with 4 tabs.
  67  * 2. Drag tab0 to last after tab3, the tab0 should not get dragged.
  68  * 3. Verify that tab0 is still the first tab.
  69  * 4. Verify that permutation change event is not received.
  70  * Repeat the test for four Sides.
  71  */
  72 public class TabPaneDragPolicyTest {
  73     CountDownLatch[] latches;
  74     CountDownLatch changeListenerLatch;
  75     static CountDownLatch startupLatch;
  76     static Robot robot;
  77     static TabPane tabPane;
  78     static volatile Stage stage;
  79     static volatile Scene scene;
  80     static final int SCENE_WIDTH = 250;
  81     static final int SCENE_HEIGHT = SCENE_WIDTH;
  82     final int DRAG_DISTANCE = SCENE_WIDTH - 50;
  83     final int DX = 15;
  84     final int DY = DX;
  85     Tab[] tabs;
  86     Tab expectedTab;
  87     Tab selectedTab;
  88     final String PERMUTED_SEQ = "tab1tab2tab3tab0";
  89     final String REORDER_SEQ = "tab1tab2tab3tab0";
  90     boolean listenerTestResult = false;
  91     ReorderChangeListener reorderListener = new ReorderChangeListener();
  92     FixedChangeListener fixedListener = new FixedChangeListener();
  93 
  94     class ReorderChangeListener implements ListChangeListener<Tab> {
  95         @Override
  96         public void onChanged(Change<? extends Tab> c) {
  97             while (c.next()) {
  98                 if (c.wasPermutated()) {
  99                     String list = "";
 100                     for (int i = c.getFrom(); i < c.getTo(); i++) {
 101                         list += tabPane.getTabs().get(i).getText();
 102                     }
 103                     listenerTestResult = list.equals(PERMUTED_SEQ);
 104                     list = "";
 105                     for (Tab t : tabPane.getTabs()) {
 106                         list += t.getText();
 107                     }
 108                     listenerTestResult = listenerTestResult && list.equals(REORDER_SEQ);
 109                     changeListenerLatch.countDown();
 110                 }
 111             }
 112         };
 113     }
 114 
 115     class FixedChangeListener implements ListChangeListener<Tab> {
 116         @Override
 117         public void onChanged(Change<? extends Tab> c) {
 118             listenerTestResult = false;
 119         };
 120     }
 121 
 122     public static void main(String[] args) {
 123         initFX();
 124         TabPaneDragPolicyTest test = new TabPaneDragPolicyTest();
 125 
 126         test.testReorderTop();
 127         test.testReorderBottom();
 128         test.testReorderLeft();
 129         test.testReorderRight();
 130 
 131         test.testFixedTop();
 132         test.testFixedBottom();
 133         test.testFixedLeft();
 134         test.testFixedRight();
 135 
 136         exit();
 137     }
 138 
 139     @Test
 140     public void testReorderTop() {
 141         expectedTab = tabs[1];
 142         setDragPolicyAndSide(TabPane.TabDragPolicy.REORDER, Side.TOP);
 143         tabPane.getTabs().addListener(reorderListener);
 144         testReorder(DX, DY, 1, 0, false);
 145         tabPane.getTabs().removeListener(reorderListener);
 146         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 147         Assert.assertEquals("Expected " + expectedTab.getText() + " to be "
 148             + "first tab after reordering.", expectedTab.getText(), selectedTab.getText());
 149         Assert.assertTrue("Incorrect permutation change received", listenerTestResult);
 150     }
 151 
 152     @Test
 153     public void testReorderBottom() {
 154         expectedTab = tabs[1];
 155         setDragPolicyAndSide(TabPane.TabDragPolicy.REORDER, Side.BOTTOM);
 156         tabPane.getTabs().addListener(reorderListener);
 157         testReorder(DX, SCENE_HEIGHT - DY, 1, 0, false);
 158         tabPane.getTabs().removeListener(reorderListener);
 159         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 160         Assert.assertEquals("Expected " + expectedTab.getText() + " to be "
 161             + "first tab after reordering.", expectedTab.getText(), selectedTab.getText());
 162         Assert.assertTrue("Incorrect permutation change received", listenerTestResult);
 163     }
 164 
 165     @Test
 166     public void testReorderLeft() {
 167         expectedTab = tabs[1];
 168         setDragPolicyAndSide(TabPane.TabDragPolicy.REORDER, Side.LEFT);
 169         tabPane.getTabs().addListener(reorderListener);
 170         testReorder(DX, DY, 0, 1, false);
 171         tabPane.getTabs().removeListener(reorderListener);
 172         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 173         Assert.assertEquals("Expected " + expectedTab.getText() + " to be "
 174             + "first tab after reordering.", expectedTab.getText(), selectedTab.getText());
 175         Assert.assertTrue("Incorrect permutation change received", listenerTestResult);
 176     }
 177 
 178     @Test
 179     public void testReorderRight() {
 180         expectedTab = tabs[1];
 181         setDragPolicyAndSide(TabPane.TabDragPolicy.REORDER, Side.RIGHT);
 182         tabPane.getTabs().addListener(reorderListener);
 183         testReorder(SCENE_WIDTH - DX, DY, 0, 1, false);
 184         tabPane.getTabs().removeListener(reorderListener);
 185         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 186         Assert.assertEquals("Expected " + expectedTab.getText() + " to be "
 187             + "first tab after reordering.", expectedTab.getText(), selectedTab.getText());
 188         Assert.assertTrue("Incorrect permutation change received", listenerTestResult);
 189     }
 190 
 191     @Test
 192     public void testFixedTop() {
 193         expectedTab = tabs[0];
 194         listenerTestResult = true;
 195         setDragPolicyAndSide(TabPane.TabDragPolicy.FIXED, Side.TOP);
 196         tabPane.getTabs().addListener(fixedListener);
 197         testReorder(DX, DY, 1, 0, true);
 198         tabPane.getTabs().removeListener(fixedListener);
 199         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 200         Assert.assertEquals("Expected " + expectedTab.getText() + " to remain "
 201             + "first tab.", expectedTab.getText(), selectedTab.getText());
 202         Assert.assertTrue("Change event should not be received", listenerTestResult);
 203     }
 204 
 205     @Test
 206     public void testFixedBottom() {
 207         expectedTab = tabs[0];
 208         listenerTestResult = true;
 209         setDragPolicyAndSide(TabPane.TabDragPolicy.FIXED, Side.BOTTOM);
 210         tabPane.getTabs().addListener(fixedListener);
 211         testReorder(DX, SCENE_HEIGHT - DY, 1, 0, true);
 212         tabPane.getTabs().removeListener(fixedListener);
 213         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 214         Assert.assertEquals("Expected " + expectedTab.getText() + " to remain "
 215             + "first tab.", expectedTab.getText(), selectedTab.getText());
 216         Assert.assertTrue("Change event should not be received", listenerTestResult);
 217     }
 218 
 219     @Test
 220     public void testFixedLeft() {
 221         expectedTab = tabs[0];
 222         listenerTestResult = true;
 223         setDragPolicyAndSide(TabPane.TabDragPolicy.FIXED, Side.LEFT);
 224         tabPane.getTabs().addListener(fixedListener);
 225         testReorder(DX, DY, 0, 1, true);
 226         tabPane.getTabs().removeListener(fixedListener);
 227         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 228         Assert.assertEquals("Expected " + expectedTab.getText() + " to remain "
 229             + "first tab.", expectedTab.getText(), selectedTab.getText());
 230         Assert.assertTrue("Change event should not be received", listenerTestResult);
 231     }
 232 
 233     @Test
 234     public void testFixedRight() {
 235         expectedTab = tabs[0];
 236         listenerTestResult = true;
 237         setDragPolicyAndSide(TabPane.TabDragPolicy.FIXED, Side.RIGHT);
 238         tabPane.getTabs().addListener(fixedListener);
 239         testReorder(SCENE_WIDTH - DX, DY, 0, 1, true);
 240         tabPane.getTabs().removeListener(fixedListener);
 241         selectedTab = (Tab)tabPane.getSelectionModel().getSelectedItem();
 242         Assert.assertEquals("Expected " + expectedTab.getText() + " to remain "
 243             + "first tab.", expectedTab.getText(), selectedTab.getText());
 244         Assert.assertTrue("Change event should not be received", listenerTestResult);
 245     }
 246 
 247     public void testReorder(int dX, int dY, int xIncr, int yIncr, boolean isFixed) {
 248         try {
 249             Thread.sleep(1000); // Wait for tabPane to layout
 250         } catch (Exception ex) {
 251             fail("Thread was interrupted." + ex);
 252         }
 253         Platform.runLater(() -> {
 254             robot.mouseMove((int)(scene.getWindow().getX() + scene.getX() + dX),
 255                 (int)(scene.getWindow().getY() + scene.getY() + dY));
 256             robot.mousePress(MouseButton.PRIMARY);
 257             robot.mouseRelease(MouseButton.PRIMARY);
 258         });
 259         waitForLatch(latches[0], 5, "Timeout waiting tabs[0] to get selected.");
 260 
 261         CountDownLatch pressLatch = new CountDownLatch(1);
 262         Platform.runLater(() -> {
 263             robot.mousePress(MouseButton.PRIMARY);
 264             pressLatch.countDown();
 265         });
 266         waitForLatch(pressLatch, 5, "Timeout waiting for robot.mousePress(Robot.MOUSE_LEFT_BTN).");
 267         for (int i = 0; i < DRAG_DISTANCE; i++) {
 268             final int c = i;
 269             CountDownLatch moveLatch = new CountDownLatch(1);
 270             Platform.runLater(() -> {
 271                 if (xIncr > 0) {
 272                     // Top & Bottom
 273                     robot.mouseMove((int)(scene.getWindow().getX() + scene.getX() + dX) + c,
 274                         (int)(scene.getWindow().getY() + scene.getY() + dY));
 275                 } else {
 276                     // Left & Right
 277                     robot.mouseMove((int)(scene.getWindow().getX() + scene.getX() + dX),
 278                         (int)(scene.getWindow().getY() + scene.getY() + dY) + c);
 279                 }
 280                 moveLatch.countDown();
 281             });
 282             waitForLatch(moveLatch, 5, "Timeout waiting for robot.mouseMove(023).");
 283         }
 284 
 285         CountDownLatch releaseLatch = new CountDownLatch(1);
 286         Platform.runLater(() -> {
 287             robot.mouseRelease(MouseButton.PRIMARY);
 288             releaseLatch.countDown();
 289         });
 290         waitForLatch(releaseLatch, 5, "Timeout waiting for robot.mouseRelease(Robot.MOUSE_LEFT_BTN).");
 291 
 292         if (isFixed) {
 293             tabPane.getSelectionModel().select(tabs[2]);
 294             waitForLatch(latches[2], 5, "Timeout waiting tabs[2] to get selected.");
 295             latches[0] = new CountDownLatch(1);
 296         }
 297 
 298         Platform.runLater(() -> {
 299             robot.mouseMove((int)(scene.getWindow().getX() + scene.getX() + dX),
 300                 (int)(scene.getWindow().getY() + scene.getY() + dY));
 301             robot.mousePress(MouseButton.PRIMARY);
 302             robot.mouseRelease(MouseButton.PRIMARY);
 303         });
 304 
 305         if (isFixed) {
 306             // For FIXED drag policy, tabs[0] should remain the first tab.
 307             try {
 308                 Thread.sleep(500); // Wait for ChangeListener to get called.
 309             } catch (Exception ex) {
 310                 fail("Thread was interrupted." + ex);
 311             }
 312             waitForLatch(latches[0], 5, "Timeout waiting tabs[0] to get selected.");
 313         } else {
 314             // For REORDER drag policy, tabs[1] should be the first tab.
 315             waitForLatch(changeListenerLatch, 5, "Timeout waiting ChangeListener to get called.");
 316             waitForLatch(latches[1], 5, "Timeout waiting tabs[1] to get selected.");
 317         }
 318     }
 319 
 320     public static class TestApp extends Application {
 321         @Override
 322         public void start(Stage primaryStage) {
 323             robot = new Robot();
 324             stage = primaryStage;
 325             tabPane = new TabPane();
 326             tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
 327             scene = new Scene(tabPane, SCENE_WIDTH, SCENE_HEIGHT);
 328             stage.setScene(scene);
 329             stage.initStyle(StageStyle.UNDECORATED);
 330             stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e ->
 331                     Platform.runLater(startupLatch::countDown));
 332             stage.setAlwaysOnTop(true);
 333             stage.show();
 334         }
 335     }
 336 
 337     @BeforeClass
 338     public static void initFX() {
 339         startupLatch = new CountDownLatch(1);
 340         new Thread(() -> Application.launch(TestApp.class, (String[])null)).start();
 341         waitForLatch(startupLatch, 10, "Timeout waiting for FX runtime to start");
 342     }
 343 
 344     @AfterClass
 345     public static void exit() {
 346         Platform.runLater(() -> {
 347             stage.hide();
 348         });
 349         Platform.exit();
 350     }
 351 
 352     @Before
 353     public void setupTest() {
 354         changeListenerLatch = new CountDownLatch(1);
 355         latches = new CountDownLatch[4];
 356         CountDownLatch latch = new CountDownLatch(1);
 357         Platform.runLater(() -> {
 358             tabs = new Tab[4];
 359             for (int i = 0 ; i < 4; ++i) {
 360                 tabs[i] = new Tab("tab" + i);
 361             }
 362             tabPane.getTabs().addAll(tabs);
 363             tabPane.getSelectionModel().select(tabs[2]);
 364             for (int i = 0 ; i < 4; ++i) {
 365                 final int c = i;
 366                 latches[i] = new CountDownLatch(1);
 367                 tabs[i].setOnSelectionChanged(event -> {
 368                     latches[c].countDown();
 369                 });
 370             }
 371             latch.countDown();
 372         });
 373         waitForLatch(latch, 5, "Timeout waiting for setupTest().");
 374     }
 375 
 376     @After
 377     public void resetTest() {
 378         expectedTab = null;
 379         selectedTab = null;
 380         listenerTestResult = false;
 381         CountDownLatch latch = new CountDownLatch(1);
 382         Platform.runLater(() -> {
 383             for (int i = 0 ; i < 4; ++i) {
 384                 tabs[i].setOnSelectionChanged(null);
 385             }
 386             tabPane.getTabs().removeAll(tabs);
 387             tabs = null;
 388             latch.countDown();
 389         });
 390         waitForLatch(latch, 5, "Timeout waiting for resetTest().");
 391     }
 392 
 393     public static void waitForLatch(CountDownLatch latch, int seconds, String msg) {
 394         try {
 395             if (!latch.await(seconds, TimeUnit.SECONDS)) {
 396                 fail(msg);
 397             }
 398         } catch (Exception ex) {
 399             fail("Unexpected exception: " + ex);
 400         }
 401     }
 402 
 403     public void setDragPolicyAndSide(TabPane.TabDragPolicy dragPolicy, Side side) {
 404         Util.runAndWait(() -> {
 405             tabPane.setTabDragPolicy(dragPolicy);
 406             tabPane.setSide(side);
 407         });
 408     }
 409 }