range_test.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  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.rangeTest');
  15. goog.setTestOnly('goog.editor.rangeTest');
  16. goog.require('goog.dom');
  17. goog.require('goog.dom.Range');
  18. goog.require('goog.dom.TagName');
  19. goog.require('goog.editor.range');
  20. goog.require('goog.editor.range.Point');
  21. goog.require('goog.string');
  22. goog.require('goog.testing.dom');
  23. goog.require('goog.testing.jsunit');
  24. goog.require('goog.userAgent');
  25. var savedHtml;
  26. var $;
  27. function setUpPage() {
  28. $ = goog.dom.getElement;
  29. }
  30. function setUp() {
  31. savedHtml = $('root').innerHTML;
  32. }
  33. function tearDown() {
  34. $('root').innerHTML = savedHtml;
  35. }
  36. function testNoNarrow() {
  37. var def = $('def');
  38. var jkl = $('jkl');
  39. var range =
  40. goog.dom.Range.createFromNodes(def.firstChild, 1, jkl.firstChild, 2);
  41. range = goog.editor.range.narrow(range, $('parentNode'));
  42. goog.testing.dom.assertRangeEquals(
  43. def.firstChild, 1, jkl.firstChild, 2, range);
  44. }
  45. function testNarrowAtEndEdge() {
  46. var def = $('def');
  47. var jkl = $('jkl');
  48. var range =
  49. goog.dom.Range.createFromNodes(def.firstChild, 1, jkl.firstChild, 2);
  50. range = goog.editor.range.narrow(range, def);
  51. goog.testing.dom.assertRangeEquals(
  52. def.firstChild, 1, def.firstChild, 3, range);
  53. }
  54. function testNarrowAtStartEdge() {
  55. var def = $('def');
  56. var jkl = $('jkl');
  57. var range =
  58. goog.dom.Range.createFromNodes(def.firstChild, 1, jkl.firstChild, 2);
  59. range = goog.editor.range.narrow(range, jkl);
  60. goog.testing.dom.assertRangeEquals(
  61. jkl.firstChild, 0, jkl.firstChild, 2, range);
  62. }
  63. function testNarrowOutsideElement() {
  64. var def = $('def');
  65. var jkl = $('jkl');
  66. var range =
  67. goog.dom.Range.createFromNodes(def.firstChild, 1, jkl.firstChild, 2);
  68. range = goog.editor.range.narrow(range, $('pqr'));
  69. assertNull(range);
  70. }
  71. function testNoExpand() {
  72. var div = $('parentNode');
  73. div.innerHTML = '<div>longword</div>';
  74. // Select "ongwo" and make sure we don't expand since this is not
  75. // a full container.
  76. var textNode = div.firstChild.firstChild;
  77. var range = goog.dom.Range.createFromNodes(textNode, 1, textNode, 6);
  78. range = goog.editor.range.expand(range);
  79. goog.testing.dom.assertRangeEquals(textNode, 1, textNode, 6, range);
  80. }
  81. function testSimpleExpand() {
  82. var div = $('parentNode');
  83. div.innerHTML = '<div>longword</div>foo';
  84. // Select "longword" and make sure we do expand to include the div since
  85. // the full container text is selected.
  86. var textNode = div.firstChild.firstChild;
  87. var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 8);
  88. range = goog.editor.range.expand(range);
  89. goog.testing.dom.assertRangeEquals(div, 0, div, 1, range);
  90. // Select "foo" and make sure we expand out to the parent div.
  91. var fooNode = div.lastChild;
  92. range = goog.dom.Range.createFromNodes(fooNode, 0, fooNode, 3);
  93. range = goog.editor.range.expand(range);
  94. goog.testing.dom.assertRangeEquals(div, 1, div, 2, range);
  95. }
  96. function testDoubleExpand() {
  97. var div = $('parentNode');
  98. div.innerHTML = '<div><span>longword</span></div>foo';
  99. // Select "longword" and make sure we do expand to include the span
  100. // and the div since both of their full contents are selected.
  101. var textNode = div.firstChild.firstChild.firstChild;
  102. var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 8);
  103. range = goog.editor.range.expand(range);
  104. goog.testing.dom.assertRangeEquals(div, 0, div, 1, range);
  105. // Same visible position, different dom position.
  106. // Start in text node, end in span.
  107. range = goog.dom.Range.createFromNodes(textNode, 0, textNode.parentNode, 1);
  108. range = goog.editor.range.expand(range);
  109. goog.testing.dom.assertRangeEquals(div, 0, div, 1, range);
  110. }
  111. function testMultipleChildrenExpand() {
  112. var div = $('parentNode');
  113. div.innerHTML = '<ol><li>one</li><li>two</li><li>three</li></ol>';
  114. // Select "two" and make sure we expand to the li, but not the ol.
  115. var li = div.firstChild.childNodes[1];
  116. var textNode = li.firstChild;
  117. var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 3);
  118. range = goog.editor.range.expand(range);
  119. goog.testing.dom.assertRangeEquals(li.parentNode, 1, li.parentNode, 2, range);
  120. // Make the same visible selection, only slightly different dom position.
  121. // Select starting from the text node, but ending in the li.
  122. range = goog.dom.Range.createFromNodes(textNode, 0, li, 1);
  123. range = goog.editor.range.expand(range);
  124. goog.testing.dom.assertRangeEquals(li.parentNode, 1, li.parentNode, 2, range);
  125. }
  126. function testSimpleDifferentContainersExpand() {
  127. var div = $('parentNode');
  128. div.innerHTML = '<ol><li>1</li><li><b>bold</b><i>italic</i></li></ol>';
  129. // Select all of "bold" and "italic" at the text node level, and
  130. // make sure we expand to the li.
  131. var li = div.firstChild.childNodes[1];
  132. var boldNode = li.childNodes[0];
  133. var italicNode = li.childNodes[1];
  134. var range = goog.dom.Range.createFromNodes(
  135. boldNode.firstChild, 0, italicNode.firstChild, 6);
  136. range = goog.editor.range.expand(range);
  137. goog.testing.dom.assertRangeEquals(li.parentNode, 1, li.parentNode, 2, range);
  138. // Make the same visible selection, only slightly different dom position.
  139. // Select "bold" at the b node level and "italic" at the text node level.
  140. range = goog.dom.Range.createFromNodes(boldNode, 0, italicNode.firstChild, 6);
  141. range = goog.editor.range.expand(range);
  142. goog.testing.dom.assertRangeEquals(li.parentNode, 1, li.parentNode, 2, range);
  143. }
  144. function testSimpleDifferentContainersSmallExpand() {
  145. var div = $('parentNode');
  146. div.innerHTML = '<ol><li>1</li><li><b>bold</b><i>italic</i>' +
  147. '<u>under</u></li></ol>';
  148. // Select all of "bold" and "italic", but we can't expand to the
  149. // entire li since we didn't select "under".
  150. var li = div.firstChild.childNodes[1];
  151. var boldNode = li.childNodes[0];
  152. var italicNode = li.childNodes[1];
  153. var range = goog.dom.Range.createFromNodes(
  154. boldNode.firstChild, 0, italicNode.firstChild, 6);
  155. range = goog.editor.range.expand(range);
  156. goog.testing.dom.assertRangeEquals(li, 0, li, 2, range);
  157. // Same visible position, different dom position.
  158. // Select "bold" starting in text node, "italic" at i node.
  159. range = goog.dom.Range.createFromNodes(boldNode.firstChild, 0, italicNode, 1);
  160. range = goog.editor.range.expand(range);
  161. goog.testing.dom.assertRangeEquals(li, 0, li, 2, range);
  162. }
  163. function testEmbeddedDifferentContainersExpand() {
  164. var div = $('parentNode');
  165. div.innerHTML = '<div><b><i>italic</i>after</b><u>under</u></div>foo';
  166. // Select "italic" "after" "under", should expand all the way to parent.
  167. var boldNode = div.firstChild.childNodes[0];
  168. var italicNode = boldNode.childNodes[0];
  169. var underNode = div.firstChild.childNodes[1];
  170. var range = goog.dom.Range.createFromNodes(
  171. italicNode.firstChild, 0, underNode.firstChild, 5);
  172. range = goog.editor.range.expand(range);
  173. goog.testing.dom.assertRangeEquals(div, 0, div, 1, range);
  174. }
  175. function testReverseSimpleExpand() {
  176. var div = $('parentNode');
  177. div.innerHTML = '<div>longword</div>foo';
  178. // Select "longword" and make sure we do expand to include the div since
  179. // the full container text is selected.
  180. var textNode = div.firstChild.firstChild;
  181. var range = goog.dom.Range.createFromNodes(textNode, 8, textNode, 0);
  182. range = goog.editor.range.expand(range);
  183. goog.testing.dom.assertRangeEquals(div, 0, div, 1, range);
  184. }
  185. function testExpandWithStopNode() {
  186. var div = $('parentNode');
  187. div.innerHTML = '<div><span>word</span></div>foo';
  188. // Select "word".
  189. var span = div.firstChild.firstChild;
  190. var textNode = span.firstChild;
  191. var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 4);
  192. range = goog.editor.range.expand(range);
  193. goog.testing.dom.assertRangeEquals(div, 0, div, 1, range);
  194. // Same selection, but force stop at the span.
  195. range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 4);
  196. range = goog.editor.range.expand(range, span);
  197. goog.testing.dom.assertRangeEquals(span, 0, span, 1, range);
  198. }
  199. // Ojan didn't believe this code worked, this was the case he
  200. // thought was broken. Keeping just as a regression test.
  201. function testOjanCase() {
  202. var div = $('parentNode');
  203. div.innerHTML = '<em><i><b>foo</b>bar</i></em>';
  204. // Select "foo", at text node level.
  205. var iNode = div.firstChild.firstChild;
  206. var textNode = iNode.firstChild.firstChild;
  207. var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 3);
  208. range = goog.editor.range.expand(range);
  209. goog.testing.dom.assertRangeEquals(iNode, 0, iNode, 1, range);
  210. // Same selection, at b node level.
  211. range =
  212. goog.dom.Range.createFromNodes(iNode.firstChild, 0, iNode.firstChild, 1);
  213. range = goog.editor.range.expand(range);
  214. goog.testing.dom.assertRangeEquals(iNode, 0, iNode, 1, range);
  215. }
  216. function testPlaceCursorNextToLeft() {
  217. var div = $('parentNode');
  218. div.innerHTML = 'foo<div id="bar">bar</div>baz';
  219. var node = $('bar');
  220. var range = goog.editor.range.placeCursorNextTo(node, true);
  221. var expose = goog.testing.dom.exposeNode;
  222. assertEquals(
  223. 'Selection should be to the left of the node ' + expose(node) + ',' +
  224. expose(range.getStartNode().nextSibling),
  225. node, range.getStartNode().nextSibling);
  226. assertEquals('Selection should be collapsed', true, range.isCollapsed());
  227. }
  228. function testPlaceCursorNextToRight() {
  229. var div = $('parentNode');
  230. div.innerHTML = 'foo<div id="bar">bar</div>baz';
  231. var node = $('bar');
  232. var range = goog.editor.range.placeCursorNextTo(node, false);
  233. assertEquals(
  234. 'Selection should be to the right of the node', node,
  235. range.getStartNode().previousSibling);
  236. assertEquals('Selection should be collapsed', true, range.isCollapsed());
  237. }
  238. function testPlaceCursorNextTo_rightOfLineBreak() {
  239. var div = $('parentNode');
  240. div.innerHTML = '<div contentEditable="true">hhhh<br />h</div>';
  241. var children = div.firstChild.childNodes;
  242. assertEquals(3, children.length);
  243. var node = children[1];
  244. var range = goog.editor.range.placeCursorNextTo(node, false);
  245. assertEquals(node.nextSibling, range.getStartNode());
  246. }
  247. function testPlaceCursorNextTo_leftOfHr() {
  248. var div = $('parentNode');
  249. div.innerHTML = '<hr />aaa';
  250. var children = div.childNodes;
  251. assertEquals(2, children.length);
  252. var node = children[0];
  253. var range = goog.editor.range.placeCursorNextTo(node, true);
  254. assertEquals(div, range.getStartNode());
  255. assertEquals(0, range.getStartOffset());
  256. }
  257. function testPlaceCursorNextTo_rightOfHr() {
  258. var div = $('parentNode');
  259. div.innerHTML = 'aaa<hr>';
  260. var children = div.childNodes;
  261. assertEquals(2, children.length);
  262. var node = children[1];
  263. var range = goog.editor.range.placeCursorNextTo(node, false);
  264. assertEquals(div, range.getStartNode());
  265. assertEquals(2, range.getStartOffset());
  266. }
  267. function testPlaceCursorNextTo_rightOfImg() {
  268. var div = $('parentNode');
  269. div.innerHTML =
  270. 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">bbb';
  271. var children = div.childNodes;
  272. assertEquals(3, children.length);
  273. var imgNode = children[1];
  274. var range = goog.editor.range.placeCursorNextTo(imgNode, false);
  275. assertEquals(
  276. 'range node should be the right sibling of img tag', children[2],
  277. range.getStartNode());
  278. assertEquals(0, range.getStartOffset());
  279. }
  280. function testPlaceCursorNextTo_rightOfImgAtEnd() {
  281. var div = $('parentNode');
  282. div.innerHTML =
  283. 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">';
  284. var children = div.childNodes;
  285. assertEquals(2, children.length);
  286. var imgNode = children[1];
  287. var range = goog.editor.range.placeCursorNextTo(imgNode, false);
  288. assertEquals(
  289. 'range node should be the parent of img', div, range.getStartNode());
  290. assertEquals(
  291. 'offset should be right after the img tag', 2, range.getStartOffset());
  292. }
  293. function testPlaceCursorNextTo_leftOfImg() {
  294. var div = $('parentNode');
  295. div.innerHTML =
  296. '<img src="https://www.google.com/images/srpr/logo3w.png">xxx';
  297. var children = div.childNodes;
  298. assertEquals(2, children.length);
  299. var imgNode = children[0];
  300. var range = goog.editor.range.placeCursorNextTo(imgNode, true);
  301. assertEquals(
  302. 'range node should be the parent of img', div, range.getStartNode());
  303. assertEquals('offset should point to the img tag', 0, range.getStartOffset());
  304. }
  305. function testPlaceCursorNextTo_rightOfFirstOfTwoImgTags() {
  306. var div = $('parentNode');
  307. div.innerHTML =
  308. 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">' +
  309. '<img src="https://www.google.com/images/srpr/logo3w.png">';
  310. var children = div.childNodes;
  311. assertEquals(3, children.length);
  312. var imgNode = children[1]; // First of two IMG nodes
  313. var range = goog.editor.range.placeCursorNextTo(imgNode, false);
  314. assertEquals(
  315. 'range node should be the parent of img instead of ' +
  316. 'node with innerHTML=' + range.getStartNode().innerHTML,
  317. div, range.getStartNode());
  318. assertEquals(
  319. 'offset should be right after the img tag', 2, range.getStartOffset());
  320. }
  321. function testGetDeepEndPoint() {
  322. var div = $('parentNode');
  323. var def = $('def');
  324. var jkl = $('jkl');
  325. assertPointEquals(
  326. div.firstChild, 0, goog.editor.range.getDeepEndPoint(
  327. goog.dom.Range.createFromNodeContents(div), true));
  328. assertPointEquals(
  329. div.lastChild, div.lastChild.length,
  330. goog.editor.range.getDeepEndPoint(
  331. goog.dom.Range.createFromNodeContents(div), false));
  332. assertPointEquals(
  333. def.firstChild, 0, goog.editor.range.getDeepEndPoint(
  334. goog.dom.Range.createCaret(div, 1), true));
  335. assertPointEquals(
  336. def.nextSibling, 0, goog.editor.range.getDeepEndPoint(
  337. goog.dom.Range.createCaret(div, 2), true));
  338. }
  339. function testNormalizeOnNormalizedDom() {
  340. var defText = $('def').firstChild;
  341. var jklText = $('jkl').firstChild;
  342. var range = goog.dom.Range.createFromNodes(defText, 1, jklText, 2);
  343. var newRange = normalizeBody(range);
  344. goog.testing.dom.assertRangeEquals(defText, 1, jklText, 2, newRange);
  345. }
  346. function testDeepPointFindingOnNormalizedDom() {
  347. var def = $('def');
  348. var jkl = $('jkl');
  349. var range = goog.dom.Range.createFromNodes(def, 0, jkl, 1);
  350. var newRange = normalizeBody(range);
  351. // Make sure that newRange is measured relative to the text nodes,
  352. // not the DIV elements.
  353. goog.testing.dom.assertRangeEquals(
  354. def.firstChild, 0, jkl.firstChild, 3, newRange);
  355. }
  356. function testNormalizeOnVeryFragmentedDom() {
  357. var defText = $('def').firstChild;
  358. var jklText = $('jkl').firstChild;
  359. var range = goog.dom.Range.createFromNodes(defText, 1, jklText, 2);
  360. // Fragment the DOM a bunch.
  361. fragmentText(defText);
  362. fragmentText(jklText);
  363. var newRange = normalizeBody(range);
  364. // our old text nodes may not be valid anymore. find new ones.
  365. defText = $('def').firstChild;
  366. jklText = $('jkl').firstChild;
  367. goog.testing.dom.assertRangeEquals(defText, 1, jklText, 2, newRange);
  368. }
  369. function testNormalizeOnDivWithEmptyTextNodes() {
  370. var emptyDiv = $('normalizeTest-with-empty-text-nodes');
  371. // Append empty text nodes to the emptyDiv.
  372. var tnode1 = goog.dom.createTextNode('');
  373. var tnode2 = goog.dom.createTextNode('');
  374. var tnode3 = goog.dom.createTextNode('');
  375. goog.dom.appendChild(emptyDiv, tnode1);
  376. goog.dom.appendChild(emptyDiv, tnode2);
  377. goog.dom.appendChild(emptyDiv, tnode3);
  378. var range = goog.dom.Range.createFromNodes(emptyDiv, 1, emptyDiv, 2);
  379. // Cannot use document.body.normalize() as it fails to normalize the div
  380. // (in IE) if it has nothing but empty text nodes.
  381. var newRange = goog.editor.range.rangePreservingNormalize(emptyDiv, range);
  382. if (goog.userAgent.GECKO &&
  383. goog.string.compareVersions(goog.userAgent.VERSION, '1.9') == -1) {
  384. // In FF2, node.normalize() leaves an empty textNode in the div, unlike
  385. // other browsers where the div is left with no children.
  386. goog.testing.dom.assertRangeEquals(
  387. emptyDiv.firstChild, 0, emptyDiv.firstChild, 0, newRange);
  388. } else {
  389. goog.testing.dom.assertRangeEquals(emptyDiv, 0, emptyDiv, 0, newRange);
  390. }
  391. }
  392. function testRangeCreatedInVeryFragmentedDom() {
  393. var def = $('def');
  394. var defText = def.firstChild;
  395. var jkl = $('jkl');
  396. var jklText = jkl.firstChild;
  397. // Fragment the DOM a bunch.
  398. fragmentText(defText);
  399. fragmentText(jklText);
  400. // Notice that there are two empty text nodes at the beginning of each
  401. // fragmented node.
  402. var range = goog.dom.Range.createFromNodes(def, 3, jkl, 4);
  403. var newRange = normalizeBody(range);
  404. // our old text nodes may not be valid anymore. find new ones.
  405. defText = $('def').firstChild;
  406. jklText = $('jkl').firstChild;
  407. goog.testing.dom.assertRangeEquals(defText, 1, jklText, 2, newRange);
  408. }
  409. function testNormalizeInFragmentedDomWithPreviousSiblings() {
  410. var ghiText = $('def').nextSibling;
  411. var mnoText = $('jkl').nextSibling;
  412. var range = goog.dom.Range.createFromNodes(ghiText, 1, mnoText, 2);
  413. // Fragment the DOM a bunch.
  414. fragmentText($('def').previousSibling); // fragment abc
  415. fragmentText(ghiText);
  416. fragmentText(mnoText);
  417. var newRange = normalizeBody(range);
  418. // our old text nodes may not be valid anymore. find new ones.
  419. ghiText = $('def').nextSibling;
  420. mnoText = $('jkl').nextSibling;
  421. goog.testing.dom.assertRangeEquals(ghiText, 1, mnoText, 2, newRange);
  422. }
  423. function testRangeCreatedInFragmentedDomWithPreviousSiblings() {
  424. var def = $('def');
  425. var ghiText = $('def').nextSibling;
  426. var jkl = $('jkl');
  427. var mnoText = $('jkl').nextSibling;
  428. // Fragment the DOM a bunch.
  429. fragmentText($('def').previousSibling); // fragment abc
  430. fragmentText(ghiText);
  431. fragmentText(mnoText);
  432. // Notice that there are two empty text nodes at the beginning of each
  433. // fragmented node.
  434. var root = $('parentNode');
  435. var range = goog.dom.Range.createFromNodes(root, 9, root, 16);
  436. var newRange = normalizeBody(range);
  437. // our old text nodes may not be valid anymore. find new ones.
  438. ghiText = $('def').nextSibling;
  439. mnoText = $('jkl').nextSibling;
  440. goog.testing.dom.assertRangeEquals(ghiText, 1, mnoText, 2, newRange);
  441. }
  442. /**
  443. * Branched from the tests for goog.dom.SavedCaretRange.
  444. */
  445. function testSavedCaretRange() {
  446. var def = $('def-1');
  447. var jkl = $('jkl-1');
  448. var range =
  449. goog.dom.Range.createFromNodes(def.firstChild, 1, jkl.firstChild, 2);
  450. range.select();
  451. var saved = goog.editor.range.saveUsingNormalizedCarets(range);
  452. assertHTMLEquals(
  453. 'd<span id="' + saved.startCaretId_ + '"></span>ef', def.innerHTML);
  454. assertHTMLEquals(
  455. 'jk<span id="' + saved.endCaretId_ + '"></span>l', jkl.innerHTML);
  456. clearSelectionAndRestoreSaved(saved);
  457. var selection = goog.dom.Range.createFromWindow(window);
  458. def = $('def-1');
  459. jkl = $('jkl-1');
  460. assertHTMLEquals('def', def.innerHTML);
  461. assertHTMLEquals('jkl', jkl.innerHTML);
  462. // Check that everything was normalized ok.
  463. assertEquals(1, def.childNodes.length);
  464. assertEquals(1, jkl.childNodes.length);
  465. goog.testing.dom.assertRangeEquals(
  466. def.firstChild, 1, jkl.firstChild, 2, selection);
  467. }
  468. function testRangePreservingNormalize() {
  469. var parent = $('normalizeTest-4');
  470. var def = $('def-4');
  471. var jkl = $('jkl-4');
  472. fragmentText(def.firstChild);
  473. fragmentText(jkl.firstChild);
  474. var range = goog.dom.Range.createFromNodes(def, 3, jkl, 4);
  475. var oldRangeDescription = goog.testing.dom.exposeRange(range);
  476. range = goog.editor.range.rangePreservingNormalize(parent, range);
  477. // Check that everything was normalized ok.
  478. assertEquals(
  479. 'def should have 1 child; range is ' +
  480. goog.testing.dom.exposeRange(range) + ', range was ' +
  481. oldRangeDescription,
  482. 1, def.childNodes.length);
  483. assertEquals(
  484. 'jkl should have 1 child; range is ' +
  485. goog.testing.dom.exposeRange(range) + ', range was ' +
  486. oldRangeDescription,
  487. 1, jkl.childNodes.length);
  488. goog.testing.dom.assertRangeEquals(
  489. def.firstChild, 1, jkl.firstChild, 2, range);
  490. }
  491. function testRangePreservingNormalizeWhereEndNodePreviousSiblingIsSplit() {
  492. var parent = $('normalizeTest-with-br');
  493. var br = parent.childNodes[1];
  494. fragmentText(parent.firstChild);
  495. var range = goog.dom.Range.createFromNodes(parent, 3, br, 0);
  496. range = goog.editor.range.rangePreservingNormalize(parent, range);
  497. // Code used to throw an error here.
  498. assertEquals('parent should have 3 children', 3, parent.childNodes.length);
  499. goog.testing.dom.assertRangeEquals(parent.firstChild, 1, parent, 1, range);
  500. }
  501. function testRangePreservingNormalizeWhereStartNodePreviousSiblingIsSplit() {
  502. var parent = $('normalizeTest-with-br');
  503. var br = parent.childNodes[1];
  504. fragmentText(parent.firstChild);
  505. fragmentText(parent.lastChild);
  506. var range = goog.dom.Range.createFromNodes(br, 0, parent, 9);
  507. range = goog.editor.range.rangePreservingNormalize(parent, range);
  508. // Code used to throw an error here.
  509. assertEquals('parent should have 3 children', 3, parent.childNodes.length);
  510. goog.testing.dom.assertRangeEquals(parent, 1, parent.lastChild, 1, range);
  511. }
  512. function testSelectionPreservingNormalize1() {
  513. var parent = $('normalizeTest-2');
  514. var def = $('def-2');
  515. var jkl = $('jkl-2');
  516. fragmentText(def.firstChild);
  517. fragmentText(jkl.firstChild);
  518. goog.dom.Range.createFromNodes(def, 3, jkl, 4).select();
  519. assertFalse(goog.dom.Range.createFromWindow(window).isReversed());
  520. var oldRangeDescription =
  521. goog.testing.dom.exposeRange(goog.dom.Range.createFromWindow(window));
  522. goog.editor.range.selectionPreservingNormalize(parent);
  523. // Check that everything was normalized ok.
  524. var range = goog.dom.Range.createFromWindow(window);
  525. assertFalse(range.isReversed());
  526. assertEquals(
  527. 'def should have 1 child; range is ' +
  528. goog.testing.dom.exposeRange(range) + ', range was ' +
  529. oldRangeDescription,
  530. 1, def.childNodes.length);
  531. assertEquals(
  532. 'jkl should have 1 child; range is ' +
  533. goog.testing.dom.exposeRange(range) + ', range was ' +
  534. oldRangeDescription,
  535. 1, jkl.childNodes.length);
  536. goog.testing.dom.assertRangeEquals(
  537. def.firstChild, 1, jkl.firstChild, 2, range);
  538. }
  539. /**
  540. * Make sure that selectionPreservingNormalize doesn't explode with no
  541. * selection in the document.
  542. */
  543. function testSelectionPreservingNormalize2() {
  544. var parent = $('normalizeTest-3');
  545. var def = $('def-3');
  546. var jkl = $('jkl-3');
  547. def.firstChild.splitText(1);
  548. jkl.firstChild.splitText(2);
  549. goog.dom.Range.clearSelection(window);
  550. goog.editor.range.selectionPreservingNormalize(parent);
  551. // Check that everything was normalized ok.
  552. assertEquals(1, def.childNodes.length);
  553. assertEquals(1, jkl.childNodes.length);
  554. assertFalse(goog.dom.Range.hasSelection(window));
  555. }
  556. function testSelectionPreservingNormalize3() {
  557. if (goog.userAgent.EDGE_OR_IE) {
  558. return;
  559. }
  560. var parent = $('normalizeTest-2');
  561. var def = $('def-2');
  562. var jkl = $('jkl-2');
  563. fragmentText(def.firstChild);
  564. fragmentText(jkl.firstChild);
  565. goog.dom.Range.createFromNodes(jkl, 4, def, 3).select();
  566. assertTrue(goog.dom.Range.createFromWindow(window).isReversed());
  567. var oldRangeDescription =
  568. goog.testing.dom.exposeRange(goog.dom.Range.createFromWindow(window));
  569. goog.editor.range.selectionPreservingNormalize(parent);
  570. // Check that everything was normalized ok.
  571. var range = goog.dom.Range.createFromWindow(window);
  572. assertTrue(range.isReversed());
  573. assertEquals(
  574. 'def should have 1 child; range is ' +
  575. goog.testing.dom.exposeRange(range) + ', range was ' +
  576. oldRangeDescription,
  577. 1, def.childNodes.length);
  578. assertEquals(
  579. 'jkl should have 1 child; range is ' +
  580. goog.testing.dom.exposeRange(range) + ', range was ' +
  581. oldRangeDescription,
  582. 1, jkl.childNodes.length);
  583. goog.testing.dom.assertRangeEquals(
  584. def.firstChild, 1, jkl.firstChild, 2, range);
  585. }
  586. function testSelectionPreservingNormalizeAfterPlaceCursorNextTo() {
  587. var parent = $('normalizeTest-with-div');
  588. goog.editor.range.placeCursorNextTo(parent.firstChild);
  589. goog.editor.range.selectionPreservingNormalize(parent);
  590. // Code used to throw an exception here.
  591. }
  592. /** Normalize the body and return the normalized range. */
  593. function normalizeBody(range) {
  594. var rangeFactory = goog.editor.range.normalize(range);
  595. document.body.normalize();
  596. return rangeFactory();
  597. }
  598. /** Break a text node up into lots of little fragments. */
  599. function fragmentText(text) {
  600. // NOTE(nicksantos): For some reason, splitText makes IE deeply
  601. // unhappy to the point where normalize and other normal DOM operations
  602. // start failing. It's a useful test for Firefox though, because different
  603. // versions of FireFox handle empty text nodes differently.
  604. // See goog.editor.BrowserFeature.
  605. if (goog.userAgent.IE) {
  606. manualSplitText(text, 2);
  607. manualSplitText(text, 1);
  608. manualSplitText(text, 0);
  609. manualSplitText(text, 0);
  610. } else {
  611. text.splitText(2);
  612. text.splitText(1);
  613. text.splitText(0);
  614. text.splitText(0);
  615. }
  616. }
  617. /**
  618. * Clear the selection by re-parsing the DOM. Then restore the saved
  619. * selection.
  620. * @param {goog.dom.SavedRange} saved The saved range.
  621. */
  622. function clearSelectionAndRestoreSaved(saved) {
  623. goog.dom.Range.clearSelection(window);
  624. assertFalse(goog.dom.Range.hasSelection(window));
  625. saved.restore();
  626. assertTrue(goog.dom.Range.hasSelection(window));
  627. }
  628. function manualSplitText(node, pos) {
  629. var newNodeString = node.nodeValue.substr(pos);
  630. node.nodeValue = node.nodeValue.substr(0, pos);
  631. goog.dom.insertSiblingAfter(document.createTextNode(newNodeString), node);
  632. }
  633. function testSelectNodeStartSimple() {
  634. var div = $('parentNode');
  635. div.innerHTML = '<p>Cursor should go in here</p>';
  636. goog.editor.range.selectNodeStart(div);
  637. var range = goog.dom.Range.createFromWindow(window);
  638. // Gotta love browsers and their inconsistencies with selection
  639. // representations. What we are trying to achieve is that when we type
  640. // the text will go into the P node. In Gecko, the selection is at the start
  641. // of the text node, as you'd expect, but in pre-530 Webkit, it has been
  642. // normalized to the visible position of P:0.
  643. if (goog.userAgent.GECKO || goog.userAgent.IE || goog.userAgent.EDGE ||
  644. (goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('530'))) {
  645. goog.testing.dom.assertRangeEquals(
  646. div.firstChild.firstChild, 0, div.firstChild.firstChild, 0, range);
  647. } else {
  648. goog.testing.dom.assertRangeEquals(
  649. div.firstChild, 0, div.firstChild, 0, range);
  650. }
  651. }
  652. function testSelectNodeStartBr() {
  653. var div = $('parentNode');
  654. div.innerHTML = '<p><br>Cursor should go in here</p>';
  655. goog.editor.range.selectNodeStart(div);
  656. var range = goog.dom.Range.createFromWindow(window);
  657. // We have to skip the BR since Gecko can't render a cursor at a BR.
  658. goog.testing.dom.assertRangeEquals(
  659. div.firstChild, 0, div.firstChild, 0, range);
  660. }
  661. function testIsEditable() {
  662. var containerElement = document.getElementById('editableTest');
  663. // Find editable container element's index.
  664. var containerIndex = 0;
  665. var currentSibling = containerElement;
  666. while (currentSibling = currentSibling.previousSibling) {
  667. containerIndex++;
  668. }
  669. var editableContainer = goog.dom.Range.createFromNodes(
  670. containerElement.parentNode, containerIndex, containerElement.parentNode,
  671. containerIndex + 1);
  672. assertFalse(
  673. 'Range containing container element not considered editable',
  674. goog.editor.range.isEditable(editableContainer));
  675. var allEditableChildren = goog.dom.Range.createFromNodes(
  676. containerElement, 0, containerElement,
  677. containerElement.childNodes.length);
  678. assertTrue(
  679. 'Range of all of container element children considered editable',
  680. goog.editor.range.isEditable(allEditableChildren));
  681. var someEditableChildren =
  682. goog.dom.Range.createFromNodes(containerElement, 2, containerElement, 6);
  683. assertTrue(
  684. 'Range of some container element children considered editable',
  685. goog.editor.range.isEditable(someEditableChildren));
  686. var mixedEditableNonEditable = goog.dom.Range.createFromNodes(
  687. containerElement.previousSibling, 0, containerElement, 2);
  688. assertFalse(
  689. 'Range overlapping some content not considered editable',
  690. goog.editor.range.isEditable(mixedEditableNonEditable));
  691. }
  692. function testIntersectsTag() {
  693. var root = $('root');
  694. root.innerHTML =
  695. '<b>Bold</b><p><span><code>x</code></span></p><p>y</p><i>Italic</i>';
  696. // Select the whole thing.
  697. var range = goog.dom.Range.createFromNodeContents(root);
  698. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV));
  699. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.B));
  700. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.I));
  701. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE));
  702. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U));
  703. // Just select italic.
  704. range = goog.dom.Range.createFromNodes(root, 3, root, 4);
  705. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV));
  706. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.B));
  707. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.I));
  708. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE));
  709. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U));
  710. // Select "ld x y".
  711. range = goog.dom.Range.createFromNodes(
  712. root.firstChild.firstChild, 2, root.childNodes[2], 1);
  713. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV));
  714. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.B));
  715. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.I));
  716. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE));
  717. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U));
  718. // Select ol.
  719. range = goog.dom.Range.createFromNodes(
  720. root.firstChild.firstChild, 1, root.firstChild.firstChild, 3);
  721. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV));
  722. assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.B));
  723. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.I));
  724. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE));
  725. assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U));
  726. }
  727. function testNormalizeNode() {
  728. var div = goog.dom.createDom(goog.dom.TagName.DIV, null, 'a', 'b', 'c');
  729. assertEquals(3, div.childNodes.length);
  730. goog.editor.range.normalizeNode(div);
  731. assertEquals(1, div.childNodes.length);
  732. assertEquals('abc', div.firstChild.nodeValue);
  733. div = goog.dom.createDom(
  734. goog.dom.TagName.DIV, null,
  735. goog.dom.createDom(goog.dom.TagName.SPAN, null, '1', '2'),
  736. goog.dom.createTextNode(''), goog.dom.createDom(goog.dom.TagName.BR), 'b',
  737. 'c');
  738. assertEquals(5, div.childNodes.length);
  739. assertEquals(2, div.firstChild.childNodes.length);
  740. goog.editor.range.normalizeNode(div);
  741. if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(1.9) ||
  742. goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher(526)) {
  743. // Old Gecko and Webkit versions don't delete the empty node.
  744. assertEquals(4, div.childNodes.length);
  745. } else {
  746. assertEquals(3, div.childNodes.length);
  747. }
  748. assertEquals(1, div.firstChild.childNodes.length);
  749. assertEquals('12', div.firstChild.firstChild.nodeValue);
  750. assertEquals('bc', div.lastChild.nodeValue);
  751. assertEquals(
  752. String(goog.dom.TagName.BR), div.lastChild.previousSibling.tagName);
  753. }
  754. function testDeepestPoint() {
  755. var parent = $('parentNode');
  756. var def = $('def');
  757. assertEquals(def, parent.childNodes[1]);
  758. var deepestPoint = goog.editor.range.Point.createDeepestPoint;
  759. var defStartLeft = deepestPoint(parent, 1, true);
  760. assertPointEquals(
  761. def.previousSibling, def.previousSibling.nodeValue.length, defStartLeft);
  762. var defStartRight = deepestPoint(parent, 1, false);
  763. assertPointEquals(def.firstChild, 0, defStartRight);
  764. var defEndLeft = deepestPoint(parent, 2, true);
  765. assertPointEquals(
  766. def.firstChild, def.firstChild.nodeValue.length, defEndLeft);
  767. var defEndRight = deepestPoint(parent, 2, false);
  768. assertPointEquals(def.nextSibling, 0, defEndRight);
  769. }
  770. function assertPointEquals(node, offset, actualPoint) {
  771. assertEquals('Point has wrong node', node, actualPoint.node);
  772. assertEquals('Point has wrong offset', offset, actualPoint.offset);
  773. }