popupmenu_test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.ui.PopupMenuTest');
  15. goog.setTestOnly('goog.ui.PopupMenuTest');
  16. goog.require('goog.dom');
  17. goog.require('goog.events.EventHandler');
  18. goog.require('goog.events.EventType');
  19. goog.require('goog.events.KeyCodes');
  20. goog.require('goog.math.Box');
  21. goog.require('goog.math.Coordinate');
  22. goog.require('goog.positioning.Corner');
  23. goog.require('goog.style');
  24. goog.require('goog.testing.events');
  25. goog.require('goog.testing.jsunit');
  26. goog.require('goog.ui.Menu');
  27. goog.require('goog.ui.MenuItem');
  28. goog.require('goog.ui.PopupMenu');
  29. var anchor;
  30. var menu;
  31. var menuitem;
  32. // Event handler
  33. var handler;
  34. var showPopup;
  35. var beforeShowPopupCalled;
  36. var popup;
  37. function setUp() {
  38. anchor = goog.dom.getElement('popup-anchor');
  39. menu = goog.dom.getElement('menu');
  40. menuitem1 = goog.dom.getElement('menuitem_1');
  41. menuitem3 = goog.dom.getElement('menuitem_3');
  42. handler = new goog.events.EventHandler();
  43. popup = new goog.ui.PopupMenu();
  44. }
  45. function tearDown() {
  46. handler.dispose();
  47. popup.dispose();
  48. }
  49. /**
  50. * Asserts properties of {@code target} matches the expected value.
  51. *
  52. * @param {Object} target The target specifying how the popup menu should be
  53. * attached to an anchor.
  54. * @param {Element} expectedElement The expected anchoring element.
  55. * @param {goog.positioning.Corner} expectedTargetCorner The expected value of
  56. * the {@code target.targetCorner_} property.
  57. * @param {goog.positioning.Corner} expectedMenuCorner The expected value of
  58. * the {@code target.menuCorner_} property.
  59. * @param {goog.events.EventType} expectedEventType The expected value of the
  60. * {@code target.eventType_} property.
  61. * @param {goog.math.Box} expectedMargin The expected value of the
  62. * {@code target.margin_} property.
  63. */
  64. function assertTarget(
  65. target, expectedElement, expectedTargetCorner, expectedMenuCorner,
  66. expectedEventType, expectedMargin) {
  67. var expectedTarget = {
  68. element_: expectedElement,
  69. targetCorner_: expectedTargetCorner,
  70. menuCorner_: expectedMenuCorner,
  71. eventType_: expectedEventType,
  72. margin_: expectedMargin
  73. };
  74. assertObjectEquals('Target does not match.', expectedTarget, target);
  75. }
  76. /**
  77. * Test menu receives BEFORE_SHOW event before it's displayed.
  78. */
  79. function testBeforeShowEvent() {
  80. popup.render();
  81. var target = popup.createAttachTarget(anchor);
  82. popup.attach(anchor);
  83. function beforeShowPopup(e) {
  84. // Ensure that the element is not yet visible.
  85. assertFalse(
  86. 'The element should not be shown when BEFORE_SHOW event is ' +
  87. 'being handled',
  88. goog.style.isElementShown(popup.getElement()));
  89. // Verify that current anchor is set before dispatching BEFORE_SHOW.
  90. assertNotNullNorUndefined(popup.getAttachedElement());
  91. assertEquals(
  92. 'The attached anchor element is incorrect', target.element_,
  93. popup.getAttachedElement());
  94. beforeShowPopupCalled = true;
  95. return showPopup;
  96. };
  97. function onShowPopup(e) {
  98. assertEquals(
  99. 'The attached anchor element is incorrect', target.element_,
  100. popup.getAttachedElement());
  101. };
  102. handler.listen(popup, goog.ui.Menu.EventType.BEFORE_SHOW, beforeShowPopup);
  103. handler.listen(popup, goog.ui.Menu.EventType.SHOW, onShowPopup);
  104. beforeShowPopupCalled = false;
  105. showPopup = false;
  106. popup.showMenu(target, 0, 0);
  107. assertTrue(
  108. 'BEFORE_SHOW event handler should be called on #showMenu',
  109. beforeShowPopupCalled);
  110. assertFalse(
  111. 'The element should not be shown when BEFORE_SHOW handler ' +
  112. 'returned false',
  113. goog.style.isElementShown(popup.getElement()));
  114. beforeShowPopupCalled = false;
  115. showPopup = true;
  116. popup.showMenu(target, 0, 0);
  117. assertTrue(
  118. 'The element should be shown when BEFORE_SHOW handler ' +
  119. 'returned true',
  120. goog.style.isElementShown(popup.getElement()));
  121. }
  122. /**
  123. * Test the behavior of {@link PopupMenu.isAttachTarget}.
  124. */
  125. function testIsAttachTarget() {
  126. popup.render();
  127. // Before 'attach' is called.
  128. assertFalse(
  129. 'Menu should not be attached to the element',
  130. popup.isAttachTarget(anchor));
  131. popup.attach(anchor);
  132. assertTrue(
  133. 'Menu should be attached to the anchor', popup.isAttachTarget(anchor));
  134. popup.detach(anchor);
  135. assertFalse(
  136. 'Menu is expected to be detached from the element',
  137. popup.isAttachTarget(anchor));
  138. }
  139. /**
  140. * Tests the behavior of {@link PopupMenu.createAttachTarget}.
  141. */
  142. function testCreateAttachTarget() {
  143. // Randomly picking parameters.
  144. var targetCorner = goog.positioning.Corner.TOP_END;
  145. var menuCorner = goog.positioning.Corner.BOTTOM_LEFT;
  146. var contextMenu = false; // Show menu on mouse down event.
  147. var margin = new goog.math.Box(0, 10, 5, 25);
  148. // Simply setting the required parameters.
  149. var target = popup.createAttachTarget(anchor);
  150. assertTrue(popup.isAttachTarget(anchor));
  151. assertTarget(
  152. target, anchor, undefined, undefined, goog.events.EventType.MOUSEDOWN,
  153. undefined);
  154. // Creating another target with all the parameters.
  155. target = popup.createAttachTarget(
  156. anchor, targetCorner, menuCorner, contextMenu, margin);
  157. assertTrue(popup.isAttachTarget(anchor));
  158. assertTarget(
  159. target, anchor, targetCorner, menuCorner, goog.events.EventType.MOUSEDOWN,
  160. margin);
  161. // Finally, switch up the 'contextMenu'
  162. target = popup.createAttachTarget(
  163. anchor, undefined, undefined, true /*opt_contextMenu*/, undefined);
  164. assertTarget(
  165. target, anchor, undefined, undefined, goog.events.EventType.CONTEXTMENU,
  166. undefined);
  167. }
  168. /**
  169. * Tests the behavior of {@link PopupMenu.getAttachTarget}.
  170. */
  171. function testGetAttachTarget() {
  172. popup.render();
  173. // Before the menu is attached to the anchor.
  174. var target = popup.getAttachTarget(anchor);
  175. assertTrue(
  176. 'Not expecting a target before the element is attach to the menu',
  177. target == null);
  178. // Randomly picking parameters.
  179. var targetCorner = goog.positioning.Corner.TOP_END;
  180. var menuCorner = goog.positioning.Corner.BOTTOM_LEFT;
  181. var contextMenu = false; // Show menu on mouse down event.
  182. var margin = new goog.math.Box(0, 10, 5, 25);
  183. popup.attach(anchor, targetCorner, menuCorner, contextMenu, margin);
  184. target = popup.getAttachTarget(anchor);
  185. assertTrue(
  186. 'Failed to get target after attaching element to menu', target != null);
  187. // Make sure we got the right target back.
  188. assertTarget(
  189. target, anchor, targetCorner, menuCorner, goog.events.EventType.MOUSEDOWN,
  190. margin);
  191. }
  192. function testSmallViewportSliding() {
  193. popup.render();
  194. popup.getElement().style.position = 'absolute';
  195. popup.getElement().style.outline = '1px solid blue';
  196. var item = new goog.ui.MenuItem('Test Item');
  197. popup.addChild(item, true);
  198. item.getElement().style.overflow = 'hidden';
  199. var viewport = goog.style.getClientViewportElement();
  200. var viewportRect = goog.style.getVisibleRectForElement(viewport);
  201. var middlePos = Math.floor((viewportRect.right - viewportRect.left) / 2);
  202. var leftwardPos = Math.floor((viewportRect.right - viewportRect.left) / 3);
  203. var rightwardPos =
  204. Math.floor((viewportRect.right - viewportRect.left) / 3 * 2);
  205. // Can interpret these positions as widths relative to the viewport as well.
  206. var smallWidth = leftwardPos;
  207. var mediumWidth = middlePos;
  208. var largeWidth = rightwardPos;
  209. // Test small menu first. This should be small enough that it will display
  210. // its upper left corner where we tell it to in all three positions.
  211. popup.getElement().style.width = smallWidth + 'px';
  212. var target = popup.createAttachTarget(anchor);
  213. popup.attach(anchor);
  214. popup.showMenu(target, leftwardPos, 0);
  215. assertObjectEquals(
  216. 'Popup in wrong position: small size, leftward pos',
  217. new goog.math.Coordinate(leftwardPos, 0),
  218. goog.style.getPosition(popup.getElement()));
  219. popup.showMenu(target, middlePos, 0);
  220. assertObjectEquals(
  221. 'Popup in wrong position: small size, middle pos',
  222. new goog.math.Coordinate(middlePos, 0),
  223. goog.style.getPosition(popup.getElement()));
  224. popup.showMenu(target, rightwardPos, 0);
  225. assertObjectEquals(
  226. 'Popup in wrong position: small size, rightward pos',
  227. new goog.math.Coordinate(rightwardPos, 0),
  228. goog.style.getPosition(popup.getElement()));
  229. // Test medium menu next. This should display with its upper left corner
  230. // at the target when leftward and middle, but on the right it should
  231. // position its upper right corner at the target instead.
  232. popup.getElement().style.width = mediumWidth + 'px';
  233. popup.showMenu(target, leftwardPos, 0);
  234. assertObjectEquals(
  235. 'Popup in wrong position: medium size, leftward pos',
  236. new goog.math.Coordinate(leftwardPos, 0),
  237. goog.style.getPosition(popup.getElement()));
  238. popup.showMenu(target, middlePos, 0);
  239. assertObjectEquals(
  240. 'Popup in wrong position: medium size, middle pos',
  241. new goog.math.Coordinate(middlePos, 0),
  242. goog.style.getPosition(popup.getElement()));
  243. popup.showMenu(target, rightwardPos, 0);
  244. assertObjectEquals(
  245. 'Popup in wrong position: medium size, rightward pos',
  246. new goog.math.Coordinate(rightwardPos - mediumWidth, 0),
  247. goog.style.getPosition(popup.getElement()));
  248. // Test large menu next. This should display with its upper left corner at
  249. // the target when leftward, and its upper right corner at the target when
  250. // rightward, but right in the middle neither corner can be at the target and
  251. // keep the entire menu onscreen, so it should place its upper right corner
  252. // at the very right edge of the viewport.
  253. popup.getElement().style.width = largeWidth + 'px';
  254. popup.showMenu(target, leftwardPos, 0);
  255. assertObjectEquals(
  256. 'Popup in wrong position: large size, leftward pos',
  257. new goog.math.Coordinate(leftwardPos, 0),
  258. goog.style.getPosition(popup.getElement()));
  259. popup.showMenu(target, middlePos, 0);
  260. assertObjectEquals(
  261. 'Popup in wrong position: large size, middle pos',
  262. new goog.math.Coordinate(
  263. viewportRect.right - viewportRect.left - largeWidth, 0),
  264. goog.style.getPosition(popup.getElement()));
  265. popup.showMenu(target, rightwardPos, 0);
  266. assertObjectEquals(
  267. 'Popup in wrong position: large size, rightward pos',
  268. new goog.math.Coordinate(rightwardPos - largeWidth, 0),
  269. goog.style.getPosition(popup.getElement()));
  270. // Make sure that the menu still displays correctly if we give the target
  271. // a target corner. We can't set the overflow policy in that case, but it
  272. // should still display.
  273. popup.detach(anchor);
  274. anchor.style.position = 'absolute';
  275. anchor.style.left = '24px';
  276. anchor.style.top = '24px';
  277. var targetCorner = goog.positioning.Corner.TOP_END;
  278. target = popup.createAttachTarget(anchor, targetCorner);
  279. popup.attach(anchor, targetCorner);
  280. popup.getElement().style.width = smallWidth + 'px';
  281. popup.showMenu(target, leftwardPos, 0);
  282. assertObjectEquals(
  283. 'Popup in wrong position: small size, leftward pos, with target corner',
  284. new goog.math.Coordinate(24, 24),
  285. goog.style.getPosition(popup.getElement()));
  286. }
  287. /**
  288. * Tests that the menu is shown if the SPACE or ENTER keys are pressed, and that
  289. * none of the menu items are highlighted (PopupMenu.highlightedIndex == -1).
  290. */
  291. function testKeyboardEventsShowMenu() {
  292. popup.decorate(menu);
  293. popup.attach(anchor);
  294. popup.hide();
  295. assertFalse(popup.isVisible());
  296. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.SPACE);
  297. assertTrue(popup.isVisible());
  298. assertEquals(-1, popup.getHighlightedIndex());
  299. popup.hide();
  300. assertFalse(popup.isVisible());
  301. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.ENTER);
  302. assertTrue(popup.isVisible());
  303. assertEquals(-1, popup.getHighlightedIndex());
  304. }
  305. /**
  306. * Tests that the menu is shown and the first item is highlighted if the DOWN
  307. * key is pressed.
  308. */
  309. function testDownKey() {
  310. popup.decorate(menu);
  311. popup.attach(anchor);
  312. popup.hide();
  313. assertFalse(popup.isVisible());
  314. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.DOWN);
  315. assertTrue(popup.isVisible());
  316. assertEquals(0, popup.getHighlightedIndex());
  317. }
  318. /**
  319. * Tests activation of menu items by keyboard.
  320. */
  321. function testMenuItemKeyboardActivation() {
  322. popup.decorate(menu);
  323. popup.attach(anchor);
  324. // Check that if the ESC key is pressed the focus is on
  325. // the anchor element.
  326. goog.testing.events.fireKeySequence(menu, goog.events.KeyCodes.ESC);
  327. assertEquals(anchor, document.activeElement);
  328. var menuitemListenerFired = false;
  329. function onMenuitemAction(event) {
  330. if (event.keyCode == goog.events.KeyCodes.SPACE ||
  331. event.keyCode == goog.events.KeyCodes.ENTER) {
  332. menuitemListenerFired = true;
  333. }
  334. };
  335. handler.listen(menuitem1, goog.events.EventType.KEYDOWN, onMenuitemAction);
  336. // Simulate opening a menu using the DOWN key, and pressing the SPACE/ENTER
  337. // key in order to activate the first menuitem.
  338. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.DOWN);
  339. goog.testing.events.fireKeySequence(menu, goog.events.KeyCodes.SPACE);
  340. assertTrue(menuitemListenerFired);
  341. menuitemListenerFired = false;
  342. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.DOWN);
  343. goog.testing.events.fireKeySequence(menu, goog.events.KeyCodes.ENTER);
  344. assertTrue(menuitemListenerFired);
  345. // Make sure the menu item's listener doesn't fire for any key.
  346. menuitemListenerFired = false;
  347. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.DOWN);
  348. goog.testing.events.fireKeySequence(menu, goog.events.KeyCodes.SHIFT);
  349. assertFalse(menuitemListenerFired);
  350. // Simulate opening menu and moving down to the third menu item using the
  351. // DOWN key, and then activating it using the SPACE key.
  352. menuitemListenerFired = false;
  353. handler.listen(menuitem3, goog.events.EventType.KEYDOWN, onMenuitemAction);
  354. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.DOWN);
  355. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.DOWN);
  356. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.DOWN);
  357. goog.testing.events.fireKeySequence(menu, goog.events.KeyCodes.SPACE);
  358. assertTrue(menuitemListenerFired);
  359. }
  360. /**
  361. * Tests that a context menu isn't shown if the SPACE or ENTER keys are pressed.
  362. */
  363. function testContextMenuKeyboard() {
  364. popup.attach(anchor, null, null, true);
  365. popup.hide();
  366. assertFalse(popup.isVisible());
  367. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.SPACE);
  368. assertFalse(popup.isVisible());
  369. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.ENTER);
  370. assertFalse(popup.isVisible());
  371. }
  372. /**
  373. * Tests that there is no crash when hitting a key when no menu item is
  374. * highlighted.
  375. */
  376. function testKeyPressWithNoHighlightedItem() {
  377. popup.decorate(menu);
  378. popup.attach(anchor);
  379. goog.testing.events.fireKeySequence(anchor, goog.events.KeyCodes.SPACE);
  380. assertTrue(popup.isVisible());
  381. try {
  382. goog.testing.events.fireKeySequence(menu, goog.events.KeyCodes.SPACE);
  383. } catch (e) {
  384. fail(
  385. 'Crash attempting to reference null selected menu item after ' +
  386. 'keyboard event.');
  387. }
  388. }