UNPKG

accessibility-developer-tools

Version:

This is a library of accessibility-related testing and utility code.

1,301 lines (1,105 loc) 41 kB
// 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.BasicTextFormatterTest'); goog.setTestOnly('goog.editor.plugins.BasicTextFormatterTest'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.dom.Range'); goog.require('goog.dom.TagName'); goog.require('goog.editor.BrowserFeature'); goog.require('goog.editor.Command'); goog.require('goog.editor.Field'); goog.require('goog.editor.Plugin'); goog.require('goog.editor.plugins.BasicTextFormatter'); goog.require('goog.object'); goog.require('goog.style'); goog.require('goog.testing.ExpectedFailures'); goog.require('goog.testing.LooseMock'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.editor.FieldMock'); goog.require('goog.testing.editor.TestHelper'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.mockmatchers'); goog.require('goog.userAgent'); goog.require('goog.userAgent.product'); var stubs; var SAVED_HTML; var FIELDMOCK; var FORMATTER; var ROOT; var HELPER; var OPEN_SUB; var CLOSE_SUB; var OPEN_SUPER; var CLOSE_SUPER; var MOCK_BLOCKQUOTE_STYLE = 'border-left: 1px solid gray;'; var MOCK_GET_BLOCKQUOTE_STYLES = function() { return MOCK_BLOCKQUOTE_STYLE; }; var REAL_FIELD; var REAL_PLUGIN; var expectedFailures; function setUpPage() { stubs = new goog.testing.PropertyReplacer(); SAVED_HTML = goog.dom.getElement('html').innerHTML; FIELDMOCK; FORMATTER; ROOT = goog.dom.getElement('root'); HELPER; OPEN_SUB = goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('530') ? '<span class="Apple-style-span" style="vertical-align: sub;">' : '<sub>'; CLOSE_SUB = goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('530') ? '</span>' : '</sub>'; OPEN_SUPER = goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('530') ? '<span class="Apple-style-span" style="vertical-align: super;">' : '<sup>'; CLOSE_SUPER = goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('530') ? '</span>' : '</sup>'; expectedFailures = new goog.testing.ExpectedFailures(); } function setUp() { FIELDMOCK = new goog.testing.editor.FieldMock(); FORMATTER = new goog.editor.plugins.BasicTextFormatter(); FORMATTER.fieldObject = FIELDMOCK; } function setUpRealField() { REAL_FIELD = new goog.editor.Field('real-field'); REAL_PLUGIN = new goog.editor.plugins.BasicTextFormatter(); REAL_FIELD.registerPlugin(REAL_PLUGIN); REAL_FIELD.makeEditable(); } function setUpRealFieldIframe() { REAL_FIELD = new goog.editor.Field('iframe'); FORMATTER = new goog.editor.plugins.BasicTextFormatter(); REAL_FIELD.registerPlugin(FORMATTER); REAL_FIELD.makeEditable(); } function selectRealField() { goog.dom.Range.createFromNodeContents(REAL_FIELD.getElement()).select(); REAL_FIELD.dispatchSelectionChangeEvent(); } function tearDown() { tearDownFontSizeTests(); if (REAL_FIELD) { REAL_FIELD.makeUneditable(); REAL_FIELD.dispose(); REAL_FIELD = null; } expectedFailures.handleTearDown(); stubs.reset(); goog.dom.getElement('html').innerHTML = SAVED_HTML; } function setUpListAndBlockquoteTests() { var htmlDiv = document.getElementById('html'); HELPER = new goog.testing.editor.TestHelper(htmlDiv); HELPER.setUpEditableElement(); FIELDMOCK.getElement(); FIELDMOCK.$anyTimes(); FIELDMOCK.$returns(htmlDiv); } function tearDownHelper() { HELPER.tearDownEditableElement(); HELPER.dispose(); HELPER = null; } function tearDownListAndBlockquoteTests() { tearDownHelper(); } function testIEList() { if (goog.userAgent.IE) { setUpListAndBlockquoteTests(); FIELDMOCK.queryCommandValue('rtl').$returns(null); FIELDMOCK.$replay(); var ul = goog.dom.getElement('outerUL'); goog.dom.Range .createFromNodeContents(goog.dom.getFirstElementChild(ul).firstChild) .select(); FORMATTER.fixIELists_(); assertFalse('Unordered list must not have ordered type', ul.type == '1'); var ol = goog.dom.getElement('ol'); ol.type = 'disc'; goog.dom.Range .createFromNodeContents(goog.dom.getFirstElementChild(ul).firstChild) .select(); FORMATTER.fixIELists_(); assertFalse('Ordered list must not have unordered type', ol.type == 'disc'); ol.type = '1'; goog.dom.Range .createFromNodeContents(goog.dom.getFirstElementChild(ul).firstChild) .select(); FORMATTER.fixIELists_(); assertTrue('Ordered list must retain ordered list type', ol.type == '1'); tearDownListAndBlockquoteTests(); } } function testWebKitList() { if (goog.userAgent.WEBKIT) { setUpListAndBlockquoteTests(); FIELDMOCK.queryCommandValue('rtl').$returns(null); FIELDMOCK.$replay(); var ul = document.getElementById('outerUL'); var html = ul.innerHTML; goog.dom.Range.createFromNodeContents(ul).select(); FORMATTER.fixSafariLists_(); assertEquals('Contents of UL shouldn\'t change', html, ul.innerHTML); ul = document.getElementById('outerUL2'); goog.dom.Range.createFromNodeContents(ul).select(); FORMATTER.fixSafariLists_(); var childULs = goog.dom.getElementsByTagName(goog.dom.TagName.UL, ul); assertEquals('UL should have one child UL', 1, childULs.length); tearDownListAndBlockquoteTests(); } } function testGeckoListFont() { if (goog.userAgent.GECKO) { setUpListAndBlockquoteTests(); FIELDMOCK.queryCommandValue(goog.editor.Command.DEFAULT_TAG) .$returns('BR') .$times(2); FIELDMOCK.$replay(); var p = goog.dom.getElement('geckolist'); var font = p.firstChild; goog.dom.Range.createFromNodeContents(font).select(); retVal = FORMATTER.beforeInsertListGecko_(); assertFalse('Workaround shouldn\'t be applied when not needed', retVal); goog.dom.removeChildren(font); goog.dom.Range.createFromNodeContents(font).select(); var retVal = FORMATTER.beforeInsertListGecko_(); assertTrue('Workaround should be applied when needed', retVal); document.execCommand('insertorderedlist', false, true); assertTrue( 'Font should be Courier', /courier/i.test(document.queryCommandValue('fontname'))); tearDownListAndBlockquoteTests(); } } function testSwitchListType() { if (!goog.userAgent.WEBKIT) { return; } // Test that we're not seeing https://bugs.webkit.org/show_bug.cgi?id=19539, // the type of multi-item lists. setUpListAndBlockquoteTests(); FIELDMOCK.$replay(); var list = goog.dom.getElement('switchListType'); var parent = goog.dom.getParentElement(list); goog.dom.Range.createFromNodeContents(list).select(); FORMATTER.execCommandInternal('insertunorderedlist'); list = goog.dom.getFirstElementChild(parent); assertEquals(String(goog.dom.TagName.UL), list.tagName); assertEquals( 3, goog.dom.getElementsByTagNameAndClass(goog.dom.TagName.LI, null, list) .length); goog.dom.Range.createFromNodeContents(list).select(); FORMATTER.execCommandInternal('insertorderedlist'); list = goog.dom.getFirstElementChild(parent); assertEquals(String(goog.dom.TagName.OL), list.tagName); assertEquals( 3, goog.dom.getElementsByTagNameAndClass(goog.dom.TagName.LI, null, list) .length); tearDownListAndBlockquoteTests(); } function testIsSilentCommand() { var commands = goog.object.getValues(goog.editor.plugins.BasicTextFormatter.COMMAND); var silentCommands = [goog.editor.plugins.BasicTextFormatter.COMMAND.CREATE_LINK]; for (var i = 0; i < commands.length; i += 1) { var command = commands[i]; var shouldBeSilent = goog.array.contains(silentCommands, command); var isSilent = goog.editor.plugins.BasicTextFormatter.prototype.isSilentCommand.call( null, command); assertEquals(shouldBeSilent, isSilent); } } function setUpSubSuperTests() { goog.dom.setTextContent(ROOT, '12345'); HELPER = new goog.testing.editor.TestHelper(ROOT); HELPER.setUpEditableElement(); } function tearDownSubSuperTests() { tearDownHelper(); } function testSubscriptRemovesSuperscript() { setUpSubSuperTests(); FIELDMOCK.$replay(); HELPER.select('12345', 1, '12345', 4); // Selects '234'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUPERSCRIPT); HELPER.assertHtmlMatches('1' + OPEN_SUPER + '234' + CLOSE_SUPER + '5'); FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUBSCRIPT); HELPER.assertHtmlMatches('1' + OPEN_SUB + '234' + CLOSE_SUB + '5'); FIELDMOCK.$verify(); tearDownSubSuperTests(); } function testSuperscriptRemovesSubscript() { setUpSubSuperTests(); FIELDMOCK.$replay(); HELPER.select('12345', 1, '12345', 4); // Selects '234'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUBSCRIPT); HELPER.assertHtmlMatches('1' + OPEN_SUB + '234' + CLOSE_SUB + '5'); FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUPERSCRIPT); HELPER.assertHtmlMatches('1' + OPEN_SUPER + '234' + CLOSE_SUPER + '5'); FIELDMOCK.$verify(); tearDownSubSuperTests(); } function testSubscriptRemovesSuperscriptIntersecting() { // Tests: 12345 , sup(23) , sub(34) ==> 1+sup(2)+sub(34)+5 // This is more complex because the sub and sup calls are made on separate // fields which intersect each other and queryCommandValue seems to return // false if the command is only applied to part of the field. setUpSubSuperTests(); FIELDMOCK.$replay(); HELPER.select('12345', 1, '12345', 3); // Selects '23'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUPERSCRIPT); HELPER.assertHtmlMatches('1' + OPEN_SUPER + '23' + CLOSE_SUPER + '45'); HELPER.select('23', 1, '45', 1); // Selects '34'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUBSCRIPT); HELPER.assertHtmlMatches( '1' + OPEN_SUPER + '2' + CLOSE_SUPER + OPEN_SUB + '34' + CLOSE_SUB + '5'); FIELDMOCK.$verify(); tearDownSubSuperTests(); } function testSuperscriptRemovesSubscriptIntersecting() { // Tests: 12345 , sub(23) , sup(34) ==> 1+sub(2)+sup(34)+5 setUpSubSuperTests(); FIELDMOCK.$replay(); HELPER.select('12345', 1, '12345', 3); // Selects '23'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUBSCRIPT); HELPER.assertHtmlMatches('1' + OPEN_SUB + '23' + CLOSE_SUB + '45'); HELPER.select('23', 1, '45', 1); // Selects '34'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.SUPERSCRIPT); HELPER.assertHtmlMatches( '1' + OPEN_SUB + '2' + CLOSE_SUB + OPEN_SUPER + '34' + CLOSE_SUPER + '5'); FIELDMOCK.$verify(); tearDownSubSuperTests(); } function setUpLinkTests(text, url, isEditable) { stubs.set(window, 'prompt', function() { return url; }); ROOT.innerHTML = text; HELPER = new goog.testing.editor.TestHelper(ROOT); if (isEditable) { HELPER.setUpEditableElement(); FIELDMOCK .execCommand( goog.editor.Command.MODAL_LINK_EDITOR, goog.testing.mockmatchers.isObject) .$returns(undefined); FORMATTER.focusField_ = function() { throw 'Field should not be re-focused'; }; } FIELDMOCK.getElement().$anyTimes().$returns(ROOT); FIELDMOCK.setModalMode(true); FIELDMOCK.isSelectionEditable().$anyTimes().$returns(isEditable); } function tearDownLinkTests() { tearDownHelper(); } function testLink() { setUpLinkTests('12345', 'http://www.x.com/', true); FIELDMOCK.$replay(); HELPER.select('12345', 3); FORMATTER.execCommandInternal(goog.editor.Command.LINK); HELPER.assertHtmlMatches( goog.editor.BrowserFeature.GETS_STUCK_IN_LINKS ? '123<a href="http://www.x.com/">http://www.x.com/</a>&nbsp;45' : '123<a href="http://www.x.com/">http://www.x.com/</a>45'); FIELDMOCK.$verify(); tearDownLinkTests(); } function testLinks() { var url1 = 'http://google.com/1'; var url2 = 'http://google.com/2'; var dialogUrl = 'http://google.com/3'; var html = '<p>' + url1 + '</p><p>' + url2 + '</p>'; setUpLinkTests(html, dialogUrl, true); FIELDMOCK.$replay(); HELPER.select(url1, 0, url2, url2.length); FORMATTER.execCommandInternal(goog.editor.Command.LINK); var expectDialogUrl = false; if (goog.userAgent.IE || (goog.userAgent.EDGE && !goog.userAgent.product.isVersion(14))) { expectDialogUrl = true; } HELPER.assertHtmlMatches( '<p><a href="' + url1 + '">' + url1 + '</a></p><p>' + '<a href="' + dialogUrl + '">' + (expectDialogUrl ? dialogUrl : url2) + '</a></p>'); } function testSelectedLink() { setUpLinkTests('12345', 'http://www.x.com/', true); FIELDMOCK.$replay(); HELPER.select('12345', 1, '12345', 4); FORMATTER.execCommandInternal(goog.editor.Command.LINK); HELPER.assertHtmlMatches( goog.editor.BrowserFeature.GETS_STUCK_IN_LINKS ? '1<a href="http://www.x.com/">234</a>&nbsp;5' : '1<a href="http://www.x.com/">234</a>5'); FIELDMOCK.$verify(); tearDownLinkTests(); } function testCanceledLink() { setUpLinkTests('12345', undefined, true); FIELDMOCK.$replay(); HELPER.select('12345', 1, '12345', 4); FORMATTER.execCommandInternal(goog.editor.Command.LINK); HELPER.assertHtmlMatches('12345'); assertEquals('234', FIELDMOCK.getRange().getText()); FIELDMOCK.$verify(); tearDownLinkTests(); } function testUnfocusedLink() { FIELDMOCK.$reset(); FIELDMOCK.getEditableDomHelper().$anyTimes().$returns( goog.dom.getDomHelper(window.document)); setUpLinkTests('12345', undefined, false); FIELDMOCK.getRange().$anyTimes().$returns(null); FIELDMOCK.$replay(); FORMATTER.execCommandInternal(goog.editor.Command.LINK); HELPER.assertHtmlMatches('12345'); FIELDMOCK.$verify(); tearDownLinkTests(); } function testCreateLink() { var text = 'some text here'; var url = 'http://google.com'; ROOT.innerHTML = text; HELPER = new goog.testing.editor.TestHelper(ROOT); HELPER.setUpEditableElement(); FIELDMOCK.isSelectionEditable().$anyTimes().$returns(true); FIELDMOCK.getElement().$anyTimes().$returns(ROOT); FIELDMOCK.$replay(); HELPER.select(text, 0, text, text.length); FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.CREATE_LINK, FIELDMOCK.getRange(), url); HELPER.assertHtmlMatches('<a href="' + url + '">' + text + '</a>'); FIELDMOCK.$verify(); tearDownLinkTests(); } function setUpJustifyTests(html) { ROOT.innerHTML = html; HELPER = new goog.testing.editor.TestHelper(ROOT); HELPER.setUpEditableElement(ROOT); FIELDMOCK.getElement(); FIELDMOCK.$anyTimes(); FIELDMOCK.$returns(ROOT); FIELDMOCK.getElement(); FIELDMOCK.$anyTimes(); FIELDMOCK.$returns(ROOT); } function tearDownJustifyTests() { tearDownHelper(); } function testJustify() { setUpJustifyTests('<div>abc</div><p>def</p><div>ghi</div>'); FIELDMOCK.$replay(); HELPER.select('abc', 1, 'def', 1); // Selects 'bcd'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER); HELPER.assertHtmlMatches( '<div style="text-align: center">abc</div>' + '<p style="text-align: center">def</p>' + '<div>ghi</div>'); FIELDMOCK.$verify(); tearDownJustifyTests(); } function testJustifyInInline() { setUpJustifyTests('<div>a<i>b</i>c</div><div>d</div>'); FIELDMOCK.$replay(); HELPER.select('b', 0, 'b', 1); // Selects 'b'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER); HELPER.assertHtmlMatches( '<div style="text-align: center">a<i>b</i>c</div><div>d</div>'); FIELDMOCK.$verify(); tearDownJustifyTests(); } function testJustifyInBlock() { setUpJustifyTests('<div>a<div>b</div>c</div>'); FIELDMOCK.$replay(); HELPER.select('b', 0, 'b', 1); // Selects 'h'. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER); HELPER.assertHtmlMatches( '<div>a<div style="text-align: center">b</div>c</div>'); FIELDMOCK.$verify(); tearDownJustifyTests(); } var isFontSizeTest = false; var defaultFontSizeMap; function setUpFontSizeTests() { isFontSizeTest = true; ROOT.innerHTML = '1<span style="font-size:2px">23</span>4' + '<span style="font-size:5px; white-space:pre">56</span>7'; HELPER = new goog.testing.editor.TestHelper(ROOT); HELPER.setUpEditableElement(); FIELDMOCK.getElement().$returns(ROOT).$anyTimes(); // Map representing the sizes of the text in the HTML snippet used in these // tests. The key is the exact text content of each text node, and the value // is the initial size of the font in pixels. Since tests may cause a text // node to be split in two, this also contains keys that initially don't // match any text node, but may match one later if an existing node is // split. The value for these keys is null, signifying no text node should // exist with that content. defaultFontSizeMap = { '1': 16, '2': null, '23': 2, '3': null, '4': 16, '5': null, '56': 5, '6': null, '7': 16 }; assertFontSizes('Assertion failed on default font sizes!', {}); } function tearDownFontSizeTests() { if (isFontSizeTest) { tearDownHelper(); isFontSizeTest = false; } } /** * Asserts that the text nodes set up by setUpFontSizeTests() have had their * font sizes changed as described by sizeChangesMap. * @param {string} msg Assertion error message. * @param {Object<string,?number>} sizeChangesMap Maps the text content * of a text node to be measured to its expected font size in pixels, or * null if that text node should not be present in the document (i.e. * because it was split into two). Only the text nodes that have changed * from their default need to be specified. */ function assertFontSizes(msg, sizeChangesMap) { goog.object.extend(defaultFontSizeMap, sizeChangesMap); for (var k in defaultFontSizeMap) { var node = HELPER.findTextNode(k); var expected = defaultFontSizeMap[k]; if (expected) { assertNotNull(msg + ' [couldn\'t find text node "' + k + '"]', node); assertEquals( msg + ' [incorrect font size for "' + k + '"]', expected, goog.style.getFontSize(node.parentNode)); } else { assertNull(msg + ' [unexpected text node "' + k + '"]', node); } } } /** * Regression test for {@bug 1286408}. Tests that when you change the font * size of a selection, any font size styles that were nested inside are * removed. */ function testFontSizeOverridesStyleAttr() { setUpFontSizeTests(); FIELDMOCK.$replay(); HELPER.select('1', 0, '4', 1); // Selects 1234. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_SIZE, 6); assertFontSizes( 'New font size should override existing font size', {'1': 32, '23': 32, '4': 32}); if (goog.editor.BrowserFeature.DOESNT_OVERRIDE_FONT_SIZE_IN_STYLE_ATTR) { var span = HELPER.findTextNode('23').parentNode; assertFalse( 'Style attribute should be gone', span.getAttributeNode('style') != null && span.getAttributeNode('style').specified); } FIELDMOCK.$verify(); } /** * Make sure the style stripping works when the selection starts and stops in * different nodes that both contain font size styles. */ function testFontSizeOverridesStyleAttrMultiNode() { setUpFontSizeTests(); FIELDMOCK.$replay(); HELPER.select('23', 0, '56', 2); // Selects 23456. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_SIZE, 6); var span = HELPER.findTextNode('23').parentNode; var span2 = HELPER.findTextNode('56').parentNode; assertFontSizes( 'New font size should override existing font size in all spans', {'23': 32, '4': 32, '56': 32}); var whiteSpace = goog.userAgent.IE ? goog.style.getCascadedStyle(span2, 'whiteSpace') : goog.style.getComputedStyle(span2, 'whiteSpace'); assertEquals( 'Whitespace style in last span should have been left', 'pre', whiteSpace); if (goog.editor.BrowserFeature.DOESNT_OVERRIDE_FONT_SIZE_IN_STYLE_ATTR) { assertFalse( 'Style attribute should be gone from first span', span.getAttributeNode('style') != null && span.getAttributeNode('style').specified); assertTrue( 'Style attribute should not be gone from last span', span2.getAttributeNode('style').specified); } FIELDMOCK.$verify(); } /** * Makes sure the font size style is not removed when only a part of the * element with font size style is selected during the font size command. */ function testFontSizeDoesntOverrideStyleAttr() { setUpFontSizeTests(); FIELDMOCK.$replay(); HELPER.select('23', 1, '4', 1); // Selects 34 (half of span with font size). FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_SIZE, 6); assertFontSizes( 'New font size shouldn\'t override existing font size before selection', {'2': 2, '23': null, '3': 32, '4': 32}); FIELDMOCK.$verify(); } /** * Makes sure the font size style is not removed when only a part of the * element with font size style is selected during the font size command, but * is removed for another element that is fully selected. */ function testFontSizeDoesntOverrideStyleAttrMultiNode() { setUpFontSizeTests(); FIELDMOCK.$replay(); HELPER.select('23', 1, '56', 2); // Selects 3456. FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_SIZE, 6); assertFontSizes( 'New font size shouldn\'t override existing font size before ' + 'selection, but still override existing font size in last span', {'2': 2, '23': null, '3': 32, '4': 32, '56': 32}); FIELDMOCK.$verify(); } /** * Helper to make sure the precondition that executing the font size command * wraps the content in font tags instead of modifying the style attribute is * maintained by the browser even if the selection is already text that is * wrapped in a tag with a font size style. We test this with several * permutations of how the selection looks: selecting the text in the text * node, selecting the whole text node as a unit, or selecting the whole span * node as a unit. Sometimes the browser wraps the text node with the font * tag, sometimes it wraps the span with the font tag. Either one is ok as * long as a font tag is actually being used instead of just modifying the * span's style, because our fix for {@bug 1286408} would remove that style. * @param {function()} doSelect Function to select the "23" text in the test * content. */ function doTestFontSizeStyledSpan(doSelect) { // Make sure no new browsers start getting this bad behavior. If they do, // this test will unexpectedly pass. expectedFailures.expectFailureFor( !goog.editor.BrowserFeature.DOESNT_OVERRIDE_FONT_SIZE_IN_STYLE_ATTR); try { setUpFontSizeTests(); FIELDMOCK.$replay(); doSelect(); FORMATTER.execCommandInternal( goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_SIZE, 7); var parentNode = HELPER.findTextNode('23').parentNode; var grandparentNode = parentNode.parentNode; var fontNode = goog.dom.getElementsByTagNameAndClass( goog.dom.TagName.FONT, undefined, ROOT)[0]; var spanNode = goog.dom.getElementsByTagNameAndClass( goog.dom.TagName.SPAN, undefined, ROOT)[0]; assertTrue( 'A font tag should have been added either outside or inside' + ' the existing span', parentNode == spanNode && grandparentNode == fontNode || parentNode == fontNode && grandparentNode == spanNode); FIELDMOCK.$verify(); } catch (e) { expectedFailures.handleException(e); } } function testFontSizeStyledSpanSelectingText() { doTestFontSizeStyledSpan(function() { HELPER.select('23', 0, '23', 2); }); } function testFontSizeStyledSpanSelectingTextNode() { doTestFontSizeStyledSpan(function() { var textNode = HELPER.findTextNode('23'); HELPER.select(textNode.parentNode, 0, textNode.parentNode, 1); }); } function testFontSizeStyledSpanSelectingSpanNode() { doTestFontSizeStyledSpan(function() { var spanNode = HELPER.findTextNode('23').parentNode; HELPER.select(spanNode.parentNode, 1, spanNode.parentNode, 2); }); } function setUpIframeField(content) { var ifr = document.getElementById('iframe'); var body = ifr.contentWindow.document.body; body.innerHTML = content; HELPER = new goog.testing.editor.TestHelper(body); HELPER.setUpEditableElement(); FIELDMOCK = new goog.testing.editor.FieldMock(ifr.contentWindow); FIELDMOCK.getElement(); FIELDMOCK.$anyTimes(); FIELDMOCK.$returns(body); FIELDMOCK.queryCommandValue('rtl'); FIELDMOCK.$anyTimes(); FIELDMOCK.$returns(null); FORMATTER.fieldObject = FIELDMOCK; } function tearDownIframeField() { tearDownHelper(); } function setUpConvertBreaksToDivTests() { ROOT.innerHTML = '<p>paragraph</p>one<br id="br1">two<br><br><br>three'; HELPER = new goog.testing.editor.TestHelper(ROOT); HELPER.setUpEditableElement(); FIELDMOCK.getElement(); FIELDMOCK.$anyTimes(); FIELDMOCK.$returns(ROOT); } function tearDownConvertBreaksToDivTests() { tearDownHelper(); } /** @bug 1414941 */ function testConvertBreaksToDivsKeepsP() { if (goog.editor.BrowserFeature.CAN_LISTIFY_BR) { return; } setUpConvertBreaksToDivTests(); FIELDMOCK.$replay(); HELPER.select('three', 0); FORMATTER.convertBreaksToDivs_(); assertEquals( 'There should still be a <p> tag', 1, goog.dom.getElementsByTagName(goog.dom.TagName.P, FIELDMOCK.getElement()) .length); var html = FIELDMOCK.getElement().innerHTML.toLowerCase(); assertNotBadBrElements(html); assertNotContains( 'There should not be empty <div> elements', '<div><\/div>', html); FIELDMOCK.$verify(); tearDownConvertBreaksToDivTests(); } /** * @bug 1414937 * @bug 934535 */ function testConvertBreaksToDivsDoesntCollapseBR() { if (goog.editor.BrowserFeature.CAN_LISTIFY_BR) { return; } setUpConvertBreaksToDivTests(); FIELDMOCK.$replay(); HELPER.select('three', 0); FORMATTER.convertBreaksToDivs_(); var html = FIELDMOCK.getElement().innerHTML.toLowerCase(); assertNotBadBrElements(html); assertNotContains( 'There should not be empty <div> elements', '<div><\/div>', html); if (goog.userAgent.IE) { // <div><br></div> misbehaves in IE assertNotContains( '<br> should not be used to prevent <div> from collapsing', '<div><br><\/div>', html); } FIELDMOCK.$verify(); tearDownConvertBreaksToDivTests(); } function testConvertBreaksToDivsSelection() { if (goog.editor.BrowserFeature.CAN_LISTIFY_BR) { return; } setUpConvertBreaksToDivTests(); FIELDMOCK.$replay(); HELPER.select('two', 1, 'three', 3); var before = FIELDMOCK.getRange().getText().replace(/\s/g, ''); FORMATTER.convertBreaksToDivs_(); assertEquals( 'Selection must not be changed', before, FIELDMOCK.getRange().getText().replace(/\s/g, '')); FIELDMOCK.$verify(); tearDownConvertBreaksToDivTests(); } /** @bug 1414937 */ function testConvertBreaksToDivsInsertList() { setUpConvertBreaksToDivTests(); FIELDMOCK.$replay(); HELPER.select('three', 0); FORMATTER.execCommandInternal('insertorderedlist'); assertTrue( 'Ordered list must be inserted', FIELDMOCK.getEditableDomHelper().getDocument().queryCommandState( 'insertorderedlist')); tearDownConvertBreaksToDivTests(); } /** * Regression test for {@bug 1939883}, where if a br has an id, it causes * the convert br code to throw a js error. This goes a step further and * ensures that the id is preserved in the resulting div element. */ function testConvertBreaksToDivsKeepsId() { if (goog.editor.BrowserFeature.CAN_LISTIFY_BR) { return; } setUpConvertBreaksToDivTests(); FIELDMOCK.$replay(); HELPER.select('one', 0, 'two', 0); FORMATTER.convertBreaksToDivs_(); var html = FIELDMOCK.getElement().innerHTML.toLowerCase(); assertNotBadBrElements(html); var idBr = document.getElementById('br1'); assertNotNull('There should still be a tag with id="br1"', idBr); assertEquals('The tag with id="br1" should be a <div> now', String(goog.dom.TagName.DIV), idBr.tagName); assertNull( 'There should not be any tag with id="temp_br"', document.getElementById('temp_br')); FIELDMOCK.$verify(); tearDownConvertBreaksToDivTests(); } /** * @bug 2420054 */ var JUSTIFICATION_COMMANDS = [ goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT, goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT, goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER, goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_FULL ]; function doTestIsJustification(command) { setUpRealField(); REAL_FIELD.setHtml(false, 'foo'); selectRealField(); REAL_FIELD.execCommand(command); for (var i = 0; i < JUSTIFICATION_COMMANDS.length; i++) { if (JUSTIFICATION_COMMANDS[i] == command) { assertTrue( 'queryCommandValue(' + JUSTIFICATION_COMMANDS[i] + ') should be true after execCommand(' + command + ')', REAL_FIELD.queryCommandValue(JUSTIFICATION_COMMANDS[i])); } else { assertFalse( 'queryCommandValue(' + JUSTIFICATION_COMMANDS[i] + ') should be false after execCommand(' + command + ')', REAL_FIELD.queryCommandValue(JUSTIFICATION_COMMANDS[i])); } } } function testIsJustificationLeft() { doTestIsJustification( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT); } function testIsJustificationRight() { doTestIsJustification( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT); } function testIsJustificationCenter() { doTestIsJustification( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER); } function testIsJustificationFull() { doTestIsJustification( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_FULL); } /** * Regression test for {@bug 1414813}, where all 3 justification buttons are * considered "on" when you first tab into the editable field. In this * situation, when lorem ipsum is the only node in the editable field iframe * body, mockField.getRange() returns an empty range. */ function testIsJustificationEmptySelection() { var mockField = new goog.testing.LooseMock(goog.editor.Field); mockField.getRange(); mockField.$anyTimes(); mockField.$returns(null); mockField.getPluginByClassId('Bidi'); mockField.$anyTimes(); mockField.$returns(null); FORMATTER.fieldObject = mockField; mockField.$replay(); assertFalse( 'Empty selection should not be justified', FORMATTER.isJustification_( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER)); assertFalse( 'Empty selection should not be justified', FORMATTER.isJustification_( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_FULL)); assertFalse( 'Empty selection should not be justified', FORMATTER.isJustification_( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); assertFalse( 'Empty selection should not be justified', FORMATTER.isJustification_( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); mockField.$verify(); } function testIsJustificationSimple1() { setUpRealField(); REAL_FIELD.setHtml(false, '<div align="right">foo</div>'); selectRealField(); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); assertTrue( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); } function testIsJustificationSimple2() { setUpRealField(); REAL_FIELD.setHtml(false, '<div style="text-align: right;">foo</div>'); selectRealField(); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); assertTrue( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); } function testIsJustificationComplete1() { setUpRealField(); REAL_FIELD.setHtml( false, '<div align="left">a</div><div align="right">b</div>'); selectRealField(); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); } function testIsJustificationComplete2() { setUpRealField(); REAL_FIELD.setHtml( false, '<div align="left">a</div><div align="left">b</div>'); selectRealField(); assertTrue( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); } function testIsJustificationComplete3() { setUpRealField(); REAL_FIELD.setHtml( false, '<div align="right">a</div><div align="right">b</div>'); selectRealField(); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); assertTrue( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); } function testIsJustificationComplete4() { setUpRealField(); REAL_FIELD.setHtml( false, '<div align="right"><div align="left">a</div></div>' + '<div align="right">b</div>'); selectRealField(); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); assertTrue( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); } function testIsJustificationComplete5() { setUpRealField(); REAL_FIELD.setHtml( false, '<div align="right">a</div>b' + '<div align="right">c</div>'); selectRealField(); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT)); assertFalse( REAL_FIELD.queryCommandValue( goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT)); } /** @bug 2472589 */ function doTestIsJustificationPInDiv(useCss, align, command) { setUpRealField(); var html = '<div ' + (useCss ? 'style="text-align:' : 'align="') + align + '"><p>foo</p></div>'; REAL_FIELD.setHtml(false, html); selectRealField(); assertTrue( 'P inside ' + align + ' aligned' + (useCss ? ' (using CSS)' : '') + ' DIV should be considered ' + align + ' aligned', REAL_FIELD.queryCommandValue(command)); } function testIsJustificationPInDivLeft() { doTestIsJustificationPInDiv( false, 'left', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT); } function testIsJustificationPInDivRight() { doTestIsJustificationPInDiv( false, 'right', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT); } function testIsJustificationPInDivCenter() { doTestIsJustificationPInDiv( false, 'center', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER); } function testIsJustificationPInDivJustify() { doTestIsJustificationPInDiv( false, 'justify', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_FULL); } function testIsJustificationPInDivLeftCss() { doTestIsJustificationPInDiv( true, 'left', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT); } function testIsJustificationPInDivRightCss() { doTestIsJustificationPInDiv( true, 'right', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT); } function testIsJustificationPInDivCenterCss() { doTestIsJustificationPInDiv( true, 'center', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER); } function testIsJustificationPInDivJustifyCss() { doTestIsJustificationPInDiv( true, 'justify', goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_FULL); } function testPrepareContent() { setUpRealField(); assertPreparedContents('\n', '\n'); if (goog.editor.BrowserFeature.COLLAPSES_EMPTY_NODES) { assertPreparedContents( "&nbsp;<script>alert('hi')<" + '/script>', "<script>alert('hi')<" + '/script>'); } else { assertNotPreparedContents("<script>alert('hi')<" + '/script>'); } if (goog.editor.BrowserFeature.CONVERT_TO_B_AND_I_TAGS) { assertPreparedContents( '<b id=\'foo\'>hi</b>', '<strong id=\'foo\'>hi</strong>'); assertPreparedContents('<i>hi</i>', '<em>hi</em>'); assertNotPreparedContents('<embed>'); } else { assertNotPreparedContents('<em>hi</em>'); assertNotPreparedContents('<strong id=\'foo\'>hi</strong>'); } } function testScrubImagesRemovesCustomAttributes() { var fieldElem = goog.dom.getElement('real-field'); goog.dom.removeChildren(fieldElem); var attrs = { 'src': 'http://www.google.com/foo.jpg', 'tabIndex': '0', 'tabIndexSet': '0' }; attrs[goog.HASH_CODE_PROPERTY_] = '0'; goog.dom.appendChild( fieldElem, goog.dom.createDom(goog.dom.TagName.IMG, attrs)); setUpRealField(); var html = REAL_FIELD.getCleanContents(); assert( 'Images must not have forbidden attributes: ' + html, -1 == html.indexOf('tabIndex') && -1 == html.indexOf('closure')); assert( 'Image URLs must not be made relative by default: ' + html, -1 != html.indexOf('/foo.jpg')); } function testGeckoSelectionChange() { if (!goog.userAgent.GECKO || !goog.userAgent.isVersionOrHigher('1.9')) { return; } setUpRealFieldIframe(); // Use native selection for this test because goog.dom.Range will // change selections of <br> var ifr = document.getElementById('iframe'); ifr.contentWindow.document.body.innerHTML = 'hello<br id="br1"><br id="br2">'; var body = ifr.contentWindow.document.body; var range = REAL_FIELD.getRange(); var browserRange = range.getBrowserRangeObject(); browserRange.setStart(body, 2); browserRange.setEnd(body, 2); FORMATTER.applyExecCommandGeckoFixes_('formatblock'); var updatedRange = REAL_FIELD.getRange().getBrowserRangeObject(); assertEquals( 'Range should have been updated to deep range', 'br2', updatedRange.startContainer.id); assertEquals( 'Range should have been updated to deep range', 0, updatedRange.startOffset); } function testIEExecCommandFixes() { if (!goog.userAgent.IE) { return; } setUpRealField(); REAL_FIELD.setHtml(false, '<blockquote>hi</blockquote>'); goog.dom.Range.createFromNodeContents(REAL_FIELD.getElement()).select(); var nodes = REAL_PLUGIN.applyExecCommandIEFixes_('insertOrderedList'); if (goog.userAgent.isVersionOrHigher('9')) { assertHTMLEquals( '<blockquote>hi<div style="height:0px"></div></blockquote>', REAL_FIELD.getCleanContents()); } else { assertHTMLEquals( '<blockquote>hi <div style="height:0px"></div></blockquote>', REAL_FIELD.getCleanContents()); } } /** * Assert that the prepared contents matches the expected. */ function assertPreparedContents(expected, original) { assertEquals( expected, REAL_FIELD.reduceOp_( goog.editor.Plugin.Op.PREPARE_CONTENTS_HTML, original)); } /** * Assert that sanitization doesn't affect the given text. */ function assertNotPreparedContents(text) { assertPreparedContents(text, text); } /** * Assert that only BR elements expected to persist after convertBreaksToDivs_ * are in the HTML. */ function assertNotBadBrElements(html) { if (goog.userAgent.IE) { assertNotContains('There should not be <br> elements', '<br', html); } else { assertFalse( 'There should not be <br> elements, except ones to prevent ' + '<div>s from collapsing' + html, /(?!<div>)<br>(?!<\/div>)/.test(html)); } }