1 /*
   2  * Copyright (c) 2010, 2014, 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 
  26 package javafx.scene.control;
  27 
  28 import com.sun.javafx.scene.control.infrastructure.StageLoader;
  29 import javafx.event.ActionEvent;
  30 import javafx.event.EventHandler;
  31 
  32 import javafx.geometry.Side;
  33 import org.junit.After;
  34 import org.junit.Before;
  35 import org.junit.Test;
  36 
  37 import static org.junit.Assert.*;
  38 import static com.sun.javafx.scene.control.skin.ContextMenuContentRetriever.*;
  39 
  40 public class ContextMenuTest {
  41     private MenuItem menuItem0, menuItem1, menuItem2, menuItem3;
  42 
  43     private ContextMenu contextMenu;
  44     private ContextMenu contextMenuWithOneItem;
  45     private ContextMenu contextMenuWithManyItems;
  46 
  47     private StageLoader sl;
  48     private Button anchorBtn;
  49 
  50     @Before public void setup() {
  51         // earlier test items
  52         menuItem0 = new MenuItem();
  53         menuItem1 = new MenuItem(); 
  54         menuItem2 = new MenuItem(); 
  55         menuItem3 = new MenuItem();
  56 
  57         contextMenu = new ContextMenu();
  58         contextMenuWithOneItem = new ContextMenu(menuItem0);
  59         contextMenuWithManyItems = new ContextMenu(menuItem1, menuItem2, menuItem3);
  60 
  61 
  62         // more recent test items
  63         // specify items (layout relates to the item positioning inside menus)
  64         menuItem = new MenuItem("MenuItem 1");
  65         subMenu = new Menu("submenu");
  66             subMenuItem1 = new MenuItem("SubMenuItem 1");
  67             customMenuItem = new CustomMenuItem(new Label("CustomMenuItem 1"));
  68 
  69         // install items into menus
  70         subMenu.getItems().setAll(subMenuItem1, customMenuItem);
  71 
  72         // for the show/hide tests, we need a stage with an anchor in it
  73         anchorBtn = new Button("Anchor");
  74         sl = new StageLoader(anchorBtn);
  75     }
  76 
  77     @After public void after() {
  78         sl.dispose();
  79     }
  80 
  81     @Test public void defaultGetId() {
  82         assertNull(contextMenu.getId());
  83     }
  84 
  85     @Test public void getStyleClassNotNull() {
  86         assertNotNull(contextMenu.getStyleClass());
  87     }
  88 
  89     @Test public void shouldBeAutoHideOn() {
  90         assertTrue(contextMenu.isAutoHide());
  91     }
  92 
  93     @Test public void shouldHaveZeroItems() {
  94         assertEquals(0, contextMenu.getItems().size());
  95     }
  96 
  97     @Test public void shouldHaveOneItem() {
  98         assertEquals(1, contextMenuWithOneItem.getItems().size());
  99     }
 100 
 101     @Test public void shouldHaveManyItems() {
 102         assertEquals(3, contextMenuWithManyItems.getItems().size());
 103     }
 104 
 105     @Test public void getDefaultSetOnActionHandler() {
 106         assertNull(contextMenu.getOnAction());
 107     }
 108 
 109     @Test public void getSpecifiedSetOnActionHandler() {
 110         EventHandlerStub handler = new EventHandlerStub();
 111         contextMenu.setOnAction(handler);
 112         assertEquals(handler, contextMenu.getOnAction());
 113     }
 114 
 115     @Test public void setTwiceAndGetSpecifiedSetOnActionHandler() {
 116         EventHandlerStub handler1 = new EventHandlerStub();
 117         EventHandlerStub handler2 = new EventHandlerStub();
 118         contextMenu.setOnAction(handler1);
 119         contextMenu.setOnAction(handler2);
 120         assertEquals(handler2, contextMenu.getOnAction());
 121     }
 122 
 123     @Test public void getNullSetOnActionHandler() {
 124         contextMenu.setOnAction(null);
 125         assertNull(contextMenu.getOnAction());
 126     }
 127 
 128     @Test public void defaultOnActionPropertyNotNull() {
 129         assertNotNull(contextMenu.onActionProperty());
 130     }
 131 
 132     @Test public void getOnActionPropertyBean() {
 133         assertEquals(contextMenu, contextMenu.onActionProperty().getBean());
 134     }
 135 
 136     @Test public void getOnActionPropertyName() {
 137         assertEquals("onAction", contextMenu.onActionProperty().getName());
 138     }
 139 
 140     @Test public void removedItemsAreChanged() {
 141         contextMenuWithManyItems.getItems().remove(menuItem2);
 142         assertNull(menuItem2.getParentPopup());
 143     }
 144 
 145     @Test public void addedItemsAreChanged() {
 146         MenuItem addedMenuItem = new MenuItem();
 147         contextMenuWithManyItems.getItems().add(addedMenuItem);
 148         assertEquals(contextMenuWithManyItems, addedMenuItem.getParentPopup());
 149     }
 150 
 151     @Test public void test_rt_34106_menus_should_not_be_reused() {
 152         // This test ensures the new behavior of ContextMenu's whereby it is only
 153         // allowed for a Menu/MenuItem to be in one parentPopup at a time.
 154         // Previously we allowed multiple ContextMenus to refer to the same
 155         // Menu/MenuItem, but this didn't work as there was no way to discern
 156         // when to show
 157         //
 158         MenuItem item1 = new MenuItem("MenuItem 1");
 159         Menu menu = new Menu("Menu");
 160         menu.getItems().addAll(item1);
 161 
 162         ContextMenu cm1 = new ContextMenu(menu);
 163         assertEquals(1, cm1.getItems().size());
 164         assertEquals(menu, cm1.getItems().get(0));
 165         assertEquals(cm1, menu.getParentPopup());
 166         assertEquals(cm1, item1.getParentPopup());
 167 
 168         ContextMenu cm2 = new ContextMenu(menu);
 169         assertEquals(0, cm1.getItems().size());
 170         assertEquals(1, cm2.getItems().size());
 171         assertEquals(menu, cm2.getItems().get(0));
 172         assertEquals(cm2, menu.getParentPopup());
 173         assertEquals(cm2, item1.getParentPopup());
 174     }
 175 
 176     public static final class EventHandlerStub implements EventHandler<ActionEvent> {
 177         boolean called = false;
 178         @Override public void handle(ActionEvent event) {
 179             called = true;
 180         }
 181     };
 182 
 183 
 184 
 185 
 186 
 187     private MenuItem menuItem;
 188     private Menu subMenu;
 189     private MenuItem subMenuItem1;
 190     private CustomMenuItem customMenuItem;
 191 
 192     private ContextMenu createContextMenu(boolean showMenu) {
 193         // create and return the context menu with the root menu in it
 194         ContextMenu contextMenu = new ContextMenu(menuItem, subMenu);
 195 
 196         if (showMenu) {
 197             contextMenu.show(anchorBtn, Side.RIGHT, 0, 0);
 198         }
 199         return contextMenu;
 200     }
 201 
 202     private void showMenu(ContextMenu cm, MenuItem... browseTo) {
 203         cm.show(anchorBtn, Side.RIGHT, 0, 0);
 204 
 205         if (browseTo == null) return;
 206 
 207         // navigate through the browseTo list, focusing / expanding as necessary
 208         for (int i = 0; i < browseTo.length; i++) {
 209             MenuItem item = browseTo[i];
 210 
 211             // find item in current showing menu
 212             boolean found = false;
 213             while (true) {
 214                 MenuItem focusedItem = getCurrentFocusedItem(cm);
 215                 if (item == focusedItem) {
 216                     found = true;
 217                     break;
 218                 }
 219 
 220                 pressDownKey(cm);
 221             }
 222 
 223             if (! found) {
 224                 break;
 225             } else {
 226                 if (item instanceof Menu) {
 227                     pressRightKey(cm);
 228                 }
 229             }
 230         }
 231     }
 232 
 233     private ContextMenu createContextMenuAndShowSubMenu() {
 234         ContextMenu cm = createContextMenu(true);
 235 
 236         // press down twice to go to subMenu
 237         pressDownKey(cm);
 238         pressDownKey(cm);
 239 
 240         // ensure the submenu isn't showing (it should only show on right-arrow)
 241         assertFalse(subMenu.isShowing());
 242 
 243         // open up the submenu
 244         pressRightKey(cm);
 245         assertTrue(subMenu.isShowing());
 246         assertEquals(subMenu, getShowingSubMenu(cm));
 247 
 248         // make sure the first item of the subMenu is focused
 249         MenuItem focusedItem = getCurrentFocusedItem(cm);
 250         assertEquals(0, getCurrentFocusedIndex(cm));
 251         assertEquals("Expected " + subMenuItem1.getText() + ", found " + focusedItem.getText(),
 252                 subMenuItem1, focusedItem);
 253 
 254         return cm;
 255     }
 256 
 257     @Test public void test_showAndHide() {
 258         ContextMenu cm = createContextMenu(false);
 259         assertFalse(cm.isShowing());
 260 
 261         cm.show(anchorBtn, Side.RIGHT, 0, 0);
 262         assertTrue(cm.isShowing());
 263 
 264         cm.hide();
 265         assertFalse(cm.isShowing());
 266     }
 267 
 268     @Test public void test_navigateMenu_downwards() {
 269         ContextMenu cm = createContextMenu(true);
 270 
 271         assertNotNull(getShowingMenuContent(cm));
 272         assertEquals(-1, getCurrentFocusedIndex(cm));
 273 
 274         // press down once to go to menuItem
 275         pressDownKey(cm);
 276         MenuItem focusedItem = getCurrentFocusedItem(cm);
 277         assertEquals(0, getCurrentFocusedIndex(cm));
 278         assertEquals("Expected " + menuItem.getText() + ", found " + focusedItem.getText(),
 279                 menuItem, focusedItem);
 280 
 281         // press down once to go to subMenu
 282         pressDownKey(cm);
 283         focusedItem = getCurrentFocusedItem(cm);
 284         assertEquals(1, getCurrentFocusedIndex(cm));
 285         assertEquals("Expected " + subMenu.getText() + ", found " + focusedItem.getText(),
 286                 subMenu, focusedItem);
 287 
 288         // ensure the submenu isn't showing (it should only show on right-arrow)
 289         assertFalse(subMenu.isShowing());
 290 
 291         // press down once more to loop back to the top (menuItem)
 292         pressDownKey(cm);
 293         focusedItem = getCurrentFocusedItem(cm);
 294         assertEquals(0, getCurrentFocusedIndex(cm));
 295         assertEquals("Expected " + menuItem.getText() + ", found " + focusedItem.getText(),
 296                 menuItem, focusedItem);
 297     }
 298 
 299     @Test public void test_navigateMenu_upwards() {
 300         ContextMenu cm = createContextMenu(true);
 301 
 302         assertNotNull(getShowingMenuContent(cm));
 303         assertEquals(-1, getCurrentFocusedIndex(cm));
 304 
 305         // press up once to loop to the last menu item (subMenu)
 306         pressUpKey(cm);
 307         MenuItem focusedItem = getCurrentFocusedItem(cm);
 308         assertEquals(1, getCurrentFocusedIndex(cm));
 309         assertEquals("Expected " + subMenu.getText() + ", found " + focusedItem.getText(),
 310                 subMenu, focusedItem);
 311 
 312         // press up once to go to menuItem
 313         pressDownKey(cm);
 314         focusedItem = getCurrentFocusedItem(cm);
 315         assertEquals(0, getCurrentFocusedIndex(cm));
 316         assertEquals("Expected " + menuItem.getText() + ", found " + focusedItem.getText(),
 317                 menuItem, focusedItem);
 318     }
 319 
 320     @Test public void test_navigateMenu_showSubMenu() {
 321         createContextMenuAndShowSubMenu();
 322     }
 323 
 324     @Test public void test_navigateSubMenu_downwards() {
 325         ContextMenu cm = createContextMenuAndShowSubMenu();
 326 
 327         // we now have focus in the submenu, and on its first item, so lets navigate it
 328         assertNotNull(getShowingMenuContent(cm));
 329         MenuItem focusedItem = getCurrentFocusedItem(cm);
 330         assertEquals(0, getCurrentFocusedIndex(cm));
 331         assertEquals("Expected " + subMenuItem1.getText() + ", found " + focusedItem.getText(),
 332                 subMenuItem1, focusedItem);
 333 
 334         // press down once to go to customMenuItem
 335         pressDownKey(cm);
 336         focusedItem = getCurrentFocusedItem(cm);
 337         assertEquals(1, getCurrentFocusedIndex(cm));
 338         assertEquals("Expected " + customMenuItem.getText() + ", found " + focusedItem.getText(),
 339                 customMenuItem, focusedItem);
 340 
 341         // press down once to go to wrap back around to subMenuItem1
 342         pressDownKey(cm);
 343         focusedItem = getCurrentFocusedItem(cm);
 344         assertEquals(0, getCurrentFocusedIndex(cm));
 345         assertEquals("Expected " + subMenuItem1.getText() + ", found " + focusedItem.getText(),
 346                 subMenuItem1, focusedItem);
 347     }
 348 
 349     @Test public void test_navigateSubMenu_upwards() {
 350         ContextMenu cm = createContextMenuAndShowSubMenu();
 351 
 352         // we now have focus in the submenu, and on its first item, so lets navigate it
 353         assertNotNull(getShowingMenuContent(cm));
 354         MenuItem focusedItem = getCurrentFocusedItem(cm);
 355         assertEquals(0, getCurrentFocusedIndex(cm));
 356         assertEquals("Expected " + subMenuItem1.getText() + ", found " + focusedItem.getText(),
 357                 subMenuItem1, focusedItem);
 358 
 359         // press up once to go to customMenuItem
 360         pressUpKey(cm);
 361         focusedItem = getCurrentFocusedItem(cm);
 362         assertEquals(1, getCurrentFocusedIndex(cm));
 363         assertEquals("Expected " + customMenuItem.getText() + ", found " + focusedItem.getText(),
 364                 customMenuItem, focusedItem);
 365 
 366         // press up once to go to subMenuItem1
 367         pressUpKey(cm);
 368         focusedItem = getCurrentFocusedItem(cm);
 369         assertEquals(0, getCurrentFocusedIndex(cm));
 370         assertEquals("Expected " + subMenuItem1.getText() + ", found " + focusedItem.getText(),
 371                 subMenuItem1, focusedItem);
 372     }
 373 
 374     @Test public void test_navigateSubMenu_rightKeyDoesNothing() {
 375         ContextMenu cm = createContextMenuAndShowSubMenu();
 376 
 377         // we now have focus in the submenu, and on its first item, so lets navigate it
 378         pressRightKey(cm);
 379         assertNotNull(getShowingMenuContent(cm));
 380         MenuItem focusedItem = getCurrentFocusedItem(cm);
 381         assertEquals(0, getCurrentFocusedIndex(cm));
 382         assertEquals("Expected " + subMenuItem1.getText() + ", found " + focusedItem.getText(),
 383                 subMenuItem1, focusedItem);
 384     }
 385 
 386     @Test public void test_navigateSubMenu_leftKeyClosesSubMenu() {
 387         ContextMenu cm = createContextMenuAndShowSubMenu();
 388 
 389         // we now have focus in the submenu, and on its first item.
 390         // If we press left we expect the submenu to close and for focus to go
 391         // back to the parent menu.
 392         pressLeftKey(cm);
 393         assertNotNull(getShowingMenuContent(cm));
 394         MenuItem focusedItem = getCurrentFocusedItem(cm);
 395         assertEquals(1, getCurrentFocusedIndex(cm));
 396         assertEquals("Expected " + subMenu.getText() + ", found " + focusedItem.getText(),
 397                 subMenu, focusedItem);
 398     }
 399 
 400     private int rt_37127_count = 0;
 401     @Test public void test_rt_37127_keyboard() {
 402         ContextMenu cm = createContextMenuAndShowSubMenu();
 403 
 404         customMenuItem.setOnAction(event -> rt_37127_count++);
 405 
 406         // we now have focus in the submenu, and on its first item.
 407         // For this test we now need to move focus down to the custom menu item
 408         // and press the enter key. We expect to only receive one event
 409         pressDownKey(cm);
 410         MenuItem focusedItem = getCurrentFocusedItem(cm);
 411         assertEquals(1, getCurrentFocusedIndex(cm));
 412         assertEquals(customMenuItem, focusedItem);
 413 
 414         assertEquals(0, rt_37127_count);
 415         pressEnterKey(cm);
 416         assertEquals(1, rt_37127_count);
 417 
 418         // now go back to the customMenuItem and press it again
 419         showMenu(cm, subMenu, customMenuItem);
 420         pressEnterKey(cm);
 421         assertEquals(2, rt_37127_count);
 422 
 423         // and once more....
 424         showMenu(cm, subMenu, customMenuItem);
 425         pressEnterKey(cm);
 426         assertEquals(3, rt_37127_count);
 427     }
 428 
 429     @Test public void test_rt_37127_mouse() {
 430         ContextMenu cm = createContextMenuAndShowSubMenu();
 431 
 432         customMenuItem.setOnAction(event -> rt_37127_count++);
 433 
 434         // we now have focus in the submenu, and on its first item.
 435         // For this test we now need to move focus down to the custom menu item
 436         // and press the enter key. We expect to only receive one event
 437         pressDownKey(cm);
 438         MenuItem focusedItem = getCurrentFocusedItem(cm);
 439         assertEquals(1, getCurrentFocusedIndex(cm));
 440         assertEquals(customMenuItem, focusedItem);
 441 
 442         assertEquals(0, rt_37127_count);
 443         pressMouseButton(cm);
 444         assertEquals(1, rt_37127_count);
 445 
 446         // now go back to the customMenuItem and press it again
 447         showMenu(cm, subMenu, customMenuItem);
 448         pressMouseButton(cm);
 449         assertEquals(2, rt_37127_count);
 450 
 451         // and once more....
 452         showMenu(cm, subMenu, customMenuItem);
 453         pressMouseButton(cm);
 454         assertEquals(3, rt_37127_count);
 455     }
 456 
 457     @Test public void test_rt_37102() {
 458         // This resulted in a NPE before the bug was fixed
 459         ContextMenu cm = createContextMenuAndShowSubMenu();
 460         pressLeftKey(cm);
 461         showMenu(cm, subMenu);
 462     }
 463 
 464     @Test public void test_rt_37091() {
 465         ContextMenu cm = createContextMenuAndShowSubMenu();
 466 
 467         assertEquals(subMenu, getShowingSubMenu(cm));
 468         assertEquals(subMenu, getOpenSubMenu(cm));
 469 
 470         cm.hide();
 471         assertNull(getOpenSubMenu(cm));
 472 
 473         cm.getItems().clear();
 474         cm.getItems().add(subMenu);
 475 
 476         assertNull(getOpenSubMenu(cm));
 477         cm.show(anchorBtn, Side.RIGHT, 0, 0);
 478         pressDownKey(cm);
 479         pressDownKey(cm);
 480         pressRightKey(cm);
 481         assertEquals(subMenu, getShowingSubMenu(cm));
 482         assertEquals(subMenuItem1, getCurrentFocusedItem(cm));
 483     }
 484 }