linkbubble_test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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.editor.plugins.LinkBubbleTest');
  15. goog.setTestOnly('goog.editor.plugins.LinkBubbleTest');
  16. goog.require('goog.dom');
  17. goog.require('goog.dom.Range');
  18. goog.require('goog.dom.TagName');
  19. goog.require('goog.editor.Command');
  20. goog.require('goog.editor.Link');
  21. goog.require('goog.editor.plugins.LinkBubble');
  22. goog.require('goog.events.BrowserEvent');
  23. goog.require('goog.events.Event');
  24. goog.require('goog.events.EventType');
  25. goog.require('goog.events.KeyCodes');
  26. goog.require('goog.string');
  27. goog.require('goog.style');
  28. goog.require('goog.testing.FunctionMock');
  29. goog.require('goog.testing.PropertyReplacer');
  30. goog.require('goog.testing.editor.FieldMock');
  31. goog.require('goog.testing.editor.TestHelper');
  32. goog.require('goog.testing.events');
  33. goog.require('goog.testing.jsunit');
  34. goog.require('goog.userAgent');
  35. var fieldDiv;
  36. var FIELDMOCK;
  37. var linkBubble;
  38. var link;
  39. var linkChild;
  40. var mockWindowOpen;
  41. var stubs;
  42. var testHelper;
  43. function setUpPage() {
  44. fieldDiv = goog.dom.$('field');
  45. stubs = new goog.testing.PropertyReplacer();
  46. testHelper = new goog.testing.editor.TestHelper(goog.dom.getElement('field'));
  47. }
  48. function setUp() {
  49. testHelper.setUpEditableElement();
  50. FIELDMOCK = new goog.testing.editor.FieldMock();
  51. linkBubble = new goog.editor.plugins.LinkBubble();
  52. linkBubble.fieldObject = FIELDMOCK;
  53. link = fieldDiv.firstChild;
  54. linkChild = link.lastChild;
  55. mockWindowOpen = new goog.testing.FunctionMock('open');
  56. stubs.set(window, 'open', mockWindowOpen);
  57. }
  58. function tearDown() {
  59. linkBubble.closeBubble();
  60. testHelper.tearDownEditableElement();
  61. stubs.reset();
  62. }
  63. function testLinkSelected() {
  64. FIELDMOCK.$replay();
  65. linkBubble.enable(FIELDMOCK);
  66. goog.dom.Range.createFromNodeContents(link).select();
  67. linkBubble.handleSelectionChange();
  68. assertBubble();
  69. FIELDMOCK.$verify();
  70. }
  71. function testLinkClicked() {
  72. FIELDMOCK.$replay();
  73. linkBubble.enable(FIELDMOCK);
  74. linkBubble.handleSelectionChange(createMouseEvent(link));
  75. assertBubble();
  76. FIELDMOCK.$verify();
  77. }
  78. function testImageLink() {
  79. FIELDMOCK.$replay();
  80. linkBubble.enable(FIELDMOCK);
  81. link.setAttribute('imageanchor', 1);
  82. linkBubble.handleSelectionChange(createMouseEvent(link));
  83. assertBubble();
  84. FIELDMOCK.$verify();
  85. }
  86. function closeBox() {
  87. var closeBox = goog.dom.getElementsByTagNameAndClass(
  88. goog.dom.TagName.DIV, 'tr_bubble_closebox');
  89. assertEquals('Should find only one close box', 1, closeBox.length);
  90. assertNotNull('Found close box', closeBox[0]);
  91. goog.testing.events.fireClickSequence(closeBox[0]);
  92. }
  93. function testCloseBox() {
  94. testLinkClicked();
  95. closeBox();
  96. assertNoBubble();
  97. FIELDMOCK.$verify();
  98. }
  99. function testChangeClicked() {
  100. FIELDMOCK.execCommand(
  101. goog.editor.Command.MODAL_LINK_EDITOR, new goog.editor.Link(link, false));
  102. FIELDMOCK.$registerArgumentListVerifier('execCommand', function(arr1, arr2) {
  103. return arr1.length == arr2.length && arr1.length == 2 &&
  104. arr1[0] == goog.editor.Command.MODAL_LINK_EDITOR &&
  105. arr2[0] == goog.editor.Command.MODAL_LINK_EDITOR &&
  106. arr1[1] instanceof goog.editor.Link &&
  107. arr2[1] instanceof goog.editor.Link;
  108. });
  109. FIELDMOCK.$times(1);
  110. FIELDMOCK.$returns(true);
  111. FIELDMOCK.$replay();
  112. linkBubble.enable(FIELDMOCK);
  113. linkBubble.handleSelectionChange(createMouseEvent(link));
  114. assertBubble();
  115. goog.testing.events.fireClickSequence(
  116. goog.dom.$(goog.editor.plugins.LinkBubble.CHANGE_LINK_ID_));
  117. assertNoBubble();
  118. FIELDMOCK.$verify();
  119. }
  120. function testChangePressed() {
  121. FIELDMOCK.execCommand(
  122. goog.editor.Command.MODAL_LINK_EDITOR, new goog.editor.Link(link, false));
  123. FIELDMOCK.$registerArgumentListVerifier('execCommand', function(arr1, arr2) {
  124. return arr1.length == arr2.length && arr1.length == 2 &&
  125. arr1[0] == goog.editor.Command.MODAL_LINK_EDITOR &&
  126. arr2[0] == goog.editor.Command.MODAL_LINK_EDITOR &&
  127. arr1[1] instanceof goog.editor.Link &&
  128. arr2[1] instanceof goog.editor.Link;
  129. });
  130. FIELDMOCK.$times(1);
  131. FIELDMOCK.$returns(true);
  132. FIELDMOCK.$replay();
  133. linkBubble.enable(FIELDMOCK);
  134. linkBubble.handleSelectionChange(createMouseEvent(link));
  135. assertBubble();
  136. var defaultPrevented = !goog.testing.events.fireKeySequence(
  137. goog.dom.$(goog.editor.plugins.LinkBubble.CHANGE_LINK_ID_),
  138. goog.events.KeyCodes.ENTER);
  139. assertTrue(defaultPrevented);
  140. assertNoBubble();
  141. FIELDMOCK.$verify();
  142. }
  143. function testDeleteClicked() {
  144. FIELDMOCK.dispatchBeforeChange();
  145. FIELDMOCK.$times(1);
  146. FIELDMOCK.dispatchChange();
  147. FIELDMOCK.$times(1);
  148. FIELDMOCK.focus();
  149. FIELDMOCK.$times(1);
  150. FIELDMOCK.$replay();
  151. linkBubble.enable(FIELDMOCK);
  152. linkBubble.handleSelectionChange(createMouseEvent(link));
  153. assertBubble();
  154. goog.testing.events.fireClickSequence(
  155. goog.dom.$(goog.editor.plugins.LinkBubble.DELETE_LINK_ID_));
  156. var element = goog.userAgent.GECKO ? document.body : fieldDiv;
  157. assertNotEquals(
  158. 'Link removed', element.firstChild.nodeName, String(goog.dom.TagName.A));
  159. assertNoBubble();
  160. var range = goog.dom.Range.createFromWindow();
  161. assertEquals('Link selection on link text', linkChild, range.getEndNode());
  162. assertEquals(
  163. 'Link selection on link text end',
  164. goog.dom.getRawTextContent(linkChild).length, range.getEndOffset());
  165. FIELDMOCK.$verify();
  166. }
  167. function testDeletePressed() {
  168. FIELDMOCK.dispatchBeforeChange();
  169. FIELDMOCK.$times(1);
  170. FIELDMOCK.dispatchChange();
  171. FIELDMOCK.$times(1);
  172. FIELDMOCK.focus();
  173. FIELDMOCK.$times(1);
  174. FIELDMOCK.$replay();
  175. linkBubble.enable(FIELDMOCK);
  176. linkBubble.handleSelectionChange(createMouseEvent(link));
  177. assertBubble();
  178. var defaultPrevented = !goog.testing.events.fireKeySequence(
  179. goog.dom.$(goog.editor.plugins.LinkBubble.DELETE_LINK_ID_),
  180. goog.events.KeyCodes.ENTER);
  181. assertTrue(defaultPrevented);
  182. var element = goog.userAgent.GECKO ? document.body : fieldDiv;
  183. assertNotEquals(
  184. 'Link removed', element.firstChild.nodeName, String(goog.dom.TagName.A));
  185. assertNoBubble();
  186. var range = goog.dom.Range.createFromWindow();
  187. assertEquals('Link selection on link text', linkChild, range.getEndNode());
  188. assertEquals(
  189. 'Link selection on link text end',
  190. goog.dom.getRawTextContent(linkChild).length, range.getEndOffset());
  191. FIELDMOCK.$verify();
  192. }
  193. function testActionClicked() {
  194. var SPAN = 'actionSpanId';
  195. var LINK = 'actionLinkId';
  196. var toShowCount = 0;
  197. var actionCount = 0;
  198. var linkAction = new goog.editor.plugins.LinkBubble.Action(
  199. SPAN, LINK, 'message',
  200. function() {
  201. toShowCount++;
  202. return toShowCount == 1; // Show it the first time.
  203. },
  204. function() { actionCount++; });
  205. linkBubble = new goog.editor.plugins.LinkBubble(linkAction);
  206. linkBubble.fieldObject = FIELDMOCK;
  207. FIELDMOCK.$replay();
  208. linkBubble.enable(FIELDMOCK);
  209. // The first time the bubble is shown, show our custom action.
  210. linkBubble.handleSelectionChange(createMouseEvent(link));
  211. assertBubble();
  212. assertEquals('Should check showing the action', 1, toShowCount);
  213. assertEquals('Action should not have fired yet', 0, actionCount);
  214. assertTrue(
  215. 'Action should be visible 1st time',
  216. goog.style.isElementShown(goog.dom.$(SPAN)));
  217. goog.testing.events.fireClickSequence(goog.dom.$(LINK));
  218. assertEquals('Should not check showing again yet', 1, toShowCount);
  219. assertEquals('Action should be fired', 1, actionCount);
  220. closeBox();
  221. assertNoBubble();
  222. // The action won't be shown the second time around.
  223. linkBubble.handleSelectionChange(createMouseEvent(link));
  224. assertBubble();
  225. assertEquals('Should check showing again', 2, toShowCount);
  226. assertEquals('Action should not fire again', 1, actionCount);
  227. assertFalse(
  228. 'Action should not be shown 2nd time',
  229. goog.style.isElementShown(goog.dom.$(SPAN)));
  230. FIELDMOCK.$verify();
  231. }
  232. function testLinkTextClicked() {
  233. mockWindowOpen('http://www.google.com/', '_blank', '');
  234. mockWindowOpen.$replay();
  235. FIELDMOCK.$replay();
  236. linkBubble.enable(FIELDMOCK);
  237. linkBubble.handleSelectionChange(createMouseEvent(link));
  238. assertBubble();
  239. goog.testing.events.fireClickSequence(
  240. goog.dom.$(goog.editor.plugins.LinkBubble.TEST_LINK_ID_));
  241. assertBubble();
  242. mockWindowOpen.$verify();
  243. FIELDMOCK.$verify();
  244. }
  245. function testLinkTextClickedCustomUrlFn() {
  246. mockWindowOpen('http://images.google.com/', '_blank', '');
  247. mockWindowOpen.$replay();
  248. FIELDMOCK.$replay();
  249. linkBubble.enable(FIELDMOCK);
  250. linkBubble.setTestLinkUrlFn(function(url) {
  251. return url.replace('www', 'images');
  252. });
  253. linkBubble.handleSelectionChange(createMouseEvent(link));
  254. assertBubble();
  255. goog.testing.events.fireClickSequence(
  256. goog.dom.$(goog.editor.plugins.LinkBubble.TEST_LINK_ID_));
  257. assertBubble();
  258. mockWindowOpen.$verify();
  259. FIELDMOCK.$verify();
  260. }
  261. /**
  262. * Urls with invalid schemes shouldn't be linkified.
  263. * @bug 2585360
  264. */
  265. function testDontLinkifyInvalidScheme() {
  266. mockWindowOpen.$replay();
  267. FIELDMOCK.$replay();
  268. linkBubble.enable(FIELDMOCK);
  269. var badLink = goog.dom.createElement(goog.dom.TagName.A);
  270. badLink.href = 'javascript:alert(1)';
  271. goog.dom.setTextContent(badLink, 'bad link');
  272. linkBubble.handleSelectionChange(createMouseEvent(badLink));
  273. assertBubble();
  274. // The link shouldn't exist at all
  275. assertNull(goog.dom.$(goog.editor.plugins.LinkBubble.TEST_LINK_ID_));
  276. assertBubble();
  277. mockWindowOpen.$verify();
  278. FIELDMOCK.$verify();
  279. }
  280. function testIsSafeSchemeToOpen() {
  281. // Urls with no scheme at all are ok too since 'http://' will be prepended.
  282. var good = [
  283. 'http://google.com', 'http://google.com/', 'https://google.com',
  284. 'null@google.com', 'http://www.google.com', 'http://site.com', 'google.com',
  285. 'google', 'http://google', 'HTTP://GOOGLE.COM', 'HtTp://www.google.com'
  286. ];
  287. var bad = [
  288. 'javascript:google.com', 'httpp://google.com', 'data:foo',
  289. 'javascript:alert(\'hi\');', 'abc:def'
  290. ];
  291. for (var i = 0; i < good.length; i++) {
  292. assertTrue(
  293. good[i] + ' should have a safe scheme',
  294. linkBubble.isSafeSchemeToOpen_(good[i]));
  295. }
  296. for (i = 0; i < bad.length; i++) {
  297. assertFalse(
  298. bad[i] + ' should have an unsafe scheme',
  299. linkBubble.isSafeSchemeToOpen_(bad[i]));
  300. }
  301. }
  302. function testShouldOpenWithWhitelist() {
  303. linkBubble.setSafeToOpenSchemes(['abc']);
  304. assertTrue(
  305. 'Scheme should be safe', linkBubble.shouldOpenUrl('abc://google.com'));
  306. assertFalse(
  307. 'Scheme should be unsafe', linkBubble.shouldOpenUrl('http://google.com'));
  308. linkBubble.setBlockOpeningUnsafeSchemes(false);
  309. assertTrue(
  310. 'Non-whitelisted should now be safe after disabling blocking',
  311. linkBubble.shouldOpenUrl('http://google.com'));
  312. }
  313. /**
  314. * @bug 763211
  315. * @bug 2182147
  316. */
  317. function testLongUrlTestLinkAnchorTextCorrect() {
  318. FIELDMOCK.$replay();
  319. linkBubble.enable(FIELDMOCK);
  320. var longUrl = 'http://www.reallylonglinkthatshouldbetruncated' +
  321. 'becauseitistoolong.com';
  322. var truncatedLongUrl = goog.string.truncateMiddle(longUrl, 48);
  323. var longLink = goog.dom.createElement(goog.dom.TagName.A);
  324. longLink.href = longUrl;
  325. goog.dom.setTextContent(longLink, 'Google');
  326. fieldDiv.appendChild(longLink);
  327. linkBubble.handleSelectionChange(createMouseEvent(longLink));
  328. assertBubble();
  329. var testLinkEl = goog.dom.$(goog.editor.plugins.LinkBubble.TEST_LINK_ID_);
  330. assertEquals(
  331. 'The test link\'s anchor text should be the truncated URL.',
  332. truncatedLongUrl, testLinkEl.innerHTML);
  333. fieldDiv.removeChild(longLink);
  334. FIELDMOCK.$verify();
  335. }
  336. /**
  337. * @bug 2416024
  338. */
  339. function testOverridingCreateBubbleContentsDoesntNpeGetTargetUrl() {
  340. FIELDMOCK.$replay();
  341. linkBubble.enable(FIELDMOCK);
  342. stubs.set(linkBubble, 'createBubbleContents', function(elem) {
  343. // getTargetUrl would cause an NPE if urlUtil_ wasn't defined yet.
  344. linkBubble.getTargetUrl();
  345. });
  346. assertNotThrows(
  347. 'Accessing this.urlUtil_ should not NPE',
  348. goog.bind(
  349. linkBubble.handleSelectionChange, linkBubble,
  350. createMouseEvent(link)));
  351. FIELDMOCK.$verify();
  352. }
  353. /**
  354. * @bug 15379294
  355. */
  356. function testUpdateLinkCommandDoesNotTriggerAnException() {
  357. FIELDMOCK.$replay();
  358. linkBubble.enable(FIELDMOCK);
  359. // At this point, the bubble was not created yet using its createBubble
  360. // public method.
  361. assertNotThrows(
  362. 'Executing goog.editor.Command.UPDATE_LINK_BUBBLE should not trigger ' +
  363. 'an exception even if the bubble was not created yet using its ' +
  364. 'createBubble method.',
  365. goog.bind(
  366. linkBubble.execCommandInternal, linkBubble,
  367. goog.editor.Command.UPDATE_LINK_BUBBLE));
  368. FIELDMOCK.$verify();
  369. }
  370. function assertBubble() {
  371. assertTrue('Link bubble visible', linkBubble.isVisible());
  372. assertNotNull(
  373. 'Link bubble created',
  374. goog.dom.$(goog.editor.plugins.LinkBubble.LINK_DIV_ID_));
  375. }
  376. function assertNoBubble() {
  377. assertFalse('Link bubble not visible', linkBubble.isVisible());
  378. assertNull(
  379. 'Link bubble not created',
  380. goog.dom.$(goog.editor.plugins.LinkBubble.LINK_DIV_ID_));
  381. }
  382. function createMouseEvent(target) {
  383. var eventObj = new goog.events.Event(goog.events.EventType.MOUSEUP, target);
  384. eventObj.button = goog.events.BrowserEvent.MouseButton.LEFT;
  385. return new goog.events.BrowserEvent(eventObj, target);
  386. }