tooltip_test.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  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.TooltipTest');
  15. goog.setTestOnly('goog.ui.TooltipTest');
  16. goog.require('goog.dom');
  17. goog.require('goog.dom.TagName');
  18. goog.require('goog.events.Event');
  19. goog.require('goog.events.EventHandler');
  20. goog.require('goog.events.EventType');
  21. goog.require('goog.events.FocusHandler');
  22. goog.require('goog.html.testing');
  23. goog.require('goog.math.Coordinate');
  24. goog.require('goog.positioning.AbsolutePosition');
  25. goog.require('goog.style');
  26. goog.require('goog.testing.MockClock');
  27. goog.require('goog.testing.TestQueue');
  28. goog.require('goog.testing.events');
  29. goog.require('goog.testing.jsunit');
  30. goog.require('goog.ui.PopupBase');
  31. goog.require('goog.ui.Tooltip');
  32. goog.require('goog.userAgent');
  33. /**
  34. * A subclass of Tooltip that overrides {@code getPositioningStrategy}
  35. * for testing purposes.
  36. * @constructor
  37. */
  38. function TestTooltip(el, text, dom) {
  39. goog.ui.Tooltip.call(this, el, text, dom);
  40. }
  41. goog.inherits(TestTooltip, goog.ui.Tooltip);
  42. /** @override */
  43. TestTooltip.prototype.getPositioningStrategy = function() {
  44. return new goog.positioning.AbsolutePosition(13, 17);
  45. };
  46. var tt, clock, handler, eventQueue, dom;
  47. // Allow positions to be off by one in gecko as it reports scrolling
  48. // offsets in steps of 2.
  49. var ALLOWED_OFFSET = goog.userAgent.GECKO ? 1 : 0;
  50. function setUp() {
  51. // We get access denied error when accessing the iframe in IE on the farm
  52. // as IE doesn't have the same window size issues as firefox on the farm
  53. // we bypass the iframe and use the current document instead.
  54. if (goog.userAgent.EDGE_OR_IE) {
  55. dom = goog.dom.getDomHelper(document);
  56. } else {
  57. var frame = document.getElementById('testframe');
  58. var doc = goog.dom.getFrameContentDocument(frame);
  59. dom = goog.dom.getDomHelper(doc);
  60. }
  61. // Host elements in fixed size iframe to avoid window size problems when
  62. // running under Selenium.
  63. dom.getDocument().body.innerHTML = '<p id="notpopup">Content</p>' +
  64. '<p id="hovertarget">Hover Here For Popup</p>' +
  65. '<p id="second">Secondary target</p>';
  66. tt = new goog.ui.Tooltip(undefined, undefined, dom);
  67. tt.setElement(
  68. dom.createDom(
  69. goog.dom.TagName.DIV, {id: 'popup', style: 'visibility:hidden'},
  70. 'Hello'));
  71. clock = new goog.testing.MockClock(true);
  72. eventQueue = new goog.testing.TestQueue();
  73. handler = new goog.events.EventHandler(eventQueue);
  74. handler.listen(tt, goog.ui.PopupBase.EventType.SHOW, eventQueue.enqueue);
  75. handler.listen(tt, goog.ui.PopupBase.EventType.HIDE, eventQueue.enqueue);
  76. }
  77. function tearDown() {
  78. // tooltip needs to be hidden as well as disposed of so that it doesn't
  79. // leave global state hanging around to trip up other tests.
  80. tt.onHide();
  81. tt.dispose();
  82. clock.uninstall();
  83. handler.removeAll();
  84. }
  85. function testConstructor() {
  86. var element = tt.getElement();
  87. assertNotNull('Tooltip should have non-null element', element);
  88. assertEquals(
  89. 'Tooltip element should be the DIV we created', dom.getElement('popup'),
  90. element);
  91. assertEquals(
  92. 'Tooltip element should be a child of the document body',
  93. dom.getDocument().body, element.parentNode);
  94. }
  95. function testTooltipShowsAndHides() {
  96. var hoverTarget = dom.getElement('hovertarget');
  97. var elsewhere = dom.getElement('notpopup');
  98. var element = tt.getElement();
  99. var position = new goog.math.Coordinate(5, 5);
  100. assertNotNull('Tooltip should have non-null element', element);
  101. assertEquals(
  102. 'Initial state should be inactive', goog.ui.Tooltip.State.INACTIVE,
  103. tt.getState());
  104. tt.attach(hoverTarget);
  105. tt.setShowDelayMs(100);
  106. tt.setHideDelayMs(50);
  107. goog.testing.events.fireMouseOverEvent(hoverTarget, elsewhere, position);
  108. assertEquals(goog.ui.Tooltip.State.WAITING_TO_SHOW, tt.getState());
  109. clock.tick(101);
  110. assertEquals('visible', tt.getElement().style.visibility);
  111. assertEquals(
  112. 'tooltip y position (10px margin below the cursor)', '15px',
  113. tt.getElement().style.top);
  114. assertEquals(goog.ui.Tooltip.State.SHOWING, tt.getState());
  115. assertEquals(goog.ui.PopupBase.EventType.SHOW, eventQueue.dequeue().type);
  116. assertTrue(eventQueue.isEmpty());
  117. goog.testing.events.fireMouseOutEvent(hoverTarget, elsewhere);
  118. assertEquals(goog.ui.Tooltip.State.WAITING_TO_HIDE, tt.getState());
  119. clock.tick(51);
  120. assertEquals('hidden', tt.getElement().style.visibility);
  121. assertEquals(goog.ui.Tooltip.State.INACTIVE, tt.getState());
  122. assertEquals(goog.ui.PopupBase.EventType.HIDE, eventQueue.dequeue().type);
  123. assertTrue(eventQueue.isEmpty());
  124. }
  125. function testMultipleTargets() {
  126. var firstTarget = dom.getElement('hovertarget');
  127. var secondTarget = dom.getElement('second');
  128. var elsewhere = dom.getElement('notpopup');
  129. var element = tt.getElement();
  130. tt.attach(firstTarget);
  131. tt.attach(secondTarget);
  132. tt.setShowDelayMs(100);
  133. tt.setHideDelayMs(50);
  134. // Move over first target
  135. goog.testing.events.fireMouseOverEvent(firstTarget, elsewhere);
  136. clock.tick(101);
  137. assertEquals(goog.ui.PopupBase.EventType.SHOW, eventQueue.dequeue().type);
  138. assertTrue(eventQueue.isEmpty());
  139. // Move from first to second
  140. goog.testing.events.fireMouseOutEvent(firstTarget, secondTarget);
  141. goog.testing.events.fireMouseOverEvent(secondTarget, firstTarget);
  142. assertEquals(goog.ui.Tooltip.State.UPDATING, tt.getState());
  143. assertTrue(eventQueue.isEmpty());
  144. // Move from second to element (before second shows)
  145. goog.testing.events.fireMouseOutEvent(secondTarget, element);
  146. goog.testing.events.fireMouseOverEvent(element, secondTarget);
  147. assertEquals(goog.ui.Tooltip.State.SHOWING, tt.getState());
  148. assertTrue(eventQueue.isEmpty());
  149. // Move from element to second, and let it show
  150. goog.testing.events.fireMouseOutEvent(element, secondTarget);
  151. goog.testing.events.fireMouseOverEvent(secondTarget, element);
  152. assertEquals(goog.ui.Tooltip.State.UPDATING, tt.getState());
  153. clock.tick(101);
  154. assertEquals(goog.ui.Tooltip.State.SHOWING, tt.getState());
  155. assertEquals('Anchor should be second target', secondTarget, tt.anchor);
  156. assertEquals(goog.ui.PopupBase.EventType.HIDE, eventQueue.dequeue().type);
  157. assertEquals(goog.ui.PopupBase.EventType.SHOW, eventQueue.dequeue().type);
  158. assertTrue(eventQueue.isEmpty());
  159. // Move from second to first and then off without first showing
  160. goog.testing.events.fireMouseOutEvent(secondTarget, firstTarget);
  161. goog.testing.events.fireMouseOverEvent(firstTarget, secondTarget);
  162. assertEquals(goog.ui.Tooltip.State.UPDATING, tt.getState());
  163. goog.testing.events.fireMouseOutEvent(firstTarget, elsewhere);
  164. assertEquals(goog.ui.Tooltip.State.WAITING_TO_HIDE, tt.getState());
  165. clock.tick(51);
  166. assertEquals('hidden', tt.getElement().style.visibility);
  167. assertEquals(goog.ui.Tooltip.State.INACTIVE, tt.getState());
  168. assertEquals(goog.ui.PopupBase.EventType.HIDE, eventQueue.dequeue().type);
  169. assertTrue(eventQueue.isEmpty());
  170. clock.tick(200);
  171. // Move from element to second, but detach second before it shows.
  172. goog.testing.events.fireMouseOutEvent(element, secondTarget);
  173. goog.testing.events.fireMouseOverEvent(secondTarget, element);
  174. assertEquals(goog.ui.Tooltip.State.WAITING_TO_SHOW, tt.getState());
  175. tt.detach(secondTarget);
  176. clock.tick(200);
  177. assertEquals(goog.ui.Tooltip.State.INACTIVE, tt.getState());
  178. assertEquals('Anchor should be second target', secondTarget, tt.anchor);
  179. assertTrue(eventQueue.isEmpty());
  180. }
  181. function testRequireInteraction() {
  182. var hoverTarget = dom.getElement('hovertarget');
  183. var elsewhere = dom.getElement('notpopup');
  184. tt.attach(hoverTarget);
  185. tt.setShowDelayMs(100);
  186. tt.setHideDelayMs(50);
  187. tt.setRequireInteraction(true);
  188. goog.testing.events.fireMouseOverEvent(hoverTarget, elsewhere);
  189. clock.tick(101);
  190. assertEquals(
  191. 'Tooltip should not show without mouse move event', 'hidden',
  192. tt.getElement().style.visibility);
  193. goog.testing.events.fireMouseMoveEvent(hoverTarget);
  194. goog.testing.events.fireMouseOverEvent(hoverTarget, elsewhere);
  195. clock.tick(101);
  196. assertEquals(
  197. 'Tooltip should show because we had mouse move event', 'visible',
  198. tt.getElement().style.visibility);
  199. goog.testing.events.fireMouseOutEvent(hoverTarget, elsewhere);
  200. clock.tick(51);
  201. assertEquals('hidden', tt.getElement().style.visibility);
  202. goog.testing.events.fireBrowserEvent(
  203. new goog.events.Event(goog.events.EventType.FOCUS, hoverTarget));
  204. clock.tick(101);
  205. assertEquals(
  206. 'Tooltip should show because we had focus event', 'visible',
  207. tt.getElement().style.visibility);
  208. goog.testing.events.fireBrowserEvent(
  209. new goog.events.Event(goog.events.EventType.BLUR, hoverTarget));
  210. clock.tick(51);
  211. assertEquals('hidden', tt.getElement().style.visibility);
  212. goog.testing.events.fireMouseMoveEvent(hoverTarget);
  213. goog.testing.events.fireMouseOverEvent(hoverTarget, elsewhere);
  214. goog.testing.events.fireMouseOutEvent(hoverTarget, elsewhere);
  215. goog.testing.events.fireMouseOverEvent(hoverTarget, elsewhere);
  216. clock.tick(101);
  217. assertEquals(
  218. 'A cancelled trigger should also cancel the seen interaction', 'hidden',
  219. tt.getElement().style.visibility);
  220. }
  221. function testDispose() {
  222. var element = tt.getElement();
  223. tt.dispose();
  224. assertTrue('Tooltip should have been disposed of', tt.isDisposed());
  225. assertNull(
  226. 'Tooltip element reference should have been nulled out', tt.getElement());
  227. assertNotEquals(
  228. 'Tooltip element should not be a child of the body', document.body,
  229. element.parentNode);
  230. }
  231. function testNested() {
  232. var ttNested;
  233. tt.getElement().appendChild(
  234. dom.createDom(goog.dom.TagName.SPAN, {id: 'nested'}, 'Goodbye'));
  235. ttNested = new goog.ui.Tooltip(undefined, undefined, dom);
  236. ttNested.setElement(
  237. dom.createDom(goog.dom.TagName.DIV, {id: 'nestedPopup'}, 'hi'));
  238. tt.setShowDelayMs(100);
  239. tt.setHideDelayMs(50);
  240. ttNested.setShowDelayMs(75);
  241. ttNested.setHideDelayMs(25);
  242. var nestedAnchor = dom.getElement('nested');
  243. var hoverTarget = dom.getElement('hovertarget');
  244. var outerTooltip = dom.getElement('popup');
  245. var innerTooltip = dom.getElement('nestedPopup');
  246. var elsewhere = dom.getElement('notpopup');
  247. ttNested.attach(nestedAnchor);
  248. tt.attach(hoverTarget);
  249. // Test mouse into, out of nested tooltip
  250. goog.testing.events.fireMouseOverEvent(hoverTarget, elsewhere);
  251. clock.tick(101);
  252. goog.testing.events.fireMouseOutEvent(hoverTarget, outerTooltip);
  253. goog.testing.events.fireMouseOverEvent(outerTooltip, hoverTarget);
  254. clock.tick(51);
  255. assertEquals('visible', tt.getElement().style.visibility);
  256. goog.testing.events.fireMouseOutEvent(outerTooltip, nestedAnchor);
  257. goog.testing.events.fireMouseOverEvent(nestedAnchor, outerTooltip);
  258. clock.tick(76);
  259. assertEquals('visible', tt.getElement().style.visibility);
  260. assertEquals('visible', ttNested.getElement().style.visibility);
  261. goog.testing.events.fireMouseOutEvent(nestedAnchor, outerTooltip);
  262. goog.testing.events.fireMouseOverEvent(outerTooltip, nestedAnchor);
  263. clock.tick(100);
  264. assertEquals('visible', tt.getElement().style.visibility);
  265. assertEquals('hidden', ttNested.getElement().style.visibility);
  266. // Go back in nested tooltip and then out through tooltip element.
  267. goog.testing.events.fireMouseOutEvent(outerTooltip, nestedAnchor);
  268. goog.testing.events.fireMouseOverEvent(nestedAnchor, outerTooltip);
  269. clock.tick(76);
  270. goog.testing.events.fireMouseOutEvent(nestedAnchor, innerTooltip);
  271. goog.testing.events.fireMouseOverEvent(innerTooltip, nestedAnchor);
  272. clock.tick(15);
  273. assertEquals('visible', tt.getElement().style.visibility);
  274. assertEquals('visible', ttNested.getElement().style.visibility);
  275. goog.testing.events.fireMouseOutEvent(innerTooltip, elsewhere);
  276. clock.tick(26);
  277. assertEquals('hidden', ttNested.getElement().style.visibility);
  278. clock.tick(51);
  279. assertEquals('hidden', tt.getElement().style.visibility);
  280. // Test with focus
  281. goog.testing.events.fireBrowserEvent(
  282. new goog.events.Event(goog.events.EventType.FOCUS, hoverTarget));
  283. clock.tick(101);
  284. goog.testing.events.fireBrowserEvent(
  285. new goog.events.Event(goog.events.EventType.BLUR, hoverTarget));
  286. goog.testing.events.fireBrowserEvent(
  287. new goog.events.Event(goog.events.EventType.FOCUS, nestedAnchor));
  288. clock.tick(76);
  289. assertEquals('visible', tt.getElement().style.visibility);
  290. assertEquals('visible', ttNested.getElement().style.visibility);
  291. goog.testing.events.fireBrowserEvent(
  292. new goog.events.Event(goog.events.EventType.BLUR, nestedAnchor));
  293. goog.testing.events.fireBrowserEvent(
  294. new goog.events.Event(goog.events.EventType.FOCUS, hoverTarget));
  295. clock.tick(26);
  296. assertEquals('visible', tt.getElement().style.visibility);
  297. assertEquals('hidden', ttNested.getElement().style.visibility);
  298. ttNested.onHide();
  299. ttNested.dispose();
  300. }
  301. function testPosition() {
  302. dom.getDocument().body.style.paddingBottom = '150%'; // force scrollbar
  303. var scrollEl = dom.getDocumentScrollElement();
  304. var anchor = dom.getElement('hovertarget');
  305. var tooltip = new goog.ui.Tooltip(anchor, 'foo');
  306. tooltip.getElement().style.position = 'absolute';
  307. tooltip.cursorPosition.x = 100;
  308. tooltip.cursorPosition.y = 100;
  309. tooltip.showForElement(anchor);
  310. assertEquals(
  311. 'Tooltip should be at cursor position',
  312. '(110, 110)', // (100, 100) + padding (10, 10)
  313. goog.style.getPageOffset(tooltip.getElement()).toString());
  314. scrollEl.scrollTop = 50;
  315. var offset = goog.style.getPageOffset(tooltip.getElement());
  316. assertTrue(
  317. 'Tooltip should be at cursor position when scrolled',
  318. Math.abs(offset.x - 110) <= ALLOWED_OFFSET); // 100 + padding 10
  319. assertTrue(
  320. 'Tooltip should be at cursor position when scrolled',
  321. Math.abs(offset.y - 110) <= ALLOWED_OFFSET); // 100 + padding 10
  322. tooltip.dispose();
  323. dom.getDocument().body.style.paddingTop = '';
  324. scrollEl.scrollTop = 0;
  325. }
  326. function testPositionOverride() {
  327. var anchor = dom.getElement('hovertarget');
  328. var tooltip = new TestTooltip(anchor, 'foo', dom);
  329. tooltip.showForElement(anchor);
  330. assertEquals(
  331. 'Tooltip should be at absolute position', '(13, 17)',
  332. goog.style.getPageOffset(tooltip.getElement()).toString());
  333. tooltip.dispose();
  334. }
  335. function testHtmlContent() {
  336. tt.setSafeHtml(
  337. goog.html.testing.newSafeHtmlForTest(
  338. '<span class="theSpan">Hello</span>'));
  339. var spanEl = goog.dom.getElementByClass('theSpan', tt.getElement());
  340. assertEquals('Hello', goog.dom.getTextContent(spanEl));
  341. }
  342. function testSetElementNull() {
  343. tt.setElement(null);
  344. }
  345. function testFocusBlurElementsInTooltip() {
  346. var anchorEl = dom.getElement('hovertarget');
  347. goog.dom.setFocusableTabIndex(anchorEl, true);
  348. tt.attach(anchorEl);
  349. goog.testing.events.fireFocusEvent(anchorEl);
  350. clock.tick(1000);
  351. assertEquals('visible', tt.getElement().style.visibility);
  352. goog.testing.events.fireBlurEvent(anchorEl);
  353. tt.tooltipFocusHandler_.dispatchEvent(
  354. goog.events.FocusHandler.EventType.FOCUSIN);
  355. clock.tick(1000);
  356. assertEquals('visible', tt.getElement().style.visibility);
  357. // Run blur on the previous element followed by focus on the element being
  358. // focused, as would normally happen when focus() is called on an element.
  359. tt.tooltipFocusHandler_.dispatchEvent(
  360. goog.events.FocusHandler.EventType.FOCUSOUT);
  361. tt.tooltipFocusHandler_.dispatchEvent(
  362. goog.events.FocusHandler.EventType.FOCUSIN);
  363. clock.tick(1000);
  364. assertEquals('visible', tt.getElement().style.visibility);
  365. tt.tooltipFocusHandler_.dispatchEvent(
  366. goog.events.FocusHandler.EventType.FOCUSOUT);
  367. clock.tick(1000);
  368. assertEquals('hidden', tt.getElement().style.visibility);
  369. }
  370. function testFocusElementInTooltipThenBackToAnchor() {
  371. var anchorEl = dom.getElement('hovertarget');
  372. goog.dom.setFocusableTabIndex(anchorEl, true);
  373. tt.attach(anchorEl);
  374. goog.testing.events.fireFocusEvent(anchorEl);
  375. clock.tick(1000);
  376. assertEquals('visible', tt.getElement().style.visibility);
  377. // Run blur on the previous element followed by focus on the element being
  378. // focused, as would normally happen when focus() is called on an element.
  379. goog.testing.events.fireBlurEvent(anchorEl);
  380. tt.tooltipFocusHandler_.dispatchEvent(
  381. goog.events.FocusHandler.EventType.FOCUSIN);
  382. clock.tick(1000);
  383. assertEquals('visible', tt.getElement().style.visibility);
  384. // Run blur on the previous element followed by focus on the element being
  385. // focused, as would normally happen when focus() is called on an element.
  386. tt.tooltipFocusHandler_.dispatchEvent(
  387. goog.events.FocusHandler.EventType.FOCUSOUT);
  388. goog.testing.events.fireFocusEvent(anchorEl);
  389. clock.tick(1000);
  390. assertEquals('visible', tt.getElement().style.visibility);
  391. goog.testing.events.fireBlurEvent(anchorEl);
  392. clock.tick(1000);
  393. assertEquals('hidden', tt.getElement().style.visibility);
  394. }