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