range_test.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. // Copyright 2007 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.dom.RangeTest');
  15. goog.setTestOnly('goog.dom.RangeTest');
  16. goog.require('goog.dom');
  17. goog.require('goog.dom.NodeType');
  18. goog.require('goog.dom.Range');
  19. goog.require('goog.dom.RangeType');
  20. goog.require('goog.dom.TagName');
  21. goog.require('goog.dom.TextRange');
  22. goog.require('goog.dom.browserrange');
  23. goog.require('goog.testing.dom');
  24. goog.require('goog.testing.jsunit');
  25. goog.require('goog.userAgent');
  26. var assertRangeEquals = goog.testing.dom.assertRangeEquals;
  27. function setUp() {
  28. // Reset the focus; some tests may invalidate the focus to exercise various
  29. // browser bugs.
  30. var focusableElement = goog.dom.getElement('focusableElement');
  31. focusableElement.focus();
  32. focusableElement.blur();
  33. }
  34. function normalizeHtml(str) {
  35. return str.toLowerCase()
  36. .replace(/[\n\r\f"]/g, '')
  37. .replace(/<\/li>/g, ''); // " for emacs
  38. }
  39. function testCreate() {
  40. assertNotNull(
  41. 'Browser range object can be created for node',
  42. goog.dom.Range.createFromNodeContents(goog.dom.getElement('test1')));
  43. }
  44. function testTableRange() {
  45. var tr = goog.dom.getElement('cell').parentNode;
  46. var range = goog.dom.Range.createFromNodeContents(tr);
  47. assertEquals('Selection should have correct text', '12', range.getText());
  48. assertEquals(
  49. 'Selection should have correct html fragment', '1</td><td>2',
  50. normalizeHtml(range.getHtmlFragment()));
  51. // TODO(robbyw): On IE the TR is included, on FF it is not.
  52. // assertEquals('Selection should have correct valid html',
  53. // '<tr id=row><td>1</td><td>2</td></tr>',
  54. // normalizeHtml(range.getValidHtml()));
  55. assertEquals(
  56. 'Selection should have correct pastable html',
  57. '<table><tbody><tr><td id=cell>1</td><td>2</td></tr></tbody></table>',
  58. normalizeHtml(range.getPastableHtml()));
  59. }
  60. function testUnorderedListRange() {
  61. var ul = goog.dom.getElement('ulTest').firstChild;
  62. var range = goog.dom.Range.createFromNodeContents(ul);
  63. assertEquals(
  64. 'Selection should have correct html fragment', '1<li>2',
  65. normalizeHtml(range.getHtmlFragment()));
  66. // TODO(robbyw): On IE the UL is included, on FF it is not.
  67. // assertEquals('Selection should have correct valid html',
  68. // '<li>1</li><li>2</li>', normalizeHtml(range.getValidHtml()));
  69. assertEquals(
  70. 'Selection should have correct pastable html', '<ul><li>1<li>2</ul>',
  71. normalizeHtml(range.getPastableHtml()));
  72. }
  73. function testOrderedListRange() {
  74. var ol = goog.dom.getElement('olTest').firstChild;
  75. var range = goog.dom.Range.createFromNodeContents(ol);
  76. assertEquals(
  77. 'Selection should have correct html fragment', '1<li>2',
  78. normalizeHtml(range.getHtmlFragment()));
  79. // TODO(robbyw): On IE the OL is included, on FF it is not.
  80. // assertEquals('Selection should have correct valid html',
  81. // '<li>1</li><li>2</li>', normalizeHtml(range.getValidHtml()));
  82. assertEquals(
  83. 'Selection should have correct pastable html', '<ol><li>1<li>2</ol>',
  84. normalizeHtml(range.getPastableHtml()));
  85. }
  86. function testCreateFromNodes() {
  87. var start = goog.dom.getElement('test1').firstChild;
  88. var end = goog.dom.getElement('br');
  89. var range = goog.dom.Range.createFromNodes(start, 2, end, 0);
  90. assertNotNull(
  91. 'Browser range object can be created for W3C node range', range);
  92. assertEquals(
  93. 'Start node should be selected at start endpoint', start,
  94. range.getStartNode());
  95. assertEquals('Selection should start at offset 2', 2, range.getStartOffset());
  96. assertEquals(
  97. 'Start node should be selected at anchor endpoint', start,
  98. range.getAnchorNode());
  99. assertEquals(
  100. 'Selection should be anchored at offset 2', 2, range.getAnchorOffset());
  101. var div = goog.dom.getElement('test2');
  102. assertEquals(
  103. 'DIV node should be selected at end endpoint', div, range.getEndNode());
  104. assertEquals('Selection should end at offset 1', 1, range.getEndOffset());
  105. assertEquals(
  106. 'DIV node should be selected at focus endpoint', div,
  107. range.getFocusNode());
  108. assertEquals(
  109. 'Selection should be focused at offset 1', 1, range.getFocusOffset());
  110. assertTrue(
  111. 'Text content should be "xt\\s*abc"', /xt\s*abc/.test(range.getText()));
  112. assertFalse('Nodes range is not collapsed', range.isCollapsed());
  113. }
  114. function testCreateControlRange() {
  115. if (!goog.userAgent.IE) {
  116. return;
  117. }
  118. var cr = document.body.createControlRange();
  119. cr.addElement(goog.dom.getElement('logo'));
  120. var range = goog.dom.Range.createFromBrowserRange(cr);
  121. assertNotNull(
  122. 'Control range object can be created from browser range', range);
  123. assertEquals(
  124. 'Created range is a control range', goog.dom.RangeType.CONTROL,
  125. range.getType());
  126. }
  127. function testTextNode() {
  128. var range = goog.dom.Range.createFromNodeContents(
  129. goog.dom.getElement('test1').firstChild);
  130. assertEquals(
  131. 'Created range is a text range', goog.dom.RangeType.TEXT,
  132. range.getType());
  133. assertEquals(
  134. 'Text node should be selected at start endpoint', 'Text',
  135. range.getStartNode().nodeValue);
  136. assertEquals('Selection should start at offset 0', 0, range.getStartOffset());
  137. assertEquals(
  138. 'Text node should be selected at end endpoint', 'Text',
  139. range.getEndNode().nodeValue);
  140. assertEquals(
  141. 'Selection should end at offset 4', 'Text'.length, range.getEndOffset());
  142. assertEquals(
  143. 'Container should be text node', goog.dom.NodeType.TEXT,
  144. range.getContainer().nodeType);
  145. assertEquals('Text content should be "Text"', 'Text', range.getText());
  146. assertFalse('Text range is not collapsed', range.isCollapsed());
  147. }
  148. function testDiv() {
  149. var range =
  150. goog.dom.Range.createFromNodeContents(goog.dom.getElement('test2'));
  151. assertEquals(
  152. 'Text node "abc" should be selected at start endpoint', 'abc',
  153. range.getStartNode().nodeValue);
  154. assertEquals('Selection should start at offset 0', 0, range.getStartOffset());
  155. assertEquals(
  156. 'Text node "def" should be selected at end endpoint', 'def',
  157. range.getEndNode().nodeValue);
  158. assertEquals(
  159. 'Selection should end at offset 3', 'def'.length, range.getEndOffset());
  160. assertEquals(
  161. 'Container should be DIV', goog.dom.getElement('test2'),
  162. range.getContainer());
  163. assertTrue(
  164. 'Div text content should be "abc\\s*def"',
  165. /abc\s*def/.test(range.getText()));
  166. assertFalse('Div range is not collapsed', range.isCollapsed());
  167. }
  168. function testEmptyNode() {
  169. var range =
  170. goog.dom.Range.createFromNodeContents(goog.dom.getElement('empty'));
  171. assertEquals(
  172. 'DIV be selected at start endpoint', goog.dom.getElement('empty'),
  173. range.getStartNode());
  174. assertEquals('Selection should start at offset 0', 0, range.getStartOffset());
  175. assertEquals(
  176. 'DIV should be selected at end endpoint', goog.dom.getElement('empty'),
  177. range.getEndNode());
  178. assertEquals('Selection should end at offset 0', 0, range.getEndOffset());
  179. assertEquals(
  180. 'Container should be DIV', goog.dom.getElement('empty'),
  181. range.getContainer());
  182. assertEquals('Empty text content should be ""', '', range.getText());
  183. assertTrue('Empty range is collapsed', range.isCollapsed());
  184. }
  185. function testCollapse() {
  186. var range =
  187. goog.dom.Range.createFromNodeContents(goog.dom.getElement('test2'));
  188. assertFalse('Div range is not collapsed', range.isCollapsed());
  189. range.collapse();
  190. assertTrue(
  191. 'Div range is collapsed after call to empty()', range.isCollapsed());
  192. range = goog.dom.Range.createFromNodeContents(goog.dom.getElement('empty'));
  193. assertTrue('Empty range is collapsed', range.isCollapsed());
  194. range.collapse();
  195. assertTrue('Empty range is still collapsed', range.isCollapsed());
  196. }
  197. // TODO(robbyw): Test iteration over a strange document fragment.
  198. function testIterator() {
  199. goog.testing.dom.assertNodesMatch(
  200. goog.dom.Range.createFromNodeContents(goog.dom.getElement('test2')),
  201. ['abc', '#br', '#br', 'def']);
  202. }
  203. function testReversedNodes() {
  204. var node = goog.dom.getElement('test1').firstChild;
  205. var range = goog.dom.Range.createFromNodes(node, 4, node, 0);
  206. assertTrue('Range is reversed', range.isReversed());
  207. node = goog.dom.getElement('test3');
  208. range = goog.dom.Range.createFromNodes(node, 0, node, 1);
  209. assertFalse('Range is not reversed', range.isReversed());
  210. }
  211. function testReversedContents() {
  212. var range =
  213. goog.dom.Range.createFromNodeContents(goog.dom.getElement('test1'), true);
  214. assertTrue('Range is reversed', range.isReversed());
  215. assertEquals('Range should select "Text"', 'Text', range.getText());
  216. assertEquals('Range start offset should be 0', 0, range.getStartOffset());
  217. assertEquals('Range end offset should be 4', 4, range.getEndOffset());
  218. assertEquals('Range anchor offset should be 4', 4, range.getAnchorOffset());
  219. assertEquals('Range focus offset should be 0', 0, range.getFocusOffset());
  220. var range2 = range.clone();
  221. range.collapse(true);
  222. assertTrue('Range is collapsed', range.isCollapsed());
  223. assertFalse('Collapsed range is not reversed', range.isReversed());
  224. assertEquals(
  225. 'Post collapse start offset should be 4', 4, range.getStartOffset());
  226. range2.collapse(false);
  227. assertTrue('Range 2 is collapsed', range2.isCollapsed());
  228. assertFalse('Collapsed range 2 is not reversed', range2.isReversed());
  229. assertEquals(
  230. 'Post collapse start offset 2 should be 0', 0, range2.getStartOffset());
  231. }
  232. function testRemoveContents() {
  233. var outer = goog.dom.getElement('removeTest');
  234. var range = goog.dom.Range.createFromNodeContents(outer.firstChild);
  235. range.removeContents();
  236. assertEquals('Removed range content should be ""', '', range.getText());
  237. assertTrue('Removed range should be collapsed', range.isCollapsed());
  238. assertEquals('Outer div should have 1 child now', 1, outer.childNodes.length);
  239. assertEquals(
  240. 'Inner div should be empty', 0, outer.firstChild.childNodes.length);
  241. }
  242. function testRemovePartialContents() {
  243. var outer = goog.dom.getElement('removePartialTest');
  244. var originalText = goog.dom.getTextContent(outer);
  245. try {
  246. var range = goog.dom.Range.createFromNodes(
  247. outer.firstChild, 2, outer.firstChild, 4);
  248. removeHelper(1, range, outer, 1, '0145');
  249. range = goog.dom.Range.createFromNodes(
  250. outer.firstChild, 0, outer.firstChild, 1);
  251. removeHelper(2, range, outer, 1, '145');
  252. range = goog.dom.Range.createFromNodes(
  253. outer.firstChild, 2, outer.firstChild, 3);
  254. removeHelper(3, range, outer, 1, '14');
  255. var br = goog.dom.createDom(goog.dom.TagName.BR);
  256. outer.appendChild(br);
  257. range = goog.dom.Range.createFromNodes(outer.firstChild, 1, outer, 1);
  258. removeHelper(4, range, outer, 2, '1<br>');
  259. outer.innerHTML = '<br>123';
  260. range = goog.dom.Range.createFromNodes(outer, 0, outer.lastChild, 2);
  261. removeHelper(5, range, outer, 1, '3');
  262. outer.innerHTML = '123<br>456';
  263. range =
  264. goog.dom.Range.createFromNodes(outer.firstChild, 1, outer.lastChild, 2);
  265. removeHelper(6, range, outer, 2, '16');
  266. outer.innerHTML = '123<br>456';
  267. range =
  268. goog.dom.Range.createFromNodes(outer.firstChild, 0, outer.lastChild, 2);
  269. removeHelper(7, range, outer, 1, '6');
  270. outer.innerHTML = '<div></div>';
  271. range = goog.dom.Range.createFromNodeContents(outer.firstChild);
  272. removeHelper(8, range, outer, 1, '<div></div>');
  273. } finally {
  274. // Restore the original text state for repeated runs.
  275. goog.dom.setTextContent(outer, originalText);
  276. }
  277. // TODO(robbyw): Fix the following edge cases:
  278. // * Selecting contents of a node containing multiply empty divs
  279. // * Selecting via createFromNodes(x, 0, x, x.childNodes.length)
  280. // * Consistent handling of nodeContents(<div><div></div></div>).remove
  281. }
  282. function removeHelper(
  283. testNumber, range, outer, expectedChildCount, expectedContent) {
  284. range.removeContents();
  285. assertTrue(
  286. testNumber + ': Removed range should now be collapsed',
  287. range.isCollapsed());
  288. assertEquals(
  289. testNumber + ': Removed range content should be ""', '', range.getText());
  290. assertEquals(
  291. testNumber + ': Outer div should contain correct text', expectedContent,
  292. outer.innerHTML.toLowerCase());
  293. assertEquals(
  294. testNumber + ': Outer div should have ' + expectedChildCount +
  295. ' children now',
  296. expectedChildCount, outer.childNodes.length);
  297. assertNotNull(
  298. testNumber + ': Empty node should still exist',
  299. goog.dom.getElement('empty'));
  300. }
  301. function testSurroundContents() {
  302. var outer = goog.dom.getElement('surroundTest');
  303. outer.innerHTML = '---Text that<br>will be surrounded---';
  304. var range = goog.dom.Range.createFromNodes(
  305. outer.firstChild, 3, outer.lastChild,
  306. outer.lastChild.nodeValue.length - 3);
  307. var div = goog.dom.createDom(goog.dom.TagName.DIV, {'style': 'color: red'});
  308. var output = range.surroundContents(div);
  309. assertEquals(
  310. 'Outer element should contain new element', outer, output.parentNode);
  311. assertFalse('New element should have no id', !!output.id);
  312. assertEquals('New element should be red', 'red', output.style.color);
  313. assertEquals(
  314. 'Outer element should have three children', 3, outer.childNodes.length);
  315. assertEquals(
  316. 'New element should have three children', 3, output.childNodes.length);
  317. // TODO(robbyw): Ensure the range stays in a reasonable state.
  318. }
  319. /**
  320. * Given two offsets into the 'foobar' node, make sure that inserting
  321. * nodes at those offsets doesn't change a selection of 'oba'.
  322. * @bug 1480638
  323. */
  324. function assertSurroundDoesntChangeSelectionWithOffsets(
  325. offset1, offset2, expectedHtml) {
  326. var div = goog.dom.getElement('bug1480638');
  327. goog.dom.setTextContent(div, 'foobar');
  328. var rangeToSelect =
  329. goog.dom.Range.createFromNodes(div.firstChild, 2, div.firstChild, 5);
  330. rangeToSelect.select();
  331. var rangeToSurround = goog.dom.Range.createFromNodes(
  332. div.firstChild, offset1, div.firstChild, offset2);
  333. rangeToSurround.surroundWithNodes(
  334. goog.dom.createDom(goog.dom.TagName.SPAN),
  335. goog.dom.createDom(goog.dom.TagName.SPAN));
  336. // Make sure that the selection didn't change.
  337. assertHTMLEquals(
  338. 'Selection must not change when contents are surrounded.', expectedHtml,
  339. goog.dom.Range.createFromWindow().getHtmlFragment());
  340. }
  341. function testSurroundWithNodesDoesntChangeSelection1() {
  342. assertSurroundDoesntChangeSelectionWithOffsets(
  343. 3, 4, 'o<span></span>b<span></span>a');
  344. }
  345. function testSurroundWithNodesDoesntChangeSelection2() {
  346. assertSurroundDoesntChangeSelectionWithOffsets(3, 6, 'o<span></span>ba');
  347. }
  348. function testSurroundWithNodesDoesntChangeSelection3() {
  349. assertSurroundDoesntChangeSelectionWithOffsets(1, 3, 'o<span></span>ba');
  350. }
  351. function testSurroundWithNodesDoesntChangeSelection4() {
  352. assertSurroundDoesntChangeSelectionWithOffsets(1, 6, 'oba');
  353. }
  354. function testInsertNode() {
  355. var outer = goog.dom.getElement('insertTest');
  356. goog.dom.setTextContent(outer, 'ACD');
  357. var range =
  358. goog.dom.Range.createFromNodes(outer.firstChild, 1, outer.firstChild, 2);
  359. range.insertNode(goog.dom.createTextNode('B'), true);
  360. assertEquals(
  361. 'Element should have correct innerHTML', 'ABCD', outer.innerHTML);
  362. goog.dom.setTextContent(outer, '12');
  363. range =
  364. goog.dom.Range.createFromNodes(outer.firstChild, 0, outer.firstChild, 1);
  365. var br = range.insertNode(goog.dom.createDom(goog.dom.TagName.BR), false);
  366. assertEquals(
  367. 'New element should have correct innerHTML', '1<br>2',
  368. outer.innerHTML.toLowerCase());
  369. assertEquals('BR should be in outer', outer, br.parentNode);
  370. }
  371. function testReplaceContentsWithNode() {
  372. var outer = goog.dom.getElement('insertTest');
  373. goog.dom.setTextContent(outer, 'AXC');
  374. var range =
  375. goog.dom.Range.createFromNodes(outer.firstChild, 1, outer.firstChild, 2);
  376. range.replaceContentsWithNode(goog.dom.createTextNode('B'));
  377. assertEquals('Element should have correct innerHTML', 'ABC', outer.innerHTML);
  378. goog.dom.setTextContent(outer, 'ABC');
  379. range =
  380. goog.dom.Range.createFromNodes(outer.firstChild, 3, outer.firstChild, 3);
  381. range.replaceContentsWithNode(goog.dom.createTextNode('D'));
  382. assertEquals(
  383. 'Element should have correct innerHTML after collapsed replace', 'ABCD',
  384. outer.innerHTML);
  385. outer.innerHTML = 'AX<b>X</b>XC';
  386. range =
  387. goog.dom.Range.createFromNodes(outer.firstChild, 1, outer.lastChild, 1);
  388. range.replaceContentsWithNode(goog.dom.createTextNode('B'));
  389. goog.testing.dom.assertHtmlContentsMatch('ABC', outer);
  390. }
  391. function testSurroundWithNodes() {
  392. var outer = goog.dom.getElement('insertTest');
  393. goog.dom.setTextContent(outer, 'ACE');
  394. var range =
  395. goog.dom.Range.createFromNodes(outer.firstChild, 1, outer.firstChild, 2);
  396. range.surroundWithNodes(
  397. goog.dom.createTextNode('B'), goog.dom.createTextNode('D'));
  398. assertEquals(
  399. 'New element should have correct innerHTML', 'ABCDE', outer.innerHTML);
  400. }
  401. function testIsRangeInDocument() {
  402. var outer = goog.dom.getElement('insertTest');
  403. outer.innerHTML = '<br>ABC';
  404. var range = goog.dom.Range.createCaret(outer.lastChild, 1);
  405. assertEquals(
  406. 'Should get correct start element', 'ABC',
  407. range.getStartNode().nodeValue);
  408. assertTrue('Should be considered in document', range.isRangeInDocument());
  409. goog.dom.setTextContent(outer, 'DEF');
  410. assertFalse('Should be marked as out of document', range.isRangeInDocument());
  411. }
  412. function testRemovedNode() {
  413. var node = goog.dom.getElement('removeNodeTest');
  414. var range = goog.dom.browserrange.createRangeFromNodeContents(node);
  415. range.select();
  416. goog.dom.removeNode(node);
  417. var newRange = goog.dom.Range.createFromWindow(window);
  418. // In Chrome 14 and below (<= Webkit 535.1), newRange will be null.
  419. // In Chrome 16 and above (>= Webkit 535.7), newRange will be collapsed
  420. // like on other browsers.
  421. // We didn't bother testing in between.
  422. if (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('535.7')) {
  423. assertNull('Webkit supports rangeCount == 0', newRange);
  424. } else {
  425. assertTrue(
  426. 'The other browsers will just have an empty range.',
  427. newRange.isCollapsed());
  428. }
  429. }
  430. function testReversedRange() {
  431. if (goog.userAgent.EDGE_OR_IE) return; // IE doesn't make this distinction.
  432. goog.dom.Range
  433. .createFromNodes(
  434. goog.dom.getElement('test2'), 0, goog.dom.getElement('test1'), 0)
  435. .select();
  436. var range = goog.dom.Range.createFromWindow(window);
  437. assertTrue('Range should be reversed', range.isReversed());
  438. }
  439. function testUnreversedRange() {
  440. goog.dom.Range
  441. .createFromNodes(
  442. goog.dom.getElement('test1'), 0, goog.dom.getElement('test2'), 0)
  443. .select();
  444. var range = goog.dom.Range.createFromWindow(window);
  445. assertFalse('Range should not be reversed', range.isReversed());
  446. }
  447. function testReversedThenUnreversedRange() {
  448. // This tests a workaround for a webkit bug where webkit caches selections
  449. // incorrectly.
  450. goog.dom.Range
  451. .createFromNodes(
  452. goog.dom.getElement('test2'), 0, goog.dom.getElement('test1'), 0)
  453. .select();
  454. goog.dom.Range
  455. .createFromNodes(
  456. goog.dom.getElement('test1'), 0, goog.dom.getElement('test2'), 0)
  457. .select();
  458. var range = goog.dom.Range.createFromWindow(window);
  459. assertFalse('Range should not be reversed', range.isReversed());
  460. }
  461. function testHasAndClearSelection() {
  462. goog.dom.Range.createFromNodeContents(goog.dom.getElement('test1')).select();
  463. assertTrue('Selection should exist', goog.dom.Range.hasSelection());
  464. goog.dom.Range.clearSelection();
  465. assertFalse('Selection should not exist', goog.dom.Range.hasSelection());
  466. }
  467. function assertForward(string, startNode, startOffset, endNode, endOffset) {
  468. var root = goog.dom.getElement('test2');
  469. var originalInnerHtml = root.innerHTML;
  470. assertFalse(
  471. string,
  472. goog.dom.Range.isReversed(startNode, startOffset, endNode, endOffset));
  473. assertTrue(
  474. string,
  475. goog.dom.Range.isReversed(endNode, endOffset, startNode, startOffset));
  476. assertEquals(
  477. 'Contents should be unaffected after: ' + string, root.innerHTML,
  478. originalInnerHtml);
  479. }
  480. function testIsReversed() {
  481. var root = goog.dom.getElement('test2');
  482. var text1 = root.firstChild; // Text content: 'abc'.
  483. var br = root.childNodes[1];
  484. var text2 = root.lastChild; // Text content: 'def'.
  485. assertFalse(
  486. 'Same element position gives false',
  487. goog.dom.Range.isReversed(root, 0, root, 0));
  488. assertFalse(
  489. 'Same text position gives false',
  490. goog.dom.Range.isReversed(text1, 0, text2, 0));
  491. assertForward(
  492. 'Element offsets should compare against each other', root, 0, root, 2);
  493. assertForward(
  494. 'Text node offsets should compare against each other', text1, 0, text2,
  495. 2);
  496. assertForward('Text nodes should compare correctly', text1, 0, text2, 0);
  497. assertForward('Text nodes should compare to later elements', text1, 0, br, 0);
  498. assertForward(
  499. 'Text nodes should compare to earlier elements', br, 0, text2, 0);
  500. assertForward('Parent is before element child', root, 0, br, 0);
  501. assertForward('Parent is before text child', root, 0, text1, 0);
  502. assertFalse(
  503. 'Equivalent position gives false',
  504. goog.dom.Range.isReversed(root, 0, text1, 0));
  505. assertFalse(
  506. 'Equivalent position gives false',
  507. goog.dom.Range.isReversed(root, 1, br, 0));
  508. assertForward('End of element is after children', text1, 0, root, 3);
  509. assertForward('End of element is after children', br, 0, root, 3);
  510. assertForward('End of element is after children', text2, 0, root, 3);
  511. assertForward('End of element is after end of last child', text2, 3, root, 3);
  512. }
  513. function testSelectAroundSpaces() {
  514. // set the selection
  515. var textNode = goog.dom.getElement('textWithSpaces').firstChild;
  516. goog.dom.TextRange.createFromNodes(textNode, 5, textNode, 12).select();
  517. // get the selection and check that it matches what we set it to
  518. var range = goog.dom.Range.createFromWindow();
  519. assertEquals(' world ', range.getText());
  520. assertEquals(5, range.getStartOffset());
  521. assertEquals(12, range.getEndOffset());
  522. assertEquals(textNode, range.getContainer());
  523. // Check the contents again, because there used to be a bug where
  524. // it changed after calling getContainer().
  525. assertEquals(' world ', range.getText());
  526. }
  527. function testSelectInsideSpaces() {
  528. // set the selection
  529. var textNode = goog.dom.getElement('textWithSpaces').firstChild;
  530. goog.dom.TextRange.createFromNodes(textNode, 6, textNode, 11).select();
  531. // get the selection and check that it matches what we set it to
  532. var range = goog.dom.Range.createFromWindow();
  533. assertEquals('world', range.getText());
  534. assertEquals(6, range.getStartOffset());
  535. assertEquals(11, range.getEndOffset());
  536. assertEquals(textNode, range.getContainer());
  537. // Check the contents again, because there used to be a bug where
  538. // it changed after calling getContainer().
  539. assertEquals('world', range.getText());
  540. }
  541. function testRangeBeforeBreak() {
  542. var container = goog.dom.getElement('rangeAroundBreaks');
  543. var text = container.firstChild;
  544. var offset = text.length;
  545. assertEquals(4, offset);
  546. var br = container.childNodes[1];
  547. var caret = goog.dom.Range.createCaret(text, offset);
  548. caret.select();
  549. assertEquals(offset, caret.getStartOffset());
  550. var range = goog.dom.Range.createFromWindow();
  551. assertFalse('Should not contain whole <br>', range.containsNode(br, false));
  552. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
  553. assertTrue(
  554. 'Range over <br> is adjacent to the immediate range before it',
  555. range.containsNode(br, true));
  556. } else {
  557. assertFalse(
  558. 'Should not contain partial <br>', range.containsNode(br, true));
  559. }
  560. assertEquals(offset, range.getStartOffset());
  561. assertEquals(text, range.getStartNode());
  562. }
  563. function testRangeAfterBreak() {
  564. var container = goog.dom.getElement('rangeAroundBreaks');
  565. var br = container.childNodes[1];
  566. var caret = goog.dom.Range.createCaret(container.lastChild, 0);
  567. caret.select();
  568. assertEquals(0, caret.getStartOffset());
  569. var range = goog.dom.Range.createFromWindow();
  570. assertFalse('Should not contain whole <br>', range.containsNode(br, false));
  571. var isSafari3 =
  572. goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('528');
  573. if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) ||
  574. isSafari3) {
  575. assertTrue(
  576. 'Range over <br> is adjacent to the immediate range after it',
  577. range.containsNode(br, true));
  578. } else {
  579. assertFalse(
  580. 'Should not contain partial <br>', range.containsNode(br, true));
  581. }
  582. if (isSafari3) {
  583. assertEquals(2, range.getStartOffset());
  584. assertEquals(container, range.getStartNode());
  585. } else {
  586. assertEquals(0, range.getStartOffset());
  587. assertEquals(container.lastChild, range.getStartNode());
  588. }
  589. }
  590. function testRangeAtBreakAtStart() {
  591. var container = goog.dom.getElement('breaksAroundNode');
  592. var br = container.firstChild;
  593. var caret = goog.dom.Range.createCaret(container.firstChild, 0);
  594. caret.select();
  595. assertEquals(0, caret.getStartOffset());
  596. var range = goog.dom.Range.createFromWindow();
  597. assertTrue(
  598. 'Range over <br> is adjacent to the immediate range before it',
  599. range.containsNode(br, true));
  600. assertFalse('Should not contain whole <br>', range.containsNode(br, false));
  601. assertRangeEquals(container, 0, container, 0, range);
  602. }
  603. function testFocusedElementDisappears() {
  604. // This reproduces a failure case specific to Gecko, where an element is
  605. // created, contentEditable is set, is focused, and removed. After that
  606. // happens, calling selection.collapse fails.
  607. // https://bugzilla.mozilla.org/show_bug.cgi?id=773137
  608. var disappearingElement = goog.dom.createDom(goog.dom.TagName.DIV);
  609. document.body.appendChild(disappearingElement);
  610. disappearingElement.contentEditable = true;
  611. disappearingElement.focus();
  612. document.body.removeChild(disappearingElement);
  613. var container = goog.dom.getElement('empty');
  614. var caret = goog.dom.Range.createCaret(container, 0);
  615. // This should not throw.
  616. caret.select();
  617. assertEquals(0, caret.getStartOffset());
  618. }
  619. function assertNodeEquals(expected, actual) {
  620. assertEquals(
  621. 'Expected: ' + goog.testing.dom.exposeNode(expected) + '\nActual: ' +
  622. goog.testing.dom.exposeNode(actual),
  623. expected, actual);
  624. }