blob: c00a029b90e10458726cf72a2c3cb554d9cf533c [file] [log] [blame]
// 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.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');
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 = ul.getElementsByTagName('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);
font.innerHTML = '';
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(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(goog.dom.TagName.OL, list.tagName);
assertEquals(3, goog.dom.getElementsByTagNameAndClass(
goog.dom.TagName.LI, null, list).length);
tearDownListAndBlockquoteTests();
}
function setUpSubSuperTests() {
ROOT.innerHTML = '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);
HELPER.assertHtmlMatches('<p><a href="' + url1 + '">' + url1 + '</a></p><p>' +
'<a href="' + dialogUrl + '">' + (goog.userAgent.IE ? 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 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|null>} 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') != null &&
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, FIELDMOCK.getElement().getElementsByTagName('p').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',
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');
fieldElem.innerHTML = '';
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('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));
}
}