| // 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.plugins.RemoveFormattingTest'); |
| goog.setTestOnly('goog.editor.plugins.RemoveFormattingTest'); |
| |
| goog.require('goog.dom'); |
| goog.require('goog.dom.Range'); |
| goog.require('goog.dom.TagName'); |
| goog.require('goog.editor.BrowserFeature'); |
| goog.require('goog.editor.plugins.RemoveFormatting'); |
| goog.require('goog.string'); |
| goog.require('goog.testing.ExpectedFailures'); |
| goog.require('goog.testing.dom'); |
| goog.require('goog.testing.editor.FieldMock'); |
| goog.require('goog.testing.editor.TestHelper'); |
| goog.require('goog.testing.jsunit'); |
| goog.require('goog.userAgent'); |
| |
| var SAVED_HTML; |
| var FIELDMOCK; |
| var FORMATTER; |
| var testHelper; |
| var WEBKIT_BEFORE_CHROME_8; |
| var WEBKIT_AFTER_CHROME_16; |
| var WEBKIT_AFTER_CHROME_21; |
| var insertImageBoldGarbage = ''; |
| var insertImageFontGarbage = ''; |
| var controlHtml; |
| var controlCleanHtml; |
| var expectedFailures; |
| |
| function setUpPage() { |
| WEBKIT_BEFORE_CHROME_8 = goog.userAgent.WEBKIT && |
| !goog.userAgent.isVersionOrHigher('534.10'); |
| |
| WEBKIT_AFTER_CHROME_16 = goog.userAgent.WEBKIT && |
| goog.userAgent.isVersionOrHigher('535.7'); |
| |
| WEBKIT_AFTER_CHROME_21 = goog.userAgent.WEBKIT && |
| goog.userAgent.isVersionOrHigher('537.1'); |
| // On Chrome 16, execCommand('insertImage') inserts a garbage BR |
| // after the image that we insert. We use this command to paste HTML |
| // in-place, because it has better paragraph-preserving semantics. |
| // |
| // TODO(nicksantos): Figure out if there are better chrome APIs that we |
| // should be using, or if insertImage should just be fixed. |
| if (WEBKIT_AFTER_CHROME_21) { |
| insertImageBoldGarbage = '<br>'; |
| insertImageFontGarbage = '<br>'; |
| } else if (WEBKIT_AFTER_CHROME_16) { |
| insertImageBoldGarbage = '<b><br/></b>'; |
| insertImageFontGarbage = '<font size="1"><br/></font>'; |
| } |
| // Extra html to add to test html to make sure removeformatting is actually |
| // getting called when you're testing if it leaves certain styles alone |
| // (instead of not even running at all due to some other bug). However, adding |
| // this extra text into the node to be selected screws up IE. |
| // (e.g. <a><img></a><b>t</b> --> <a></a><a><img></a>t ) |
| // TODO(user): Remove this special casing once http://b/3131117 is |
| // fixed. |
| controlHtml = goog.userAgent.IE ? '' : '<u>control</u>'; |
| controlCleanHtml = goog.userAgent.IE ? '' : 'control'; |
| expectedFailures = new goog.testing.ExpectedFailures(); |
| } |
| |
| function setUp() { |
| testHelper = new goog.testing.editor.TestHelper( |
| document.getElementById('html')); |
| testHelper.setUpEditableElement(); |
| |
| FIELDMOCK = new goog.testing.editor.FieldMock(); |
| FIELDMOCK.getElement(); |
| FIELDMOCK.$anyTimes(); |
| FIELDMOCK.$returns(document.getElementById('html')); |
| |
| FORMATTER = new goog.editor.plugins.RemoveFormatting(); |
| FORMATTER.fieldObject = FIELDMOCK; |
| |
| FIELDMOCK.$replay(); |
| } |
| |
| function tearDown() { |
| expectedFailures.handleTearDown(); |
| testHelper.tearDownEditableElement(); |
| } |
| |
| function setUpTableTests() { |
| var div = document.getElementById('html'); |
| div.innerHTML = '<table><tr> <th> head1</th><th id= "outerTh">' + |
| '<span id="emptyTh">head2</span></th> </tr><tr> <td> one </td> <td>' + |
| 'two </td> </tr><tr><td> three</td><td id="outerTd"> ' + |
| '<span id="emptyTd"><strong>four</strong></span></td></tr>' + |
| '<tr id="outerTr"><td><span id="emptyTr"> five </span></td></tr>' + |
| '<tr id="outerTr2"><td id="cell1"><b>seven</b></td><td id="cell2">' + |
| '<u>eight</u><span id="cellspan2"> foo</span></td></tr></table>'; |
| } |
| |
| function testTableTagsAreNotRemoved() { |
| setUpTableTests(); |
| var span; |
| |
| // TD |
| span = document.getElementById('emptyTd'); |
| goog.dom.Range.createFromNodeContents(span).select(); |
| FORMATTER.removeFormatting_(); |
| |
| var elem = document.getElementById('outerTd'); |
| assert('TD should not be removed', !!elem); |
| if (!goog.userAgent.WEBKIT) { |
| // webkit seems to have an Apple-style-span |
| assertEquals('TD should be clean', 'four', |
| goog.string.trim(elem.innerHTML)); |
| } |
| |
| // TR |
| span = document.getElementById('outerTr'); |
| goog.dom.Range.createFromNodeContents(span).select(); |
| FORMATTER.removeFormatting_(); |
| |
| var elem = document.getElementById('outerTr'); |
| assert('TR should not be removed', !!elem); |
| |
| // TH |
| span = document.getElementById('emptyTh'); |
| goog.dom.Range.createFromNodeContents(span).select(); |
| FORMATTER.removeFormatting_(); |
| |
| var elem = document.getElementById('outerTh'); |
| assert('TH should not be removed', !!elem); |
| if (!goog.userAgent.WEBKIT) { |
| // webkit seems to have an Apple-style-span |
| assertEquals('TH should be clean', 'head2', elem.innerHTML); |
| } |
| |
| } |
| |
| |
| /** |
| * We select two cells from the table and then make sure that there is no |
| * data loss and basic formatting is removed from each cell. |
| */ |
| function testTableDataIsNotRemoved() { |
| setUpTableTests(); |
| if (goog.userAgent.IE) { |
| // IE returns an "unspecified error" which seems to be beyond |
| // ExpectedFailures' ability to catch. |
| return; |
| } |
| |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'The content moves out of the table in WEBKIT.'); |
| |
| if (goog.userAgent.IE) { |
| // Not used since we bail out early for IE, but this is there so that |
| // developers can easily reproduce IE error. |
| goog.dom.Range.createFromNodeContents( |
| document.getElementById('outerTr2')).select(); |
| } else { |
| var selection = window.getSelection(); |
| if (selection.rangeCount > 0) selection.removeAllRanges(); |
| var range = document.createRange(); |
| range.selectNode(document.getElementById('cell1')); |
| selection.addRange(range); |
| range = document.createRange(); |
| range.selectNode(document.getElementById('cell2')); |
| selection.addRange(range); |
| } |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| |
| span = document.getElementById('outerTr2'); |
| assertEquals('Table data should not be removed', |
| '<td id="cell1">seven</td><td id="cell2">eight foo</td>', |
| span.innerHTML); |
| }); |
| } |
| |
| function testLinksAreNotRemoved() { |
| expectedFailures.expectFailureFor(WEBKIT_BEFORE_CHROME_8, |
| 'WebKit\'s removeFormatting command removes links.'); |
| |
| var anchor; |
| var div = document.getElementById('html'); |
| div.innerHTML = 'Foo<span id="link">Pre<a href="http://www.google.com">' + |
| 'Outside Span<span style="font-size:15pt">Inside Span' + |
| '</span></a></span>'; |
| |
| anchor = document.getElementById('link'); |
| goog.dom.Range.createFromNodeContents(anchor).select(); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('link should not be removed', |
| 'FooPre<a href="http://www.google.com/">Outside SpanInside Span</a>', |
| div.innerHTML); |
| }); |
| } |
| |
| |
| /** |
| * A short formatting removal function for use with the RemoveFormatting |
| * plugin. Does enough that we can tell this function was run over the |
| * document. |
| * @param {string} text The HTML in from the document. |
| * @return {string} The "cleaned" HTML out. |
| */ |
| function replacementFormattingFunc(text) { |
| // Really basic so that we can just see this is executing. |
| return text.replace(/Foo/gi, 'Bar').replace(/<[\/]*span[^>]*>/gi, ''); |
| } |
| |
| function testAlternateRemoveFormattingFunction() { |
| var div = document.getElementById('html'); |
| div.innerHTML = 'Start<span id="remFormat">Foo<pre>Bar</pre>Baz</span>'; |
| |
| FORMATTER.setRemoveFormattingFunc(replacementFormattingFunc); |
| var area = document.getElementById('remFormat'); |
| goog.dom.Range.createFromNodeContents(area).select(); |
| FORMATTER.removeFormatting_(); |
| // Webkit will change all tags to non-formatted ones anyway. |
| // Make sure 'Foo' was changed to 'Bar' |
| if (WEBKIT_BEFORE_CHROME_8) { |
| assertHTMLEquals('regular cleaner should not have run', |
| 'StartBar<br>Bar<br>Baz', |
| div.innerHTML); |
| } else { |
| assertHTMLEquals('regular cleaner should not have run', |
| 'StartBar<pre>Bar</pre>Baz', |
| div.innerHTML); |
| } |
| } |
| |
| function testGetValueForNode() { |
| // Override getValueForNode to keep bold tags. |
| var oldGetValue = |
| goog.editor.plugins.RemoveFormatting.prototype.getValueForNode; |
| goog.editor.plugins.RemoveFormatting.prototype.getValueForNode = |
| function(node) { |
| if (node.nodeName == goog.dom.TagName.B) { |
| return '<b>' + this.removeFormattingWorker_(node.innerHTML) + '</b>'; |
| } |
| return null; |
| }; |
| |
| var html = FORMATTER.removeFormattingWorker_('<div>foo<b>bar</b></div>'); |
| assertHTMLEquals('B tags should remain', 'foo<b>bar</b>', html); |
| |
| // Override getValueForNode to throw out bold tags, and their contents. |
| goog.editor.plugins.RemoveFormatting.prototype.getValueForNode = |
| function(node) { |
| if (node.nodeName == goog.dom.TagName.B) { |
| return ''; |
| } |
| return null; |
| }; |
| |
| html = FORMATTER.removeFormattingWorker_('<div>foo<b>bar</b></div>'); |
| assertHTMLEquals('B tag and its contents should be removed', 'foo', html); |
| |
| FIELDMOCK.$verify(); |
| goog.editor.plugins.RemoveFormatting.prototype.getValueForNode = |
| oldGetValue; |
| } |
| |
| function testRemoveFormattingAddsNoNbsps() { |
| var div = document.getElementById('html'); |
| div.innerHTML = '"<span id="toStrip">Twin <b>Cinema</b></span>"'; |
| |
| var span = document.getElementById('toStrip'); |
| goog.dom.Range.createFromNodeContents(span).select(); |
| |
| FORMATTER.removeFormatting_(); |
| |
| assertEquals('Text should be the same, with no non-breaking spaces', |
| '"Twin Cinema"', div.innerHTML); |
| |
| FIELDMOCK.$verify(); |
| } |
| |
| |
| /** |
| * @bug 992795 |
| */ |
| function testRemoveFormattingNestedDivs() { |
| var html = FORMATTER.removeFormattingWorker_( |
| '<div>1</div><div><div>2</div></div>'); |
| |
| goog.testing.dom.assertHtmlMatches('1<br>2', html); |
| } |
| |
| |
| /** |
| * Test that when we perform remove formatting on an entire table, |
| * that the visual look is similiar to as if there was a table there. |
| */ |
| function testRemoveFormattingForTableFormatting() { |
| // We preserve the table formatting as much as possible. |
| // Spaces separate TD's, <br>'s separate TR's. |
| // <br>'s separate the start and end of a table. |
| var html = '<table><tr><td>cell00</td><td>cell01</td></tr>' + |
| '<tr><td>cell10</td><td>cell11</td></tr></table>'; |
| html = FORMATTER.removeFormattingWorker_(html); |
| assertHTMLEquals('<br>cell00 cell01<br>cell10 cell11<br>', html); |
| } |
| |
| |
| /** |
| * @bug 1319715 |
| */ |
| function testRemoveFormattingDoesNotShrinkSelection() { |
| var div = document.getElementById('html'); |
| div.innerHTML = '<div>l </div><div><br><b>a</b>foo bar</div>'; |
| var div2 = div.lastChild; |
| |
| goog.dom.Range.createFromNodes(div2.firstChild, 0, |
| div2.lastChild, 7).select(); |
| |
| FORMATTER.removeFormatting_(); |
| |
| var range = goog.dom.Range.createFromWindow(); |
| assertEquals('Correct text should be selected', 'afoo bar', |
| range.getText()); |
| |
| // We have to trim out the leading BR in IE due to execCommand issues, |
| // so it isn't sent off to the removeFormattingWorker. |
| // Workaround for broken removeFormat in old webkit added an extra |
| // <br> to the end of the html. |
| var html = '<div>l </div><br class="GECKO WEBKIT">afoo bar' + |
| (goog.editor.BrowserFeature.ADDS_NBSPS_IN_REMOVE_FORMAT ? '<br>' : ''); |
| |
| goog.testing.dom.assertHtmlContentsMatch(html, div); |
| FIELDMOCK.$verify(); |
| } |
| |
| |
| /** |
| * @bug 1447374 |
| */ |
| function testInsideListRemoveFormat() { |
| var div = document.getElementById('html'); |
| div.innerHTML = '<ul><li>one</li><li><b>two</b></li><li>three</li></ul>'; |
| |
| var twoLi = div.firstChild.childNodes[1]; |
| goog.dom.Range.createFromNodeContents(twoLi).select(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'IE adds the "two" to the "three" li, and leaves empty B tags.'); |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'WebKit leave the "two" orphaned outside of an li but ' + |
| 'inside the ul (invalid HTML).'); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| // Test that we split the list. |
| assertHTMLEquals('<ul><li>one</li></ul><br>two<ul><li>three</li></ul>', |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testFullListRemoveFormat() { |
| var div = document.getElementById('html'); |
| div.innerHTML = |
| '<ul><li>one</li><li><b>two</b></li><li>three</li></ul>after'; |
| |
| goog.dom.Range.createFromNodeContents(div.firstChild).select(); |
| |
| // Note: This may just be a createFromNodeContents issue, as |
| // I can't ever make this happen with real user selection. |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'IE combines everything into a single LI and leaves the UL.'); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| // Test that we completely remove the list. |
| assertHTMLEquals('<br>one<br>two<br>threeafter', |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| |
| /** |
| * @bug 1440935 |
| */ |
| function testPartialListRemoveFormat() { |
| var div = document.getElementById('html'); |
| div.innerHTML = |
| '<ul><li>one</li><li>two</li><li>three</li></ul>after'; |
| |
| // Select "two three after". |
| goog.dom.Range.createFromNodes(div.firstChild.childNodes[1], 0, |
| div.lastChild, 5).select(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'IE leaves behind an empty LI.'); |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'WebKit completely loses the "one".'); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| // Test that we leave the list start alone. |
| assertHTMLEquals('<ul><li>one</li></ul><br>two<br>threeafter', |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testBasicRemoveFormatting() { |
| // IE will clobber the editable div. |
| // Note: I can't repro this using normal user selections. |
| if (goog.userAgent.IE) { |
| return; |
| } |
| var div = document.getElementById('html'); |
| div.innerHTML = '<b>bold<i>italic</i></b>'; |
| |
| goog.dom.Range.createFromNodeContents(div).select(); |
| |
| expectedFailures.expectFailureFor( |
| goog.editor.BrowserFeature.ADDS_NBSPS_IN_REMOVE_FORMAT, |
| 'The workaround for the nbsp bug adds an extra br at the end.'); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('bolditalic' + insertImageBoldGarbage, |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| |
| /** |
| * @bug 1480260 |
| */ |
| function testPartialBasicRemoveFormatting() { |
| var div = document.getElementById('html'); |
| div.innerHTML = '<b>bold<i>italic</i></b>'; |
| |
| goog.dom.Range.createFromNodes(div.firstChild.firstChild, 2, |
| div.firstChild.lastChild.firstChild, 3).select(); |
| |
| expectedFailures.expectFailureFor(WEBKIT_BEFORE_CHROME_8, |
| 'WebKit just gets this all wrong. Everything stays bold and ' + |
| '"lditalic" gets italicised.'); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('<b>bo</b>ldita<b><i>lic</i></b>', |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| |
| /** |
| * @bug 3075557 |
| */ |
| function testRemoveFormattingLinkedImageBorderZero() { |
| var testHtml = '<a href="http://www.google.com/">' + |
| '<img src="http://www.google.com/images/logo.gif" border="0"></a>'; |
| var div = document.getElementById('html'); |
| div.innerHTML = testHtml + controlHtml; |
| goog.dom.Range.createFromNodeContents(div).select(); |
| FORMATTER.removeFormatting_(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'WebKit removes the image entirely, see ' + |
| 'https://bugs.webkit.org/show_bug.cgi?id=13125 .'); |
| |
| expectedFailures.run(function() { |
| assertHTMLEquals( |
| 'Image\'s border=0 should not be removed during remove formatting', |
| testHtml + controlCleanHtml, div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| |
| /** |
| * @bug 3075557 |
| */ |
| function testRemoveFormattingLinkedImageBorderNonzero() { |
| var testHtml = '<a href="http://www.google.com/">' + |
| '<img src="http://www.google.com/images/logo.gif" border="1"></a>'; |
| var div = document.getElementById('html'); |
| div.innerHTML = testHtml + controlHtml; |
| goog.dom.Range.createFromNodeContents(div).select(); |
| FORMATTER.removeFormatting_(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'WebKit removes the image entirely, see ' + |
| 'https://bugs.webkit.org/show_bug.cgi?id=13125 .'); |
| |
| expectedFailures.run(function() { |
| assertHTMLEquals( |
| 'Image\'s border should be removed during remove formatting' + |
| ' if non-zero', |
| testHtml.replace(' border="1"', '') + controlCleanHtml, |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| |
| /** |
| * @bug 3075557 |
| */ |
| function testRemoveFormattingUnlinkedImage() { |
| var testHtml = |
| '<img src="http://www.google.com/images/logo.gif" border="0">'; |
| var div = document.getElementById('html'); |
| div.innerHTML = testHtml + controlHtml; |
| goog.dom.Range.createFromNodeContents(div).select(); |
| FORMATTER.removeFormatting_(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'WebKit removes the image entirely, see ' + |
| 'https://bugs.webkit.org/show_bug.cgi?id=13125 .'); |
| |
| expectedFailures.run(function() { |
| assertHTMLEquals( |
| 'Image\'s border=0 should not be removed during remove formatting' + |
| ' even if not wrapped by a link', |
| testHtml + controlCleanHtml, div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| |
| /** |
| * @bug 3075557 |
| */ |
| function testRemoveFormattingLinkedImageDeep() { |
| var testHtml = '<a href="http://www.google.com/"><b>hello' + |
| '<img src="http://www.google.com/images/logo.gif" border="0">' + |
| 'world</b></a>'; |
| var div = document.getElementById('html'); |
| div.innerHTML = testHtml + controlHtml; |
| goog.dom.Range.createFromNodeContents(div).select(); |
| FORMATTER.removeFormatting_(); |
| |
| expectedFailures.expectFailureFor(WEBKIT_BEFORE_CHROME_8, |
| 'WebKit removes the image entirely, see ' + |
| 'https://bugs.webkit.org/show_bug.cgi?id=13125 .'); |
| |
| expectedFailures.run(function() { |
| assertHTMLEquals( |
| 'Image\'s border=0 should not be removed during remove formatting' + |
| ' even if deep inside anchor tag', |
| testHtml.replace(/<\/?b>/g, '') + |
| controlCleanHtml + insertImageBoldGarbage, |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testFullTableRemoveFormatting() { |
| // Something goes horrible wrong in case 1 below. It was crashing all |
| // WebKit browsers, and now seems to be giving errors as it is trying |
| // to perform remove formatting on the little expected failures window |
| // instead of the dom we select. WTF. Since I'm gutting this code, |
| // I'm not going to look into this anymore right now. For what its worth, |
| // I can't repro any issues in standalone TrogEdit. |
| if (goog.userAgent.WEBKIT) { |
| return; |
| } |
| |
| var div = document.getElementById('html'); |
| |
| // WebKit has an extra BR in case 2. |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'IE clobbers the editable node in case 2 (can\'t repro with real ' + |
| 'user selections). IE doesn\'t remove the table in case 1.'); |
| |
| expectedFailures.run(function() { |
| |
| // When a full table is selected, we remove it completely. |
| div.innerHTML = 'foo<table><tr><td>bar</td></tr></table>baz1'; |
| goog.dom.Range.createFromNodeContents(div.childNodes[1]).select(); |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('foo<br>bar<br>baz1', div.innerHTML); |
| FIELDMOCK.$verify(); |
| |
| // Remove the full table when it is selected with additional |
| // contents too. |
| div.innerHTML = 'foo<table><tr><td>bar</td></tr></table>baz2'; |
| goog.dom.Range.createFromNodes(div.firstChild, 0, |
| div.lastChild, 1).select(); |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('foo<br>bar<br>baz2', div.innerHTML); |
| FIELDMOCK.$verify(); |
| |
| // We should still remove the table, even if the selection is inside the |
| // table and it is fully selected. |
| div.innerHTML = 'foo<table><tr><td id=\'td\'>bar</td></tr></table>baz3'; |
| goog.dom.Range.createFromNodeContents( |
| goog.dom.getElement('td').firstChild).select(); |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('foo<br>bar<br>baz3', div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testInsideTableRemoveFormatting() { |
| var div = document.getElementById('html'); |
| div.innerHTML = |
| '<table><tr><td><b id="b">foo</b></td></tr><tr><td>ba</td></tr></table>'; |
| |
| goog.dom.Range.createFromNodeContents(goog.dom.getElement('b')).select(); |
| |
| // Webkit adds some apple style span crap during execCommand("removeFormat") |
| // Our workaround for the nbsp bug removes these, but causes worse problems. |
| // See bugs.webkit.org/show_bug.cgi?id=29164 for more details. |
| expectedFailures.expectFailureFor( |
| WEBKIT_BEFORE_CHROME_8 && |
| !goog.editor.BrowserFeature.ADDS_NBSPS_IN_REMOVE_FORMAT, |
| 'Extra apple-style-spans'); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| |
| // Only remove styling from inside tables. |
| assertHTMLEquals( |
| '<table><tr><td>foo' + insertImageBoldGarbage + |
| '</td></tr><tr><td>ba</td></tr></table>', |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| |
| } |
| |
| function testPartialTableRemoveFormatting() { |
| if (goog.userAgent.IE) { |
| // IE returns an "unspecified error" which seems to be beyond |
| // ExpectedFailures' ability to catch. |
| return; |
| } |
| |
| var div = document.getElementById('html'); |
| div.innerHTML = 'bar<table><tr><td><b id="b">foo</b></td></tr>' + |
| '<tr><td><i>banana</i></td></tr></table><div id="baz">' + |
| 'baz</div>'; |
| |
| // Select from the "oo" inside the b tag to the end of "baz". |
| goog.dom.Range.createFromNodes(goog.dom.getElement('b').firstChild, 1, |
| goog.dom.getElement('baz').firstChild, 3).select(); |
| |
| // All browsers currently clobber the table cells that are selected. |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| // Only remove styling from inside tables. |
| assertHTMLEquals('bar<table><tr><td><b id="b">f</b>oo</td></tr>' + |
| '<tr><td>banana</td></tr></table>baz', div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| // Runs tests knowing some browsers will fail, because the new |
| // table functionality hasn't been implemented in them yet. |
| function runExpectingFailuresForUnimplementedBrowsers(func) { |
| if (goog.userAgent.IE) { |
| // IE returns an "unspecified error" which seems to be beyond |
| // ExpectedFailures' ability to catch. |
| return; |
| } |
| |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'Proper behavior not yet implemented for IE.'); |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'Proper behavior not yet implemented for WebKit.'); |
| |
| expectedFailures.run(func); |
| } |
| |
| |
| function testTwoTablesSelectedFullyRemoveFormatting() { |
| runExpectingFailuresForUnimplementedBrowsers(function() { |
| var div = document.getElementById('html'); |
| // When two tables are fully selected, we remove them completely. |
| div.innerHTML = '<table><tr><td>foo</td></tr></table>' + |
| '<table><tr><td>bar</td></tr></table>'; |
| goog.dom.Range.createFromNodes(div.firstChild, 0, |
| div.lastChild, 1).select(); |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('<br>foo<br><br>bar<br>', div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testTwoTablesSelectedFullyInsideRemoveFormatting() { |
| if (goog.userAgent.WEBKIT) { |
| // Something goes very wrong here, but it did before |
| // Julie started writing v2. Will address when converting |
| // safari to v2. |
| return; |
| } |
| |
| runExpectingFailuresForUnimplementedBrowsers(function() { |
| var div = document.getElementById('html'); |
| // When two tables are selected from inside but fully, |
| // also remove them completely. |
| div.innerHTML = '<table><tr><td id="td1">foo</td></tr></table>' + |
| '<table><tr><td id="td2">bar</td></tr></table>'; |
| goog.dom.Range.createFromNodes(goog.dom.getElement('td1').firstChild, 0, |
| goog.dom.getElement('td2').firstChild, 3).select(); |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('<br>foo<br><br>bar<br>', div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testTwoTablesSelectedFullyAndPartiallyRemoveFormatting() { |
| runExpectingFailuresForUnimplementedBrowsers(function() { |
| var div = document.getElementById('html'); |
| // Two tables selected, one fully, one partially. Remove |
| // only the fully selected one and remove styles only from |
| // partially selected one. |
| div.innerHTML = '<table><tr><td id="td1">foo</td></tr></table>' + |
| '<table><tr><td id="td2"><b>bar<b></td></tr></table>'; |
| goog.dom.Range.createFromNodes(goog.dom.getElement('td1').firstChild, 0, |
| goog.dom.getElement('td2').firstChild.firstChild, 2).select(); |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('<br>foo<br>' + |
| '<table><tr><td id="td2">ba<b>r</b></td></tr></table>', |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testTwoTablesSelectedPartiallyRemoveFormatting() { |
| runExpectingFailuresForUnimplementedBrowsers(function() { |
| var div = document.getElementById('html'); |
| // Two tables selected, both partially. Don't remove tables, |
| // but remove styles. |
| div.innerHTML = '<table><tr><td id="td1">f<i>o</i>o</td></tr></table>' + |
| '<table><tr><td id="td2">b<b>a</b>r</td></tr></table>'; |
| goog.dom.Range.createFromNodes(goog.dom.getElement('td1').firstChild, 1, |
| goog.dom.getElement('td2').childNodes[1], 1).select(); |
| FORMATTER.removeFormatting_(); |
| assertHTMLEquals('<table><tr><td id="td1">foo</td></tr></table>' + |
| '<table><tr><td id="td2">bar</td></tr></table>', |
| div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| |
| /** |
| * Test a random snippet from Google News (Google News has complicated |
| * dom structure, including tables, links, images, etc). |
| */ |
| function testRandomGoogleNewsSnippetRemoveFormatting() { |
| if (goog.userAgent.IE) { |
| // IE returns an "unspecified error" which seems to be beyond |
| // ExpectedFailures' ability to catch. |
| return; |
| } |
| |
| var div = document.getElementById('html'); |
| div.innerHTML = |
| '<font size="-3"><br></font><table align="right" border="0" ' + |
| 'cellpadding="0" cellspacing="0"><tbody><tr><td style="padding-left:' + |
| '6px;" valign="top" width="80" align="center"><a href="http://www.wash' + |
| 'ingtonpost.com/wp-dyn/content/article/2008/11/11/AR2008111101090.htm' + |
| 'l" + id="s-skHRvWH7ryqkcA4caGv0QQ:u-AFQjCNG3vx1HJOxKxMQPzCvYOVRE0JUDe' + |
| 'Q:r-1-0i_1268233361_6_H0_MH20_PL60"><img src="http://news.google.com/' + |
| 'news?imgefp=4LFiNNP62TgJ&imgurl=media3.washingtonpost.com/wp-dyn/' + |
| 'content/photo/2008/11/11/PH2008111101091.jpg" alt="" width="60" ' + |
| 'border="1" height="80"><br><font size="-2">Washington Post</font></a>' + |
| '</td></tr></tbody></table><a href="http://www.nme.com/news/britney-' + |
| 'spears/40995" id="s-xZUO-t0c1IpsVjyJj0rgxw:u-AFQjCNEZAMQCseEW6uTgXI' + |
| 'iPvAMHe_0B4A:r-1-0_1268233361_6_H0_MH20_PL60"><b>Britney\'s son ' + |
| 'released from hospital</b></a><br><font size="-1"><b><font color=' + |
| '"#6f6f6f">NME.com -</font> <nobr>53 minutes ago</nobr></b>' + |
| '</font><br><font size="-1">Britney Spears� youngest son Jayden James ' + |
| 'has been released from hospital, having been admitted on Sunday after' + |
| ' suffering a severe reaction to something he ingested.</font><br><fon' + |
| 'tsize="-1"><a href="http://www.celebrity-gossip.net/celebrities/holly' + |
| 'wood/britney-and-jamie-lynn-spears-alligator-alley-208944/" id="s-nM' + |
| 'PzHclcMG0J2WZkw9gnVQ:u-AFQjCNHal08usOQ5e5CAQsck2yGsTYeGVQ">Britney ' + |
| 'and Jamie Lynn Spears: Alligator Alley!</a> <font size="-1" color=' + |
| '"#6f6f6f"><nobr>The Gossip Girls</nobr></font></font><br><font size=' + |
| '"-1"><a href="http://foodconsumer.org/7777/8888/Other_N_ews_51/111101' + |
| '362008_Allergy_incident_could_spell_custody_trouble_for_Britney_Spear' + |
| 's.shtml" id="s-2lMNDY4joOprVvkkY_b-6A:u-AFQjCNGAeFNutMEbSg5zAvrh5reBF' + |
| 'lqUmA">Allergy incident could spell trouble for Britney Spears</a> ' + |
| '<font size="-1" color="#6f6f6f"><nobr>Food Consumer</nobr></font>' + |
| '</font><br><font class="p" size="-1"><a href="http://www.people.com/' + |
| 'people/article/0,,20239458,00.html" id="s-x9thwVUYVET0ZJOnkkcsjw:u-A' + |
| 'FQjCNE99eijVIrezr9AFRjLkmo5j_Jr7A"><nobr>People Magazine</nobr></a>&nb' + |
| 'sp;- <a href="http://www.eonline.com/uberblog/b68226_hospital_run_cou' + |
| 'ld_cost_britney_custody.html" id="s-kYt5LHDhlDnhUL9kRLuuwA:u-AFQjCNF8' + |
| '8eOy2utriYuF0icNrZQPzwK8gg"><nobr>E! Online</nobr></a> - <a href' + |
| '="http://justjared.buzznet.com/2008/11/11/britney-spears-alligator-fa' + |
| 'rm/" id="s--VDy1fyacNvaRo_aXb02Dw:u-AFQjCNEn0Rz3wg0PMwDdzKTDug-9k5W6y' + |
| 'g"><nobr>Just Jared</nobr></a> - <a href="http://www.efluxmedia.' + |
| 'com/news_Britney_Spears_Son_Released_from_Hospital_28696.html" id="s-' + |
| '8oX6hVDe4Qbcl1x5Rua_EA:u-AFQjCNEpn3nOHA8EB0pxJAPf6diOicMRDg"><nobr>eF' + |
| 'luxMedia</nobr></a></font><br><font class="p" size="-1"><a class="p" ' + |
| 'href="http://news.google.com/news?ncl=1268233361&hl=en"><nobr><b>' + |
| 'all 950 news articles �</b></nobr></a></font>'; |
| // Select it all. |
| goog.dom.Range.createFromNodeContents(div).select(); |
| |
| expectedFailures.expectFailureFor(WEBKIT_BEFORE_CHROME_8, |
| 'WebKit barfs apple-style-spans all over the place, and removes links.'); |
| |
| expectedFailures.run(function() { |
| FORMATTER.removeFormatting_(); |
| // Leave links and images alone, remove all other formatting. |
| assertHTMLEquals('<br><br><a href="http://www.washingtonpost.com/wp-dyn/' + |
| 'content/article/2008/11/11/AR2008111101090.html"><img src="http://n' + |
| 'ews.google.com/news?imgefp=4LFiNNP62TgJ&imgurl=media3.washingto' + |
| 'npost.com/wp-dyn/content/photo/2008/11/11/PH2008111101091.jpg"><br>' + |
| 'Washington Post</a><br><a href="http://www.nme.com/news/britney-spe' + |
| 'ars/40995">Britney\'s son released from hospital</a><br>NME.com - 5' + |
| '3 minutes ago<br>Britney Spears� youngest son Jayden James has been' + |
| ' released from hospital, having been admitted on Sunday after suffe' + |
| 'ring a severe reaction to something he ingested.<br><a href="http:/' + |
| '/www.celebrity-gossip.net/celebrities/hollywood/britney-and-jamie-l' + |
| 'ynn-spears-alligator-alley-208944/">Britney and Jamie Lynn Spears: ' + |
| 'Alligator Alley!</a> The Gossip Girls<br><a href="http://foodconsum' + |
| 'er.org/7777/8888/Other_N_ews_51/111101362008_Allergy_incident_could' + |
| '_spell_custody_trouble_for_Britney_Spears.shtml">Allergy incident c' + |
| 'ould spell trouble for Britney Spears</a> Food Consumer<br><a href=' + |
| '"http://www.people.com/people/article/0,,20239458,00.html">People M' + |
| 'agazine</a> - <a href="http://www.eonline.com/uberblog/b68226_hospi' + |
| 'tal_run_could_cost_britney_custody.html">E! Online</a> - <a href="h' + |
| 'ttp://justjared.buzznet.com/2008/11/11/britney-spears-alligator-far' + |
| 'm/">Just Jared</a> - <a href="http://www.efluxmedia.com/news_Britne' + |
| 'y_Spears_Son_Released_from_Hospital_28696.html">eFluxMedia</a><br><' + |
| 'a href="http://news.google.com/news?ncl=1268233361&hl=en">all 9' + |
| '50 news articles �</a>' + |
| insertImageFontGarbage, div.innerHTML); |
| FIELDMOCK.$verify(); |
| }); |
| } |
| |
| function testRangeDelimitedByRanges() { |
| var abcde = goog.dom.getElement('abcde').firstChild; |
| var start = goog.dom.Range.createFromNodes(abcde, 1, abcde, 2); |
| var end = goog.dom.Range.createFromNodes(abcde, 3, abcde, 4); |
| |
| goog.testing.dom.assertRangeEquals(abcde, 1, abcde, 4, |
| goog.editor.plugins.RemoveFormatting.createRangeDelimitedByRanges_( |
| start, end)); |
| } |
| |
| function testGetTableAncestor() { |
| var div = document.getElementById('html'); |
| |
| div.innerHTML = 'foo<table><tr><td>foo</td></tr></table>bar'; |
| assertTrue('Full table is in table', |
| !!FORMATTER.getTableAncestor_(div.childNodes[1])); |
| |
| assertFalse('Outside of table', |
| !!FORMATTER.getTableAncestor_(div.firstChild)); |
| |
| assertTrue('Table cell is in table', |
| !!FORMATTER.getTableAncestor_( |
| div.childNodes[1].firstChild.firstChild.firstChild)); |
| |
| div.innerHTML = 'foo'; |
| assertNull('No table inside field.', |
| FORMATTER.getTableAncestor_(div.childNodes[0])); |
| } |
| |
| |
| /** |
| * @bug 1272905 |
| */ |
| function testHardReturnsInHeadersPreserved() { |
| var div = document.getElementById('html'); |
| div.innerHTML = '<h1>abcd</h1><h2>efgh</h2><h3>ijkl</h3>'; |
| |
| // Select efgh. |
| goog.dom.Range.createFromNodeContents(div.childNodes[1]).select(); |
| FORMATTER.removeFormatting_(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'Proper behavior not yet implemented for IE.'); |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'Proper behavior not yet implemented for WebKit.'); |
| expectedFailures.run(function() { |
| assertHTMLEquals('<h1>abcd</h1><br>efgh<h3>ijkl</h3>', div.innerHTML); |
| }); |
| |
| // Select ijkl. |
| goog.dom.Range.createFromNodeContents(div.lastChild).select(); |
| FORMATTER.removeFormatting_(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'Proper behavior not yet implemented for IE.'); |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'Proper behavior not yet implemented for WebKit.'); |
| expectedFailures.run(function() { |
| assertHTMLEquals('<h1>abcd</h1><br>efgh<br>ijkl', div.innerHTML); |
| }); |
| |
| // Select abcd. |
| goog.dom.Range.createFromNodeContents(div.firstChild).select(); |
| FORMATTER.removeFormatting_(); |
| |
| expectedFailures.expectFailureFor(goog.userAgent.IE, |
| 'Proper behavior not yet implemented for IE.'); |
| expectedFailures.expectFailureFor(goog.userAgent.WEBKIT, |
| 'Proper behavior not yet implemented for WebKit.'); |
| expectedFailures.run(function() { |
| assertHTMLEquals('<br>abcd<br>efgh<br>ijkl', div.innerHTML); |
| }); |
| } |
| |
| function testKeyboardShortcut_space() { |
| FIELDMOCK.$reset(); |
| |
| FIELDMOCK.execCommand( |
| goog.editor.plugins.RemoveFormatting.REMOVE_FORMATTING_COMMAND); |
| |
| FIELDMOCK.$replay(); |
| |
| var e = {}; |
| var key = ' '; |
| var result = FORMATTER.handleKeyboardShortcut(e, key, true); |
| assertTrue(result); |
| |
| FIELDMOCK.$verify(); |
| } |
| |
| function testKeyboardShortcut_other() { |
| FIELDMOCK.$reset(); |
| FIELDMOCK.$replay(); |
| |
| var e = {}; |
| var key = 'a'; |
| var result = FORMATTER.handleKeyboardShortcut(e, key, true); |
| assertFalse(result); |
| |
| FIELDMOCK.$verify(); |
| } |