| // Copyright 2008 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide('goog.editor.rangeTest'); |
| goog.setTestOnly('goog.editor.rangeTest'); |
| |
| goog.require('goog.dom'); |
| goog.require('goog.dom.Range'); |
| goog.require('goog.dom.TagName'); |
| goog.require('goog.editor.range'); |
| goog.require('goog.editor.range.Point'); |
| goog.require('goog.string'); |
| goog.require('goog.testing.dom'); |
| goog.require('goog.testing.jsunit'); |
| goog.require('goog.userAgent'); |
| |
| var savedHtml; |
| var $; |
| |
| function setUpPage() { |
| $ = goog.dom.getElement; |
| } |
| |
| function setUp() { |
| savedHtml = $('root').innerHTML; |
| } |
| |
| function tearDown() { |
| $('root').innerHTML = savedHtml; |
| } |
| |
| function testNoNarrow() { |
| var def = $('def'); |
| var jkl = $('jkl'); |
| var range = goog.dom.Range.createFromNodes( |
| def.firstChild, 1, jkl.firstChild, 2); |
| |
| range = goog.editor.range.narrow(range, $('parentNode')); |
| goog.testing.dom.assertRangeEquals( |
| def.firstChild, 1, jkl.firstChild, 2, range); |
| } |
| |
| function testNarrowAtEndEdge() { |
| var def = $('def'); |
| var jkl = $('jkl'); |
| var range = goog.dom.Range.createFromNodes( |
| def.firstChild, 1, jkl.firstChild, 2); |
| |
| range = goog.editor.range.narrow(range, def); |
| goog.testing.dom.assertRangeEquals( |
| def.firstChild, 1, def.firstChild, 3, range); |
| } |
| |
| function testNarrowAtStartEdge() { |
| var def = $('def'); |
| var jkl = $('jkl'); |
| var range = goog.dom.Range.createFromNodes( |
| def.firstChild, 1, jkl.firstChild, 2); |
| |
| range = goog.editor.range.narrow(range, jkl); |
| |
| goog.testing.dom.assertRangeEquals( |
| jkl.firstChild, 0, jkl.firstChild, 2, range); |
| } |
| |
| function testNarrowOutsideElement() { |
| var def = $('def'); |
| var jkl = $('jkl'); |
| var range = goog.dom.Range.createFromNodes( |
| def.firstChild, 1, jkl.firstChild, 2); |
| |
| range = goog.editor.range.narrow(range, $('pqr')); |
| assertNull(range); |
| } |
| |
| function testNoExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<div>longword</div>'; |
| // Select "ongwo" and make sure we don't expand since this is not |
| // a full container. |
| var textNode = div.firstChild.firstChild; |
| var range = goog.dom.Range.createFromNodes(textNode, 1, textNode, 6); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(textNode, 1, textNode, 6, range); |
| } |
| |
| function testSimpleExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<div>longword</div>foo'; |
| // Select "longword" and make sure we do expand to include the div since |
| // the full container text is selected. |
| var textNode = div.firstChild.firstChild; |
| var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 8); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(div, 0, div, 1, range); |
| |
| // Select "foo" and make sure we expand out to the parent div. |
| var fooNode = div.lastChild; |
| range = goog.dom.Range.createFromNodes(fooNode, 0, fooNode, 3); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(div, 1, div, 2, range); |
| } |
| |
| function testDoubleExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<div><span>longword</span></div>foo'; |
| // Select "longword" and make sure we do expand to include the span |
| // and the div since both of their full contents are selected. |
| var textNode = div.firstChild.firstChild.firstChild; |
| var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 8); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(div, 0, div, 1, range); |
| |
| // Same visible position, different dom position. |
| // Start in text node, end in span. |
| range = goog.dom.Range.createFromNodes(textNode, 0, textNode.parentNode, 1); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(div, 0, div, 1, range); |
| } |
| |
| function testMultipleChildrenExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<ol><li>one</li><li>two</li><li>three</li></ol>'; |
| // Select "two" and make sure we expand to the li, but not the ol. |
| var li = div.firstChild.childNodes[1]; |
| var textNode = li.firstChild; |
| var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 3); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals( |
| li.parentNode, 1, li.parentNode, 2, range); |
| |
| // Make the same visible selection, only slightly different dom position. |
| // Select starting from the text node, but ending in the li. |
| range = goog.dom.Range.createFromNodes(textNode, 0, li, 1); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals( |
| li.parentNode, 1, li.parentNode, 2, range); |
| } |
| |
| function testSimpleDifferentContainersExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<ol><li>1</li><li><b>bold</b><i>italic</i></li></ol>'; |
| // Select all of "bold" and "italic" at the text node level, and |
| // make sure we expand to the li. |
| var li = div.firstChild.childNodes[1]; |
| var boldNode = li.childNodes[0]; |
| var italicNode = li.childNodes[1]; |
| var range = goog.dom.Range.createFromNodes(boldNode.firstChild, 0, |
| italicNode.firstChild, 6); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals( |
| li.parentNode, 1, li.parentNode, 2, range); |
| |
| // Make the same visible selection, only slightly different dom position. |
| // Select "bold" at the b node level and "italic" at the text node level. |
| range = goog.dom.Range.createFromNodes(boldNode, 0, |
| italicNode.firstChild, 6); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals( |
| li.parentNode, 1, li.parentNode, 2, range); |
| } |
| |
| function testSimpleDifferentContainersSmallExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<ol><li>1</li><li><b>bold</b><i>italic</i>' + |
| '<u>under</u></li></ol>'; |
| // Select all of "bold" and "italic", but we can't expand to the |
| // entire li since we didn't select "under". |
| var li = div.firstChild.childNodes[1]; |
| var boldNode = li.childNodes[0]; |
| var italicNode = li.childNodes[1]; |
| var range = goog.dom.Range.createFromNodes(boldNode.firstChild, 0, |
| italicNode.firstChild, 6); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(li, 0, li, 2, range); |
| |
| // Same visible position, different dom position. |
| // Select "bold" starting in text node, "italic" at i node. |
| range = goog.dom.Range.createFromNodes(boldNode.firstChild, 0, |
| italicNode, 1); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(li, 0, li, 2, range); |
| } |
| |
| function testEmbeddedDifferentContainersExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<div><b><i>italic</i>after</b><u>under</u></div>foo'; |
| // Select "italic" "after" "under", should expand all the way to parent. |
| var boldNode = div.firstChild.childNodes[0]; |
| var italicNode = boldNode.childNodes[0]; |
| var underNode = div.firstChild.childNodes[1]; |
| var range = goog.dom.Range.createFromNodes(italicNode.firstChild, 0, |
| underNode.firstChild, 5); |
| |
| range = goog.editor.range.expand(range); |
| goog.testing.dom.assertRangeEquals(div, 0, div, 1, range); |
| } |
| |
| function testReverseSimpleExpand() { |
| var div = $('parentNode'); |
| div.innerHTML = '<div>longword</div>foo'; |
| // Select "longword" and make sure we do expand to include the div since |
| // the full container text is selected. |
| var textNode = div.firstChild.firstChild; |
| var range = goog.dom.Range.createFromNodes(textNode, 8, textNode, 0); |
| |
| range = goog.editor.range.expand(range); |
| |
| goog.testing.dom.assertRangeEquals(div, 0, div, 1, range); |
| } |
| |
| function testExpandWithStopNode() { |
| var div = $('parentNode'); |
| div.innerHTML = '<div><span>word</span></div>foo'; |
| // Select "word". |
| var span = div.firstChild.firstChild; |
| var textNode = span.firstChild; |
| var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 4); |
| |
| range = goog.editor.range.expand(range); |
| |
| goog.testing.dom.assertRangeEquals(div, 0, div, 1, range); |
| |
| // Same selection, but force stop at the span. |
| range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 4); |
| |
| range = goog.editor.range.expand(range, span); |
| |
| goog.testing.dom.assertRangeEquals(span, 0, span, 1, range); |
| } |
| |
| // Ojan didn't believe this code worked, this was the case he |
| // thought was broken. Keeping just as a regression test. |
| function testOjanCase() { |
| var div = $('parentNode'); |
| div.innerHTML = '<em><i><b>foo</b>bar</i></em>'; |
| // Select "foo", at text node level. |
| var iNode = div.firstChild.firstChild; |
| var textNode = iNode.firstChild.firstChild; |
| var range = goog.dom.Range.createFromNodes(textNode, 0, textNode, 3); |
| |
| range = goog.editor.range.expand(range); |
| |
| goog.testing.dom.assertRangeEquals(iNode, 0, iNode, 1, range); |
| |
| // Same selection, at b node level. |
| range = goog.dom.Range.createFromNodes(iNode.firstChild, 0, |
| iNode.firstChild, 1); |
| range = goog.editor.range.expand(range); |
| |
| goog.testing.dom.assertRangeEquals(iNode, 0, iNode, 1, range); |
| } |
| |
| function testPlaceCursorNextToLeft() { |
| var div = $('parentNode'); |
| div.innerHTML = 'foo<div id="bar">bar</div>baz'; |
| var node = $('bar'); |
| var range = goog.editor.range.placeCursorNextTo(node, true); |
| |
| var expose = goog.testing.dom.exposeNode; |
| assertEquals('Selection should be to the left of the node ' + |
| expose(node) + ',' + expose(range.getStartNode().nextSibling), |
| node, range.getStartNode().nextSibling); |
| assertEquals('Selection should be collapsed', |
| true, range.isCollapsed()); |
| } |
| |
| |
| function testPlaceCursorNextToRight() { |
| var div = $('parentNode'); |
| div.innerHTML = 'foo<div id="bar">bar</div>baz'; |
| var node = $('bar'); |
| var range = goog.editor.range.placeCursorNextTo(node, false); |
| |
| assertEquals('Selection should be to the right of the node', |
| node, range.getStartNode().previousSibling); |
| assertEquals('Selection should be collapsed', |
| true, range.isCollapsed()); |
| } |
| |
| function testPlaceCursorNextTo_rightOfLineBreak() { |
| var div = $('parentNode'); |
| div.innerHTML = '<div contentEditable="true">hhhh<br />h</div>'; |
| var children = div.firstChild.childNodes; |
| assertEquals(3, children.length); |
| var node = children[1]; |
| var range = goog.editor.range.placeCursorNextTo(node, false); |
| assertEquals(node.nextSibling, range.getStartNode()); |
| } |
| |
| function testPlaceCursorNextTo_leftOfHr() { |
| var div = $('parentNode'); |
| div.innerHTML = '<hr />aaa'; |
| var children = div.childNodes; |
| assertEquals(2, children.length); |
| var node = children[0]; |
| var range = goog.editor.range.placeCursorNextTo(node, true); |
| |
| assertEquals(div, range.getStartNode()); |
| assertEquals(0, range.getStartOffset()); |
| } |
| |
| function testPlaceCursorNextTo_rightOfHr() { |
| var div = $('parentNode'); |
| div.innerHTML = 'aaa<hr>'; |
| var children = div.childNodes; |
| assertEquals(2, children.length); |
| var node = children[1]; |
| var range = goog.editor.range.placeCursorNextTo(node, false); |
| |
| assertEquals(div, range.getStartNode()); |
| assertEquals(2, range.getStartOffset()); |
| } |
| |
| function testPlaceCursorNextTo_rightOfImg() { |
| var div = $('parentNode'); |
| div.innerHTML = |
| 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">bbb'; |
| var children = div.childNodes; |
| assertEquals(3, children.length); |
| var imgNode = children[1]; |
| var range = goog.editor.range.placeCursorNextTo(imgNode, false); |
| |
| assertEquals('range node should be the right sibling of img tag', |
| children[2], range.getStartNode()); |
| assertEquals(0, range.getStartOffset()); |
| |
| } |
| |
| function testPlaceCursorNextTo_rightOfImgAtEnd() { |
| var div = $('parentNode'); |
| div.innerHTML = |
| 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">'; |
| var children = div.childNodes; |
| assertEquals(2, children.length); |
| var imgNode = children[1]; |
| var range = goog.editor.range.placeCursorNextTo(imgNode, false); |
| |
| assertEquals('range node should be the parent of img', |
| div, range.getStartNode()); |
| assertEquals('offset should be right after the img tag', |
| 2, range.getStartOffset()); |
| |
| } |
| |
| function testPlaceCursorNextTo_leftOfImg() { |
| var div = $('parentNode'); |
| div.innerHTML = |
| '<img src="https://www.google.com/images/srpr/logo3w.png">xxx'; |
| var children = div.childNodes; |
| assertEquals(2, children.length); |
| var imgNode = children[0]; |
| var range = goog.editor.range.placeCursorNextTo(imgNode, true); |
| |
| assertEquals('range node should be the parent of img', |
| div, range.getStartNode()); |
| assertEquals('offset should point to the img tag', |
| 0, range.getStartOffset()); |
| } |
| |
| function testPlaceCursorNextTo_rightOfFirstOfTwoImgTags() { |
| var div = $('parentNode'); |
| div.innerHTML = |
| 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">' + |
| '<img src="https://www.google.com/images/srpr/logo3w.png">'; |
| var children = div.childNodes; |
| assertEquals(3, children.length); |
| var imgNode = children[1]; // First of two IMG nodes |
| var range = goog.editor.range.placeCursorNextTo(imgNode, false); |
| |
| assertEquals('range node should be the parent of img instead of ' + |
| 'node with innerHTML=' + range.getStartNode().innerHTML, |
| div, range.getStartNode()); |
| assertEquals('offset should be right after the img tag', |
| 2, range.getStartOffset()); |
| } |
| |
| function testGetDeepEndPoint() { |
| var div = $('parentNode'); |
| var def = $('def'); |
| var jkl = $('jkl'); |
| |
| assertPointEquals(div.firstChild, 0, |
| goog.editor.range.getDeepEndPoint( |
| goog.dom.Range.createFromNodeContents(div), true)); |
| assertPointEquals(div.lastChild, div.lastChild.length, |
| goog.editor.range.getDeepEndPoint( |
| goog.dom.Range.createFromNodeContents(div), false)); |
| |
| assertPointEquals(def.firstChild, 0, |
| goog.editor.range.getDeepEndPoint( |
| goog.dom.Range.createCaret(div, 1), true)); |
| assertPointEquals(def.nextSibling, 0, |
| goog.editor.range.getDeepEndPoint( |
| goog.dom.Range.createCaret(div, 2), true)); |
| |
| } |
| |
| function testNormalizeOnNormalizedDom() { |
| var defText = $('def').firstChild; |
| var jklText = $('jkl').firstChild; |
| var range = goog.dom.Range.createFromNodes(defText, 1, jklText, 2); |
| |
| var newRange = normalizeBody(range); |
| goog.testing.dom.assertRangeEquals(defText, 1, jklText, 2, newRange); |
| } |
| |
| function testDeepPointFindingOnNormalizedDom() { |
| var def = $('def'); |
| var jkl = $('jkl'); |
| var range = goog.dom.Range.createFromNodes(def, 0, jkl, 1); |
| |
| var newRange = normalizeBody(range); |
| |
| // Make sure that newRange is measured relative to the text nodes, |
| // not the DIV elements. |
| goog.testing.dom.assertRangeEquals( |
| def.firstChild, 0, jkl.firstChild, 3, newRange); |
| } |
| |
| function testNormalizeOnVeryFragmentedDom() { |
| var defText = $('def').firstChild; |
| var jklText = $('jkl').firstChild; |
| var range = goog.dom.Range.createFromNodes(defText, 1, jklText, 2); |
| |
| // Fragment the DOM a bunch. |
| fragmentText(defText); |
| fragmentText(jklText); |
| |
| var newRange = normalizeBody(range); |
| |
| // our old text nodes may not be valid anymore. find new ones. |
| defText = $('def').firstChild; |
| jklText = $('jkl').firstChild; |
| |
| goog.testing.dom.assertRangeEquals(defText, 1, jklText, 2, newRange); |
| } |
| |
| function testNormalizeOnDivWithEmptyTextNodes() { |
| var emptyDiv = $('normalizeTest-with-empty-text-nodes'); |
| |
| // Append empty text nodes to the emptyDiv. |
| var tnode1 = goog.dom.createTextNode(''); |
| var tnode2 = goog.dom.createTextNode(''); |
| var tnode3 = goog.dom.createTextNode(''); |
| |
| goog.dom.appendChild(emptyDiv, tnode1); |
| goog.dom.appendChild(emptyDiv, tnode2); |
| goog.dom.appendChild(emptyDiv, tnode3); |
| |
| var range = goog.dom.Range.createFromNodes(emptyDiv, 1, emptyDiv, 2); |
| |
| // Cannot use document.body.normalize() as it fails to normalize the div |
| // (in IE) if it has nothing but empty text nodes. |
| var newRange = goog.editor.range.rangePreservingNormalize(emptyDiv, range); |
| |
| if (goog.userAgent.GECKO && |
| goog.string.compareVersions(goog.userAgent.VERSION, '1.9') == -1) { |
| // In FF2, node.normalize() leaves an empty textNode in the div, unlike |
| // other browsers where the div is left with no children. |
| goog.testing.dom.assertRangeEquals( |
| emptyDiv.firstChild, 0, emptyDiv.firstChild, 0, newRange); |
| } else { |
| goog.testing.dom.assertRangeEquals(emptyDiv, 0, emptyDiv, 0, newRange); |
| } |
| } |
| |
| function testRangeCreatedInVeryFragmentedDom() { |
| var def = $('def'); |
| var defText = def.firstChild; |
| var jkl = $('jkl'); |
| var jklText = jkl.firstChild; |
| |
| // Fragment the DOM a bunch. |
| fragmentText(defText); |
| fragmentText(jklText); |
| |
| // Notice that there are two empty text nodes at the beginning of each |
| // fragmented node. |
| var range = goog.dom.Range.createFromNodes(def, 3, jkl, 4); |
| |
| var newRange = normalizeBody(range); |
| |
| // our old text nodes may not be valid anymore. find new ones. |
| defText = $('def').firstChild; |
| jklText = $('jkl').firstChild; |
| goog.testing.dom.assertRangeEquals(defText, 1, jklText, 2, newRange); |
| } |
| |
| function testNormalizeInFragmentedDomWithPreviousSiblings() { |
| var ghiText = $('def').nextSibling; |
| var mnoText = $('jkl').nextSibling; |
| var range = goog.dom.Range.createFromNodes(ghiText, 1, mnoText, 2); |
| |
| // Fragment the DOM a bunch. |
| fragmentText($('def').previousSibling); // fragment abc |
| fragmentText(ghiText); |
| fragmentText(mnoText); |
| |
| var newRange = normalizeBody(range); |
| |
| // our old text nodes may not be valid anymore. find new ones. |
| ghiText = $('def').nextSibling; |
| mnoText = $('jkl').nextSibling; |
| |
| goog.testing.dom.assertRangeEquals(ghiText, 1, mnoText, 2, newRange); |
| } |
| |
| function testRangeCreatedInFragmentedDomWithPreviousSiblings() { |
| var def = $('def'); |
| var ghiText = $('def').nextSibling; |
| var jkl = $('jkl'); |
| var mnoText = $('jkl').nextSibling; |
| |
| // Fragment the DOM a bunch. |
| fragmentText($('def').previousSibling); // fragment abc |
| fragmentText(ghiText); |
| fragmentText(mnoText); |
| |
| // Notice that there are two empty text nodes at the beginning of each |
| // fragmented node. |
| var root = $('parentNode'); |
| var range = goog.dom.Range.createFromNodes(root, 9, root, 16); |
| |
| var newRange = normalizeBody(range); |
| |
| // our old text nodes may not be valid anymore. find new ones. |
| ghiText = $('def').nextSibling; |
| mnoText = $('jkl').nextSibling; |
| goog.testing.dom.assertRangeEquals(ghiText, 1, mnoText, 2, newRange); |
| } |
| |
| |
| /** |
| * Branched from the tests for goog.dom.SavedCaretRange. |
| */ |
| function testSavedCaretRange() { |
| var def = $('def-1'); |
| var jkl = $('jkl-1'); |
| |
| var range = goog.dom.Range.createFromNodes( |
| def.firstChild, 1, jkl.firstChild, 2); |
| range.select(); |
| |
| var saved = goog.editor.range.saveUsingNormalizedCarets(range); |
| assertHTMLEquals( |
| 'd<span id="' + saved.startCaretId_ + '"></span>ef', def.innerHTML); |
| assertHTMLEquals( |
| 'jk<span id="' + saved.endCaretId_ + '"></span>l', jkl.innerHTML); |
| |
| clearSelectionAndRestoreSaved(saved); |
| |
| var selection = goog.dom.Range.createFromWindow(window); |
| def = $('def-1'); |
| jkl = $('jkl-1'); |
| assertHTMLEquals('def', def.innerHTML); |
| assertHTMLEquals('jkl', jkl.innerHTML); |
| |
| // Check that everything was normalized ok. |
| assertEquals(1, def.childNodes.length); |
| assertEquals(1, jkl.childNodes.length); |
| goog.testing.dom.assertRangeEquals( |
| def.firstChild, 1, jkl.firstChild, 2, selection); |
| } |
| |
| function testRangePreservingNormalize() { |
| var parent = $('normalizeTest-4'); |
| var def = $('def-4'); |
| var jkl = $('jkl-4'); |
| fragmentText(def.firstChild); |
| fragmentText(jkl.firstChild); |
| |
| var range = goog.dom.Range.createFromNodes(def, 3, jkl, 4); |
| var oldRangeDescription = goog.testing.dom.exposeRange(range); |
| range = goog.editor.range.rangePreservingNormalize(parent, range); |
| |
| // Check that everything was normalized ok. |
| assertEquals('def should have 1 child; range is ' + |
| goog.testing.dom.exposeRange(range) + |
| ', range was ' + oldRangeDescription, |
| 1, def.childNodes.length); |
| assertEquals('jkl should have 1 child; range is ' + |
| goog.testing.dom.exposeRange(range) + |
| ', range was ' + oldRangeDescription, |
| 1, jkl.childNodes.length); |
| goog.testing.dom.assertRangeEquals(def.firstChild, 1, jkl.firstChild, 2, |
| range); |
| } |
| |
| function testRangePreservingNormalizeWhereEndNodePreviousSiblingIsSplit() { |
| var parent = $('normalizeTest-with-br'); |
| var br = parent.childNodes[1]; |
| fragmentText(parent.firstChild); |
| |
| var range = goog.dom.Range.createFromNodes(parent, 3, br, 0); |
| range = goog.editor.range.rangePreservingNormalize(parent, range); |
| |
| // Code used to throw an error here. |
| |
| assertEquals('parent should have 3 children', 3, parent.childNodes.length); |
| goog.testing.dom.assertRangeEquals(parent.firstChild, 1, parent, 1, range); |
| } |
| |
| function testRangePreservingNormalizeWhereStartNodePreviousSiblingIsSplit() { |
| var parent = $('normalizeTest-with-br'); |
| var br = parent.childNodes[1]; |
| fragmentText(parent.firstChild); |
| fragmentText(parent.lastChild); |
| |
| var range = goog.dom.Range.createFromNodes(br, 0, parent, 9); |
| range = goog.editor.range.rangePreservingNormalize(parent, range); |
| |
| // Code used to throw an error here. |
| |
| assertEquals('parent should have 3 children', 3, parent.childNodes.length); |
| goog.testing.dom.assertRangeEquals(parent, 1, parent.lastChild, 1, range); |
| } |
| |
| function testSelectionPreservingNormalize1() { |
| var parent = $('normalizeTest-2'); |
| var def = $('def-2'); |
| var jkl = $('jkl-2'); |
| fragmentText(def.firstChild); |
| fragmentText(jkl.firstChild); |
| |
| goog.dom.Range.createFromNodes(def, 3, jkl, 4).select(); |
| assertFalse(goog.dom.Range.createFromWindow(window).isReversed()); |
| |
| var oldRangeDescription = goog.testing.dom.exposeRange( |
| goog.dom.Range.createFromWindow(window)); |
| goog.editor.range.selectionPreservingNormalize(parent); |
| |
| // Check that everything was normalized ok. |
| var range = goog.dom.Range.createFromWindow(window); |
| assertFalse(range.isReversed()); |
| |
| assertEquals('def should have 1 child; range is ' + |
| goog.testing.dom.exposeRange(range) + |
| ', range was ' + oldRangeDescription, |
| 1, def.childNodes.length); |
| assertEquals('jkl should have 1 child; range is ' + |
| goog.testing.dom.exposeRange(range) + |
| ', range was ' + oldRangeDescription, |
| 1, jkl.childNodes.length); |
| goog.testing.dom.assertRangeEquals(def.firstChild, 1, jkl.firstChild, 2, |
| range); |
| } |
| |
| |
| /** |
| * Make sure that selectionPreservingNormalize doesn't explode with no |
| * selection in the document. |
| */ |
| function testSelectionPreservingNormalize2() { |
| var parent = $('normalizeTest-3'); |
| var def = $('def-3'); |
| var jkl = $('jkl-3'); |
| def.firstChild.splitText(1); |
| jkl.firstChild.splitText(2); |
| |
| goog.dom.Range.clearSelection(window); |
| goog.editor.range.selectionPreservingNormalize(parent); |
| |
| // Check that everything was normalized ok. |
| assertEquals(1, def.childNodes.length); |
| assertEquals(1, jkl.childNodes.length); |
| assertFalse(goog.dom.Range.hasSelection(window)); |
| } |
| |
| function testSelectionPreservingNormalize3() { |
| if (goog.userAgent.IE) { |
| return; |
| } |
| var parent = $('normalizeTest-2'); |
| var def = $('def-2'); |
| var jkl = $('jkl-2'); |
| fragmentText(def.firstChild); |
| fragmentText(jkl.firstChild); |
| |
| goog.dom.Range.createFromNodes(jkl, 4, def, 3).select(); |
| assertTrue(goog.dom.Range.createFromWindow(window).isReversed()); |
| |
| var oldRangeDescription = goog.testing.dom.exposeRange( |
| goog.dom.Range.createFromWindow(window)); |
| goog.editor.range.selectionPreservingNormalize(parent); |
| |
| // Check that everything was normalized ok. |
| var range = goog.dom.Range.createFromWindow(window); |
| assertTrue(range.isReversed()); |
| |
| assertEquals('def should have 1 child; range is ' + |
| goog.testing.dom.exposeRange(range) + |
| ', range was ' + oldRangeDescription, |
| 1, def.childNodes.length); |
| assertEquals('jkl should have 1 child; range is ' + |
| goog.testing.dom.exposeRange(range) + |
| ', range was ' + oldRangeDescription, |
| 1, jkl.childNodes.length); |
| goog.testing.dom.assertRangeEquals(def.firstChild, 1, jkl.firstChild, 2, |
| range); |
| } |
| |
| function testSelectionPreservingNormalizeAfterPlaceCursorNextTo() { |
| var parent = $('normalizeTest-with-div'); |
| goog.editor.range.placeCursorNextTo(parent.firstChild); |
| goog.editor.range.selectionPreservingNormalize(parent); |
| |
| // Code used to throw an exception here. |
| } |
| |
| |
| /** Normalize the body and return the normalized range. */ |
| function normalizeBody(range) { |
| var rangeFactory = goog.editor.range.normalize(range); |
| document.body.normalize(); |
| return rangeFactory(); |
| } |
| |
| |
| /** Break a text node up into lots of little fragments. */ |
| function fragmentText(text) { |
| // NOTE(nicksantos): For some reason, splitText makes IE deeply |
| // unhappy to the point where normalize and other normal DOM operations |
| // start failing. It's a useful test for Firefox though, because different |
| // versions of FireFox handle empty text nodes differently. |
| // See goog.editor.BrowserFeature. |
| if (goog.userAgent.IE) { |
| manualSplitText(text, 2); |
| manualSplitText(text, 1); |
| manualSplitText(text, 0); |
| manualSplitText(text, 0); |
| } else { |
| text.splitText(2); |
| text.splitText(1); |
| |
| text.splitText(0); |
| text.splitText(0); |
| } |
| } |
| |
| |
| /** |
| * Clear the selection by re-parsing the DOM. Then restore the saved |
| * selection. |
| * @param {goog.dom.SavedRange} saved The saved range. |
| */ |
| function clearSelectionAndRestoreSaved(saved) { |
| goog.dom.Range.clearSelection(window); |
| assertFalse(goog.dom.Range.hasSelection(window)); |
| saved.restore(); |
| assertTrue(goog.dom.Range.hasSelection(window)); |
| } |
| |
| function manualSplitText(node, pos) { |
| var newNodeString = node.nodeValue.substr(pos); |
| node.nodeValue = node.nodeValue.substr(0, pos); |
| goog.dom.insertSiblingAfter(document.createTextNode(newNodeString), node); |
| } |
| |
| function testSelectNodeStartSimple() { |
| var div = $('parentNode'); |
| div.innerHTML = '<p>Cursor should go in here</p>'; |
| |
| goog.editor.range.selectNodeStart(div); |
| var range = goog.dom.Range.createFromWindow(window); |
| // Gotta love browsers and their inconsistencies with selection |
| // representations. What we are trying to achieve is that when we type |
| // the text will go into the P node. In Gecko, the selection is at the start |
| // of the text node, as you'd expect, but in pre-530 Webkit, it has been |
| // normalized to the visible position of P:0. |
| if (goog.userAgent.GECKO || goog.userAgent.IE || |
| (goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('530'))) { |
| goog.testing.dom.assertRangeEquals(div.firstChild.firstChild, 0, |
| div.firstChild.firstChild, 0, range); |
| } else { |
| goog.testing.dom.assertRangeEquals(div.firstChild, 0, |
| div.firstChild, 0, range); |
| } |
| } |
| |
| function testSelectNodeStartBr() { |
| var div = $('parentNode'); |
| div.innerHTML = '<p><br>Cursor should go in here</p>'; |
| |
| goog.editor.range.selectNodeStart(div); |
| var range = goog.dom.Range.createFromWindow(window); |
| // We have to skip the BR since Gecko can't render a cursor at a BR. |
| goog.testing.dom.assertRangeEquals(div.firstChild, 0, |
| div.firstChild, 0, range); |
| } |
| |
| function testIsEditable() { |
| var containerElement = document.getElementById('editableTest'); |
| // Find editable container element's index. |
| var containerIndex = 0; |
| var currentSibling = containerElement; |
| while (currentSibling = currentSibling.previousSibling) { |
| containerIndex++; |
| } |
| |
| var editableContainer = goog.dom.Range.createFromNodes( |
| containerElement.parentNode, containerIndex, |
| containerElement.parentNode, containerIndex + 1); |
| assertFalse('Range containing container element not considered editable', |
| goog.editor.range.isEditable(editableContainer)); |
| |
| var allEditableChildren = goog.dom.Range.createFromNodes( |
| containerElement, 0, containerElement, |
| containerElement.childNodes.length); |
| assertTrue('Range of all of container element children considered editable', |
| goog.editor.range.isEditable(allEditableChildren)); |
| |
| var someEditableChildren = goog.dom.Range.createFromNodes( |
| containerElement, 2, containerElement, 6); |
| assertTrue('Range of some container element children considered editable', |
| goog.editor.range.isEditable(someEditableChildren)); |
| |
| |
| var mixedEditableNonEditable = goog.dom.Range.createFromNodes( |
| containerElement.previousSibling, 0, containerElement, 2); |
| assertFalse('Range overlapping some content not considered editable', |
| goog.editor.range.isEditable(mixedEditableNonEditable)); |
| } |
| |
| function testIntersectsTag() { |
| var root = $('root'); |
| root.innerHTML = |
| '<b>Bold</b><p><span><code>x</code></span></p><p>y</p><i>Italic</i>'; |
| |
| // Select the whole thing. |
| var range = goog.dom.Range.createFromNodeContents(root); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV)); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.B)); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.I)); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U)); |
| |
| // Just select italic. |
| range = goog.dom.Range.createFromNodes(root, 3, root, 4); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.B)); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.I)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U)); |
| |
| // Select "ld x y". |
| range = goog.dom.Range.createFromNodes(root.firstChild.firstChild, 2, |
| root.childNodes[2], 1); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV)); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.B)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.I)); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U)); |
| |
| // Select ol. |
| range = goog.dom.Range.createFromNodes(root.firstChild.firstChild, 1, |
| root.firstChild.firstChild, 3); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.DIV)); |
| assertTrue(goog.editor.range.intersectsTag(range, goog.dom.TagName.B)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.I)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.CODE)); |
| assertFalse(goog.editor.range.intersectsTag(range, goog.dom.TagName.U)); |
| } |
| |
| function testNormalizeNode() { |
| var div = goog.dom.createDom('DIV', null, 'a', 'b', 'c'); |
| assertEquals(3, div.childNodes.length); |
| goog.editor.range.normalizeNode(div); |
| assertEquals(1, div.childNodes.length); |
| assertEquals('abc', div.firstChild.nodeValue); |
| |
| div = goog.dom.createDom('DIV', null, |
| goog.dom.createDom('SPAN', null, '1', '2'), |
| goog.dom.createTextNode(''), |
| goog.dom.createDom('BR'), |
| 'b', |
| 'c'); |
| assertEquals(5, div.childNodes.length); |
| assertEquals(2, div.firstChild.childNodes.length); |
| goog.editor.range.normalizeNode(div); |
| if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(1.9) || |
| goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher(526)) { |
| // Old Gecko and Webkit versions don't delete the empty node. |
| assertEquals(4, div.childNodes.length); |
| } else { |
| assertEquals(3, div.childNodes.length); |
| } |
| assertEquals(1, div.firstChild.childNodes.length); |
| assertEquals('12', div.firstChild.firstChild.nodeValue); |
| assertEquals('bc', div.lastChild.nodeValue); |
| assertEquals('BR', div.lastChild.previousSibling.tagName); |
| } |
| |
| function testDeepestPoint() { |
| var parent = $('parentNode'); |
| var def = $('def'); |
| |
| assertEquals(def, parent.childNodes[1]); |
| |
| var deepestPoint = goog.editor.range.Point.createDeepestPoint; |
| |
| var defStartLeft = deepestPoint(parent, 1, true); |
| assertPointEquals(def.previousSibling, def.previousSibling.nodeValue.length, |
| defStartLeft); |
| |
| var defStartRight = deepestPoint(parent, 1, false); |
| assertPointEquals(def.firstChild, 0, defStartRight); |
| |
| var defEndLeft = deepestPoint(parent, 2, true); |
| assertPointEquals(def.firstChild, def.firstChild.nodeValue.length, |
| defEndLeft); |
| |
| var defEndRight = deepestPoint(parent, 2, false); |
| assertPointEquals(def.nextSibling, 0, defEndRight); |
| } |
| |
| function assertPointEquals(node, offset, actualPoint) { |
| assertEquals('Point has wrong node', node, actualPoint.node); |
| assertEquals('Point has wrong offset', offset, actualPoint.offset); |
| } |