blob: 3073be4097878a50555b941337247ff29f6ce624 [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.AbstractBubblePluginTest');
goog.setTestOnly('goog.editor.plugins.AbstractBubblePluginTest');
goog.require('goog.dom');
goog.require('goog.editor.plugins.AbstractBubblePlugin');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.functions');
goog.require('goog.style');
goog.require('goog.testing.editor.FieldMock');
goog.require('goog.testing.editor.TestHelper');
goog.require('goog.testing.events');
goog.require('goog.testing.events.Event');
goog.require('goog.testing.jsunit');
goog.require('goog.ui.editor.Bubble');
goog.require('goog.userAgent');
var testHelper;
var fieldDiv;
var COMMAND = 'base';
var fieldMock;
var bubblePlugin;
var link;
var link2;
function setUpPage() {
fieldDiv = goog.dom.getElement('field');
var viewportSize = goog.dom.getViewportSize();
// Some tests depends on enough size of viewport.
if (viewportSize.width < 600 || viewportSize.height < 440) {
window.moveTo(0, 0);
window.resizeTo(640, 480);
}
}
function setUp() {
testHelper = new goog.testing.editor.TestHelper(fieldDiv);
testHelper.setUpEditableElement();
fieldMock = new goog.testing.editor.FieldMock();
bubblePlugin = new goog.editor.plugins.AbstractBubblePlugin(COMMAND);
bubblePlugin.fieldObject = fieldMock;
fieldDiv.innerHTML = '<a href="http://www.google.com">Google</a>' +
'<a href="http://www.google.com">Google2</a>';
link = fieldDiv.firstChild;
link2 = fieldDiv.lastChild;
window.scrollTo(0, 0);
goog.style.setStyle(document.body, 'direction', 'ltr');
goog.style.setStyle(document.getElementById('field'), 'position', 'static');
}
function tearDown() {
bubblePlugin.closeBubble();
testHelper.tearDownEditableElement();
}
/**
* This is a helper function for setting up the targetElement with a
* given direction.
*
* @param {string} dir The direction of the targetElement, 'ltr' or 'rtl'.
*/
function prepareTargetWithGivenDirection(dir) {
goog.style.setStyle(document.body, 'direction', dir);
fieldDiv.style.direction = dir;
fieldDiv.innerHTML = '<a href="http://www.google.com">Google</a>';
link = fieldDiv.firstChild;
fieldMock.$replay();
bubblePlugin.createBubbleContents = function(bubbleContainer) {
bubbleContainer.innerHTML = '<div style="border:1px solid blue;">B</div>';
goog.style.setStyle(bubbleContainer, 'border', '1px solid white');
};
bubblePlugin.registerFieldObject(fieldMock);
bubblePlugin.enable(fieldMock);
bubblePlugin.createBubble(link);
}
/**
* Similar in intent to mock reset, but implemented by recreating the mock
* variable. $reset() can't work because it will reset general any-time
* expectations done in the fieldMock constructor.
*/
function resetFieldMock() {
fieldMock = new goog.testing.editor.FieldMock();
bubblePlugin.fieldObject = fieldMock;
}
function helpTestCreateBubble(opt_fn) {
fieldMock.$replay();
var numCalled = 0;
bubblePlugin.createBubbleContents = function(bubbleContainer) {
numCalled++;
assertNotNull('bubbleContainer should not be null', bubbleContainer);
};
if (opt_fn) {
opt_fn();
}
bubblePlugin.createBubble(link);
assertEquals('createBubbleContents should be called', 1, numCalled);
fieldMock.$verify();
}
function testCreateBubble(opt_fn) {
helpTestCreateBubble(opt_fn);
assertTrue(bubblePlugin.getSharedBubble_() instanceof goog.ui.editor.Bubble);
assertTrue('Bubble should be visible', bubblePlugin.isVisible());
}
function testOpeningBubbleCallsOnShow() {
var numCalled = 0;
testCreateBubble(function() {
bubblePlugin.onShow = function() {
numCalled++;
};
});
assertEquals('onShow should be called', 1, numCalled);
fieldMock.$verify();
}
function testCloseBubble() {
testCreateBubble();
bubblePlugin.closeBubble();
assertFalse('Bubble should not be visible', bubblePlugin.isVisible());
fieldMock.$verify();
}
function testZindexBehavior() {
// Don't use the default return values.
fieldMock.$reset();
fieldMock.getAppWindow().$anyTimes().$returns(window);
fieldMock.getEditableDomHelper().$anyTimes()
.$returns(goog.dom.getDomHelper(document));
fieldMock.getBaseZindex().$returns(2);
bubblePlugin.createBubbleContents = goog.nullFunction;
fieldMock.$replay();
bubblePlugin.createBubble(link);
assertEquals('2',
'' + bubblePlugin.getSharedBubble_().bubbleContainer_.style.zIndex);
fieldMock.$verify();
}
function testNoTwoBubblesOpenAtSameTime() {
fieldMock.$replay();
var origClose = goog.bind(bubblePlugin.closeBubble, bubblePlugin);
var numTimesCloseCalled = 0;
bubblePlugin.closeBubble = function() {
numTimesCloseCalled++;
origClose();
};
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
bubblePlugin.createBubbleContents = goog.nullFunction;
bubblePlugin.handleSelectionChangeInternal(link);
assertEquals(0, numTimesCloseCalled);
assertEquals(link, bubblePlugin.targetElement_);
fieldMock.$verify();
bubblePlugin.handleSelectionChangeInternal(link2);
assertEquals(1, numTimesCloseCalled);
assertEquals(link2, bubblePlugin.targetElement_);
fieldMock.$verify();
}
function testHandleSelectionChangeWithEvent() {
fieldMock.$replay();
var fakeEvent =
new goog.events.BrowserEvent({type: 'mouseup', target: link});
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
bubblePlugin.createBubbleContents = goog.nullFunction;
bubblePlugin.handleSelectionChange(fakeEvent);
assertTrue('Bubble should have been opened', bubblePlugin.isVisible());
assertEquals('Bubble target should be provided event\'s target',
link, bubblePlugin.targetElement_);
}
function testHandleSelectionChangeWithTarget() {
fieldMock.$replay();
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
bubblePlugin.createBubbleContents = goog.nullFunction;
bubblePlugin.handleSelectionChange(undefined, link2);
assertTrue('Bubble should have been opened', bubblePlugin.isVisible());
assertEquals('Bubble target should be provided target',
link2, bubblePlugin.targetElement_);
}
/**
* Regression test for @bug 2945341
*/
function testSelectOneTextCharacterNoError() {
fieldMock.$replay();
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
bubblePlugin.createBubbleContents = goog.nullFunction;
// Select first char of first link's text node.
testHelper.select(link.firstChild, 0, link.firstChild, 1);
// This should execute without js errors.
bubblePlugin.handleSelectionChange();
assertTrue('Bubble should have been opened', bubblePlugin.isVisible());
fieldMock.$verify();
}
function testTabKeyEvents() {
fieldMock.$replay();
bubblePlugin.enableKeyboardNavigation(true);
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
var nonTabbable1, tabbable1, tabbable2, nonTabbable2;
bubblePlugin.createBubbleContents = function(container) {
nonTabbable1 = goog.dom.createDom('div');
tabbable1 = goog.dom.createDom('div');
tabbable2 = goog.dom.createDom('div');
nonTabbable2 = goog.dom.createDom('div');
goog.dom.append(
container, nonTabbable1, tabbable1, tabbable2, nonTabbable2);
bubblePlugin.setTabbable(tabbable1);
bubblePlugin.setTabbable(tabbable2);
};
bubblePlugin.handleSelectionChangeInternal(link);
assertTrue('Bubble should be visible', bubblePlugin.isVisible());
var tabHandledByBubble = simulateTabKeyOnBubble();
assertTrue('The action should be handled by the plugin', tabHandledByBubble);
assertFocused(tabbable1);
// Tab on the first tabbable. The test framework doesn't easily let us verify
// the desired behavior - namely, that the second tabbable gets focused - but
// we verify that the field doesn't get the focus.
goog.testing.events.fireKeySequence(tabbable1, goog.events.KeyCodes.TAB);
fieldMock.$verify();
// Tabbing on the last tabbable should trigger focus() of the target field.
resetFieldMock();
fieldMock.focus();
fieldMock.$replay();
goog.testing.events.fireKeySequence(tabbable2, goog.events.KeyCodes.TAB);
fieldMock.$verify();
}
function testTabKeyEventsWithShiftKey() {
fieldMock.$replay();
bubblePlugin.enableKeyboardNavigation(true);
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
var nonTabbable, tabbable1, tabbable2;
bubblePlugin.createBubbleContents = function(container) {
nonTabbable = goog.dom.createDom('div');
tabbable1 = goog.dom.createDom('div');
// The test acts only on one tabbable, but we give another one to make sure
// that the tabbable we act on is not also the last.
tabbable2 = goog.dom.createDom('div');
goog.dom.append(container, nonTabbable, tabbable1, tabbable2);
bubblePlugin.setTabbable(tabbable1);
bubblePlugin.setTabbable(tabbable2);
};
bubblePlugin.handleSelectionChangeInternal(link);
assertTrue('Bubble should be visible', bubblePlugin.isVisible());
var tabHandledByBubble = simulateTabKeyOnBubble();
assertTrue('The action should be handled by the plugin', tabHandledByBubble);
assertFocused(tabbable1);
fieldMock.$verify();
// Shift-tabbing on the first tabbable should trigger focus() of the target
// field.
resetFieldMock();
fieldMock.focus();
fieldMock.$replay();
goog.testing.events.fireKeySequence(
tabbable1, goog.events.KeyCodes.TAB, {shiftKey: true});
fieldMock.$verify();
}
function testLinksAreTabbable() {
fieldMock.$replay();
bubblePlugin.enableKeyboardNavigation(true);
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
var nonTabbable1, link1, link2, nonTabbable2;
bubblePlugin.createBubbleContents = function(container) {
nonTabbable1 = goog.dom.createDom('div');
goog.dom.appendChild(container, nonTabbable1);
bubbleLink1 = this.createLink('linkInBubble1', 'Foo', false, container);
bubbleLink2 = this.createLink('linkInBubble2', 'Bar', false, container);
nonTabbable2 = goog.dom.createDom('div');
goog.dom.appendChild(container, nonTabbable2);
};
bubblePlugin.handleSelectionChangeInternal(link);
assertTrue('Bubble should be visible', bubblePlugin.isVisible());
var tabHandledByBubble = simulateTabKeyOnBubble();
assertTrue('The action should be handled by the plugin', tabHandledByBubble);
assertFocused(bubbleLink1);
fieldMock.$verify();
// Tabbing on the last link should trigger focus() of the target field.
resetFieldMock();
fieldMock.focus();
fieldMock.$replay();
goog.testing.events.fireKeySequence(bubbleLink2, goog.events.KeyCodes.TAB);
fieldMock.$verify();
}
function testTabKeyNoEffectKeyboardNavDisabled() {
fieldMock.$replay();
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
var bubbleLink;
bubblePlugin.createBubbleContents = function(container) {
bubbleLink = this.createLink('linkInBubble', 'Foo', false, container);
};
bubblePlugin.handleSelectionChangeInternal(link);
assertTrue('Bubble should be visible', bubblePlugin.isVisible());
var tabHandledByBubble = simulateTabKeyOnBubble();
assertFalse('The action should not be handled by the plugin',
tabHandledByBubble);
assertNotFocused(bubbleLink);
// Verify that tabbing the link doesn't cause focus of the field.
goog.testing.events.fireKeySequence(bubbleLink, goog.events.KeyCodes.TAB);
fieldMock.$verify();
}
function testOtherKeyEventNoEffectKeyboardNavEnabled() {
fieldMock.$replay();
bubblePlugin.enableKeyboardNavigation(true);
bubblePlugin.getBubbleTargetFromSelection = goog.functions.identity;
var bubbleLink;
bubblePlugin.createBubbleContents = function(container) {
bubbleLink = this.createLink('linkInBubble', 'Foo', false, container);
};
bubblePlugin.handleSelectionChangeInternal(link);
assertTrue('Bubble should be visible', bubblePlugin.isVisible());
// Test pressing CTRL + B: this should not have any effect.
var keyHandledByBubble =
simulateKeyDownOnBubble(goog.events.KeyCodes.B, true);
assertFalse('The action should not be handled by the plugin',
keyHandledByBubble);
assertNotFocused(bubbleLink);
fieldMock.$verify();
}
function testSetTabbableSetsTabIndex() {
var element1 = goog.dom.createDom('div');
var element2 = goog.dom.createDom('div');
element1.setAttribute('tabIndex', '1');
bubblePlugin.setTabbable(element1);
bubblePlugin.setTabbable(element2);
assertEquals('1', element1.getAttribute('tabIndex'));
assertEquals('0', element2.getAttribute('tabIndex'));
}
function testDisable() {
testCreateBubble();
fieldMock.setUneditable(true);
bubblePlugin.disable(fieldMock);
bubblePlugin.closeBubble();
}
/**
* Sends a tab key event to the bubble.
* @return {boolean} whether the bubble hanlded the event.
*/
function simulateTabKeyOnBubble() {
return simulateKeyDownOnBubble(goog.events.KeyCodes.TAB, false);
}
/**
* Sends a key event to the bubble.
* @param {number} keyCode
* @param {boolean} isCtrl
* @return {boolean} whether the bubble hanlded the event.
*/
function simulateKeyDownOnBubble(keyCode, isCtrl) {
// In some browsers (e.g. FireFox) the editable field is marked with
// designMode on. In the test setting (and not in production setting), the
// bubble element shares the same window and hence the designMode. In this
// mode, activeElement remains the <body> and isn't changed along with the
// focus as a result of tab key.
bubblePlugin.getSharedBubble_().getContentElement().
ownerDocument.designMode = 'off';
var event =
new goog.testing.events.Event(goog.events.EventType.KEYDOWN, null);
event.keyCode = keyCode;
event.ctrlKey = isCtrl;
return bubblePlugin.handleKeyDown(event);
}
function assertFocused(element) {
// The activeElement assertion below doesn't work in IE7. At this time IE7 is
// no longer supported by any client product, so we don't care.
if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(8)) {
return;
}
assertEquals('unexpected focus', element, document.activeElement);
}
function assertNotFocused(element) {
assertNotEquals('unexpected focus', element, document.activeElement);
}