123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- // Copyright 2009 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.
- /**
- * @fileoverview Trogedit unit tests for goog.editor.SeamlessField.
- *
- * @author nicksantos@google.com (Nick Santos)
- * @suppress {missingProperties} There are many mocks in this unit test,
- * and the mocks don't fit well in the type system.
- */
- /** @suppress {extraProvide} */
- goog.provide('goog.editor.seamlessfield_test');
- goog.require('goog.dom');
- goog.require('goog.dom.DomHelper');
- goog.require('goog.dom.Range');
- goog.require('goog.dom.TagName');
- goog.require('goog.editor.BrowserFeature');
- goog.require('goog.editor.Field');
- goog.require('goog.editor.SeamlessField');
- goog.require('goog.events');
- goog.require('goog.functions');
- goog.require('goog.style');
- goog.require('goog.testing.MockClock');
- goog.require('goog.testing.MockRange');
- goog.require('goog.testing.jsunit');
- goog.setTestOnly('seamlessfield_test');
- var fieldElem;
- var fieldElemClone;
- function setUp() {
- fieldElem = goog.dom.getElement('field');
- fieldElemClone = fieldElem.cloneNode(true);
- }
- function tearDown() {
- fieldElem.parentNode.replaceChild(fieldElemClone, fieldElem);
- }
- // the following tests check for blended iframe positioning. They really
- // only make sense on browsers without contentEditable.
- function testBlankField() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- assertAttachSeamlessIframeSizesCorrectly(
- initSeamlessField(' ', {}), createSeamlessIframe());
- }
- }
- function testFieldWithContent() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- assertAttachSeamlessIframeSizesCorrectly(
- initSeamlessField('Hi!', {}), createSeamlessIframe());
- }
- }
- function testFieldWithPadding() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- assertAttachSeamlessIframeSizesCorrectly(
- initSeamlessField('Hi!', {'padding': '2px 5px'}),
- createSeamlessIframe());
- }
- }
- function testFieldWithMargin() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- assertAttachSeamlessIframeSizesCorrectly(
- initSeamlessField('Hi!', {'margin': '2px 5px'}),
- createSeamlessIframe());
- }
- }
- function testFieldWithBorder() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- assertAttachSeamlessIframeSizesCorrectly(
- initSeamlessField('Hi!', {'border': '2px 5px'}),
- createSeamlessIframe());
- }
- }
- function testFieldWithOverflow() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- assertAttachSeamlessIframeSizesCorrectly(
- initSeamlessField(
- ['1', '2', '3', '4', '5', '6', '7'].join('<p/>'),
- {'overflow': 'auto', 'position': 'relative', 'height': '20px'}),
- createSeamlessIframe());
- assertEquals(20, fieldElem.offsetHeight);
- }
- }
- function testFieldWithOverflowAndPadding() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- var blendedField =
- initSeamlessField(['1', '2', '3', '4', '5', '6', '7'].join('<p/>'), {
- 'overflow': 'auto',
- 'position': 'relative',
- 'height': '20px',
- 'padding': '2px 3px'
- });
- var blendedIframe = createSeamlessIframe();
- assertAttachSeamlessIframeSizesCorrectly(blendedField, blendedIframe);
- assertEquals(24, fieldElem.offsetHeight);
- }
- }
- function testIframeHeightGrowsOnWrap() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- var clock = new goog.testing.MockClock(true);
- var blendedField;
- try {
- blendedField = initSeamlessField(
- '', {'border': '1px solid black', 'height': '20px'});
- blendedField.makeEditable();
- blendedField.setHtml(false, 'Content that should wrap after resize.');
- // Ensure that the field was fully loaded and sized before measuring.
- clock.tick(1);
- // Capture starting heights.
- var unwrappedIframeHeight = blendedField.getEditableIframe().offsetHeight;
- // Resize the field such that the text should wrap.
- fieldElem.style.width = '200px';
- blendedField.doFieldSizingGecko();
- // Iframe should grow as a result.
- var wrappedIframeHeight = blendedField.getEditableIframe().offsetHeight;
- assertTrue(
- 'Wrapped text should cause iframe to grow - initial height: ' +
- unwrappedIframeHeight + ', wrapped height: ' +
- wrappedIframeHeight,
- wrappedIframeHeight > unwrappedIframeHeight);
- } finally {
- blendedField.dispose();
- clock.dispose();
- }
- }
- }
- function testDispatchIframeResizedForWrapperHeight() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- var clock = new goog.testing.MockClock(true);
- var blendedField = initSeamlessField('Hi!', {'border': '2px 5px'});
- var iframe = createSeamlessIframe();
- blendedField.attachIframe(iframe);
- var resizeCalled = false;
- goog.events.listenOnce(
- blendedField, goog.editor.Field.EventType.IFRAME_RESIZED,
- function() { resizeCalled = true; });
- try {
- blendedField.makeEditable();
- blendedField.setHtml(false, 'Content that should wrap after resize.');
- // Ensure that the field was fully loaded and sized before measuring.
- clock.tick(1);
- assertFalse('Iframe resize must not be dispatched yet', resizeCalled);
- // Resize the field such that the text should wrap.
- fieldElem.style.width = '200px';
- blendedField.sizeIframeToWrapperGecko_();
- assertTrue('Iframe resize must be dispatched for Wrapper', resizeCalled);
- } finally {
- blendedField.dispose();
- clock.dispose();
- }
- }
- }
- function testDispatchIframeResizedForBodyHeight() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- var clock = new goog.testing.MockClock(true);
- var blendedField = initSeamlessField('Hi!', {'border': '2px 5px'});
- var iframe = createSeamlessIframe();
- blendedField.attachIframe(iframe);
- var resizeCalled = false;
- goog.events.listenOnce(
- blendedField, goog.editor.Field.EventType.IFRAME_RESIZED,
- function() { resizeCalled = true; });
- try {
- blendedField.makeEditable();
- blendedField.setHtml(false, 'Content that should wrap after resize.');
- // Ensure that the field was fully loaded and sized before measuring.
- clock.tick(1);
- assertFalse('Iframe resize must not be dispatched yet', resizeCalled);
- // Resize the field to a different body height.
- var bodyHeight = blendedField.getIframeBodyHeightGecko_();
- blendedField.getIframeBodyHeightGecko_ = function() {
- return bodyHeight + 1;
- };
- blendedField.sizeIframeToBodyHeightGecko_();
- assertTrue('Iframe resize must be dispatched for Body', resizeCalled);
- } finally {
- blendedField.dispose();
- clock.dispose();
- }
- }
- }
- function testDispatchBlur() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE &&
- !goog.editor.BrowserFeature.CLEARS_SELECTION_WHEN_FOCUS_LEAVES) {
- var blendedField = initSeamlessField('Hi!', {'border': '2px 5px'});
- var iframe = createSeamlessIframe();
- blendedField.attachIframe(iframe);
- var blurCalled = false;
- goog.events.listenOnce(
- blendedField, goog.editor.Field.EventType.BLUR,
- function() { blurCalled = true; });
- var clearSelection = goog.dom.Range.clearSelection;
- var cleared = false;
- var clearedWindow;
- blendedField.editableDomHelper = new goog.dom.DomHelper();
- blendedField.editableDomHelper.getWindow =
- goog.functions.constant(iframe.contentWindow);
- var mockRange = new goog.testing.MockRange();
- blendedField.getRange = function() { return mockRange; };
- goog.dom.Range.clearSelection = function(opt_window) {
- clearSelection(opt_window);
- cleared = true;
- clearedWindow = opt_window;
- };
- var clock = new goog.testing.MockClock(true);
- mockRange.collapse(true);
- mockRange.select();
- mockRange.$replay();
- blendedField.dispatchBlur();
- clock.tick(1);
- assertTrue('Blur must be dispatched.', blurCalled);
- assertTrue('Selection must be cleared.', cleared);
- assertEquals(
- 'Selection must be cleared in iframe', iframe.contentWindow,
- clearedWindow);
- mockRange.$verify();
- clock.dispose();
- }
- }
- function testSetMinHeight() {
- if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- var clock = new goog.testing.MockClock(true);
- try {
- var field = initSeamlessField(
- ['1', '2', '3', '4', '5', '6', '7'].join('<p/>'),
- {'position': 'relative', 'height': '60px'});
- // Initially create and size iframe.
- var iframe = createSeamlessIframe();
- field.attachIframe(iframe);
- field.iframeFieldLoadHandler(iframe, '', {});
- // Need to process timeouts set by load handlers.
- clock.tick(1000);
- var normalHeight = goog.style.getSize(iframe).height;
- var delayedChangeCalled = false;
- goog.events.listen(
- field, goog.editor.Field.EventType.DELAYEDCHANGE,
- function() { delayedChangeCalled = true; });
- // Test that min height is obeyed.
- field.setMinHeight(30);
- clock.tick(1000);
- assertEquals(
- 'Iframe height must match min height.', 30,
- goog.style.getSize(iframe).height);
- assertFalse(
- 'Setting min height must not cause delayed change event.',
- delayedChangeCalled);
- // Test that min height doesn't shrink field.
- field.setMinHeight(0);
- clock.tick(1000);
- assertEquals(normalHeight, goog.style.getSize(iframe).height);
- assertFalse(
- 'Setting min height must not cause delayed change event.',
- delayedChangeCalled);
- } finally {
- field.dispose();
- clock.dispose();
- }
- }
- }
- /**
- * @bug 1649967 This code used to throw a Javascript error.
- */
- function testSetMinHeightWithNoIframe() {
- if (goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
- try {
- var field = initSeamlessField(' ', {});
- field.makeEditable();
- field.setMinHeight(30);
- } finally {
- field.dispose();
- }
- }
- }
- function testStartChangeEvents() {
- if (goog.editor.BrowserFeature.USE_MUTATION_EVENTS) {
- var clock = new goog.testing.MockClock(true);
- try {
- var field = initSeamlessField(' ', {});
- field.makeEditable();
- var changeCalled = false;
- goog.events.listenOnce(
- field, goog.editor.Field.EventType.CHANGE,
- function() { changeCalled = true; });
- var delayedChangeCalled = false;
- goog.events.listenOnce(
- field, goog.editor.Field.EventType.CHANGE,
- function() { delayedChangeCalled = true; });
- field.stopChangeEvents(true, true);
- if (field.changeTimerGecko_) {
- field.changeTimerGecko_.start();
- }
- field.startChangeEvents();
- clock.tick(1000);
- assertFalse(changeCalled);
- assertFalse(delayedChangeCalled);
- } finally {
- clock.dispose();
- field.dispose();
- }
- }
- }
- function testManipulateDom() {
- // Test in blended field since that is what fires change events.
- var editableField = initSeamlessField(' ', {});
- var clock = new goog.testing.MockClock(true);
- var delayedChangeCalled = 0;
- goog.events.listen(
- editableField, goog.editor.Field.EventType.DELAYEDCHANGE,
- function() { delayedChangeCalled++; });
- assertFalse(editableField.isLoaded());
- editableField.manipulateDom(goog.nullFunction);
- clock.tick(1000);
- assertEquals(
- 'Must not fire delayed change events if field is not loaded.', 0,
- delayedChangeCalled);
- editableField.makeEditable();
- var usesIframe = editableField.usesIframe();
- try {
- editableField.manipulateDom(goog.nullFunction);
- clock.tick(1000); // Wait for delayed change to fire.
- assertEquals(
- 'By default must fire a single delayed change event.', 1,
- delayedChangeCalled);
- editableField.manipulateDom(goog.nullFunction, true);
- clock.tick(1000); // Wait for delayed change to fire.
- assertEquals(
- 'Must prevent all delayed change events.', 1, delayedChangeCalled);
- editableField.manipulateDom(function() {
- this.handleChange();
- this.handleChange();
- if (this.changeTimerGecko_) {
- this.changeTimerGecko_.fire();
- }
- this.dispatchDelayedChange_();
- this.delayedChangeTimer_.fire();
- }, false, editableField);
- clock.tick(1000); // Wait for delayed change to fire.
- assertEquals(
- 'Must ignore dispatch delayed change called within func.', 2,
- delayedChangeCalled);
- } finally {
- // Ensure we always uninstall the mock clock and dispose of everything.
- editableField.dispose();
- clock.dispose();
- }
- }
- function testAttachIframe() {
- var blendedField = initSeamlessField('Hi!', {});
- var iframe = createSeamlessIframe();
- try {
- blendedField.attachIframe(iframe);
- } catch (err) {
- fail('Error occurred while attaching iframe.');
- }
- }
- function createSeamlessIframe() {
- // NOTE(nicksantos): This is a reimplementation of
- // TR_EditableUtil.getIframeAttributes, but untangled for tests, and
- // specifically with what we need for blended mode.
- return goog.dom.createDom(
- goog.dom.TagName.IFRAME, {'frameBorder': '0', 'style': 'padding:0;'});
- }
- /**
- * Initialize a new editable field for the field id 'field', with the given
- * innerHTML and styles.
- *
- * @param {string} innerHTML html for the field contents.
- * @param {Object} styles Key-value pairs for styles on the field.
- * @return {goog.editor.SeamlessField} The field.
- */
- function initSeamlessField(innerHTML, styles) {
- var field = new goog.editor.SeamlessField('field');
- fieldElem.innerHTML = innerHTML;
- goog.style.setStyle(fieldElem, styles);
- return field;
- }
- /**
- * Make sure that the original field element for the given goog.editor.Field has
- * the same size before and after attaching the given iframe. If this is not
- * true, then the field will fidget while we're initializing the field,
- * and that's not what we want.
- *
- * @param {goog.editor.Field} fieldObj The field.
- * @param {HTMLIFrameElement} iframe The iframe.
- */
- function assertAttachSeamlessIframeSizesCorrectly(fieldObj, iframe) {
- var size = goog.style.getSize(fieldObj.getOriginalElement());
- fieldObj.attachIframe(iframe);
- var newSize = goog.style.getSize(fieldObj.getOriginalElement());
- assertEquals(size.width, newSize.width);
- assertEquals(size.height, newSize.height);
- }
|