123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- // 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.AbstractDialogPluginTest');
- goog.setTestOnly('goog.editor.plugins.AbstractDialogPluginTest');
- goog.require('goog.dom');
- goog.require('goog.dom.SavedRange');
- goog.require('goog.dom.TagName');
- goog.require('goog.editor.Field');
- goog.require('goog.editor.plugins.AbstractDialogPlugin');
- goog.require('goog.events.Event');
- goog.require('goog.events.EventHandler');
- goog.require('goog.functions');
- goog.require('goog.testing.MockClock');
- goog.require('goog.testing.MockControl');
- goog.require('goog.testing.PropertyReplacer');
- goog.require('goog.testing.editor.FieldMock');
- goog.require('goog.testing.editor.TestHelper');
- goog.require('goog.testing.events');
- goog.require('goog.testing.jsunit');
- goog.require('goog.testing.mockmatchers.ArgumentMatcher');
- goog.require('goog.ui.editor.AbstractDialog');
- goog.require('goog.userAgent');
- var plugin;
- var mockCtrl;
- var mockField;
- var mockSavedRange;
- var mockOpenedHandler;
- var mockClosedHandler;
- var COMMAND = 'myCommand';
- var stubs = new goog.testing.PropertyReplacer();
- var mockClock;
- var fieldObj;
- var fieldElem;
- var mockHandler;
- function setUp() {
- mockCtrl = new goog.testing.MockControl();
- mockOpenedHandler = mockCtrl.createLooseMock(goog.events.EventHandler);
- mockClosedHandler = mockCtrl.createLooseMock(goog.events.EventHandler);
- mockField = new goog.testing.editor.FieldMock(undefined, undefined, {});
- mockCtrl.addMock(mockField);
- mockField.focus();
- plugin = createDialogPlugin();
- }
- function setUpMockRange() {
- mockSavedRange = mockCtrl.createLooseMock(goog.dom.SavedRange);
- mockSavedRange.restore();
- stubs.setPath(
- 'goog.editor.range.saveUsingNormalizedCarets',
- goog.functions.constant(mockSavedRange));
- }
- function tearDown() {
- stubs.reset();
- tearDownRealEditableField();
- if (mockClock) {
- // Crucial to letting time operations work normally in the rest of tests.
- mockClock.dispose();
- }
- if (plugin) {
- mockField.$setIgnoreUnexpectedCalls(true);
- plugin.dispose();
- }
- }
- /**
- * Creates a concrete instance of goog.ui.editor.AbstractDialog by adding
- * a plain implementation of createDialogControl().
- * @param {goog.dom.DomHelper} domHelper The dom helper to be used to
- * create the dialog.
- * @return {goog.ui.editor.AbstractDialog} The created dialog.
- */
- function createDialog(domHelper) {
- var dialog = new goog.ui.editor.AbstractDialog(domHelper);
- dialog.createDialogControl = function() {
- return new goog.ui.editor.AbstractDialog.Builder(dialog).build();
- };
- return dialog;
- }
- /**
- * Creates a concrete instance of the abstract class
- * goog.editor.plugins.AbstractDialogPlugin
- * and registers it with the mock editable field being used.
- * @return {goog.editor.plugins.AbstractDialogPlugin} The created plugin.
- */
- function createDialogPlugin() {
- var plugin = new goog.editor.plugins.AbstractDialogPlugin(COMMAND);
- plugin.createDialog = createDialog;
- plugin.returnControlToEditableField = plugin.restoreOriginalSelection;
- plugin.registerFieldObject(mockField);
- plugin.addEventListener(
- goog.editor.plugins.AbstractDialogPlugin.EventType.OPENED,
- mockOpenedHandler);
- plugin.addEventListener(
- goog.editor.plugins.AbstractDialogPlugin.EventType.CLOSED,
- mockClosedHandler);
- return plugin;
- }
- /**
- * Sets up the mock event handler to expect an OPENED event.
- */
- function expectOpened(/** number= */ opt_times) {
- mockOpenedHandler.handleEvent(
- new goog.testing.mockmatchers.ArgumentMatcher(function(arg) {
- return arg.type ==
- goog.editor.plugins.AbstractDialogPlugin.EventType.OPENED;
- }));
- mockField.dispatchSelectionChangeEvent();
- if (opt_times) {
- mockOpenedHandler.$times(opt_times);
- mockField.$times(opt_times);
- }
- }
- /**
- * Sets up the mock event handler to expect a CLOSED event.
- */
- function expectClosed(/** number= */ opt_times) {
- mockClosedHandler.handleEvent(
- new goog.testing.mockmatchers.ArgumentMatcher(function(arg) {
- return arg.type ==
- goog.editor.plugins.AbstractDialogPlugin.EventType.CLOSED;
- }));
- mockField.dispatchSelectionChangeEvent();
- if (opt_times) {
- mockClosedHandler.$times(opt_times);
- mockField.$times(opt_times);
- }
- }
- /**
- * Tests the simple flow of calling execCommand (which opens the
- * dialog) and immediately disposing of the plugin (which closes the dialog).
- * @param {boolean=} opt_reuse Whether to set the plugin to reuse its dialog.
- */
- function testExecAndDispose(opt_reuse) {
- setUpMockRange();
- expectOpened();
- expectClosed();
- mockField.debounceEvent(goog.editor.Field.EventType.SELECTIONCHANGE);
- mockCtrl.$replayAll();
- if (opt_reuse) {
- plugin.setReuseDialog(true);
- }
- assertFalse(
- 'Dialog should not be open yet',
- !!plugin.getDialog() && plugin.getDialog().isOpen());
- plugin.execCommand(COMMAND);
- assertTrue(
- 'Dialog should be open now',
- !!plugin.getDialog() && plugin.getDialog().isOpen());
- var tempDialog = plugin.getDialog();
- plugin.dispose();
- assertFalse(
- 'Dialog should not still be open after disposal', tempDialog.isOpen());
- mockCtrl.$verifyAll();
- }
- /**
- * Tests execCommand and dispose while reusing the dialog.
- */
- function testExecAndDisposeReuse() {
- testExecAndDispose(true);
- }
- /**
- * Tests the flow of calling execCommand (which opens the dialog) and
- * then hiding it (simulating that a user did somthing to cause the dialog to
- * close).
- * @param {boolean=} opt_reuse Whether to set the plugin to reuse its dialog.
- */
- function testExecAndHide(opt_reuse) {
- setUpMockRange();
- expectOpened();
- expectClosed();
- mockField.debounceEvent(goog.editor.Field.EventType.SELECTIONCHANGE);
- mockCtrl.$replayAll();
- if (opt_reuse) {
- plugin.setReuseDialog(true);
- }
- assertFalse(
- 'Dialog should not be open yet',
- !!plugin.getDialog() && plugin.getDialog().isOpen());
- plugin.execCommand(COMMAND);
- assertTrue(
- 'Dialog should be open now',
- !!plugin.getDialog() && plugin.getDialog().isOpen());
- var tempDialog = plugin.getDialog();
- plugin.getDialog().hide();
- assertFalse(
- 'Dialog should not still be open after hiding', tempDialog.isOpen());
- if (opt_reuse) {
- assertFalse(
- 'Dialog should not be disposed after hiding (will be reused)',
- tempDialog.isDisposed());
- } else {
- assertTrue(
- 'Dialog should be disposed after hiding', tempDialog.isDisposed());
- }
- plugin.dispose();
- mockCtrl.$verifyAll();
- }
- /**
- * Tests execCommand and hide while reusing the dialog.
- */
- function testExecAndHideReuse() {
- testExecAndHide(true);
- }
- /**
- * Tests the flow of calling execCommand (which opens a dialog) and
- * then calling it again before the first dialog is closed. This is not
- * something anyone should be doing since dialogs are (usually?) modal so the
- * user can't do another execCommand before closing the first dialog. But
- * since the API makes it possible, I thought it would be good to guard
- * against and unit test.
- * @param {boolean=} opt_reuse Whether to set the plugin to reuse its dialog.
- */
- function testExecTwice(opt_reuse) {
- setUpMockRange();
- if (opt_reuse) {
- expectOpened(2); // The second exec should cause a second OPENED event.
- // But the dialog was not closed between exec calls, so only one CLOSED is
- // expected.
- expectClosed();
- plugin.setReuseDialog(true);
- mockField.debounceEvent(goog.editor.Field.EventType.SELECTIONCHANGE);
- } else {
- expectOpened(2); // The second exec should cause a second OPENED event.
- // The first dialog will be disposed so there should be two CLOSED events.
- expectClosed(2);
- mockSavedRange.restore(); // Expected 2x, once already recorded in setup.
- mockField.focus(); // Expected 2x, once already recorded in setup.
- mockField.debounceEvent(goog.editor.Field.EventType.SELECTIONCHANGE);
- mockField.$times(2);
- }
- mockCtrl.$replayAll();
- assertFalse(
- 'Dialog should not be open yet',
- !!plugin.getDialog() && plugin.getDialog().isOpen());
- plugin.execCommand(COMMAND);
- assertTrue(
- 'Dialog should be open now',
- !!plugin.getDialog() && plugin.getDialog().isOpen());
- var tempDialog = plugin.getDialog();
- plugin.execCommand(COMMAND);
- if (opt_reuse) {
- assertTrue(
- 'Reused dialog should still be open after second exec',
- tempDialog.isOpen());
- assertFalse(
- 'Reused dialog should not be disposed after second exec',
- tempDialog.isDisposed());
- } else {
- assertFalse(
- 'First dialog should not still be open after opening second',
- tempDialog.isOpen());
- assertTrue(
- 'First dialog should be disposed after opening second',
- tempDialog.isDisposed());
- }
- plugin.dispose();
- mockCtrl.$verifyAll();
- }
- /**
- * Tests execCommand twice while reusing the dialog.
- */
- function testExecTwiceReuse() {
- // Test is failing with an out-of-memory error in IE7.
- if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
- return;
- }
- testExecTwice(true);
- }
- /**
- * Tests that the selection is cleared when the dialog opens and is
- * correctly restored after it closes.
- */
- function testRestoreSelection() {
- setUpRealEditableField();
- fieldObj.setHtml(false, '12345');
- var elem = fieldObj.getElement();
- var helper = new goog.testing.editor.TestHelper(elem);
- helper.select('12345', 1, '12345', 4); // Selects '234'.
- assertEquals(
- 'Incorrect text selected before dialog is opened', '234',
- fieldObj.getRange().getText());
- plugin.execCommand(COMMAND);
- if (!goog.userAgent.IE && !goog.userAgent.OPERA) {
- // IE returns some bogus range when field doesn't have selection.
- // Opera can't remove the selection from a whitebox field.
- assertNull(
- 'There should be no selection while dialog is open',
- fieldObj.getRange());
- }
- plugin.getDialog().hide();
- assertEquals(
- 'Incorrect text selected after dialog is closed', '234',
- fieldObj.getRange().getText());
- }
- /**
- * Setup a real editable field (instead of a mock) and register the plugin to
- * it.
- */
- function setUpRealEditableField() {
- fieldElem = goog.dom.createElement(goog.dom.TagName.DIV);
- fieldElem.id = 'myField';
- document.body.appendChild(fieldElem);
- fieldObj = new goog.editor.Field('myField', document);
- fieldObj.makeEditable();
- // Register the plugin to that field.
- plugin.getTrogClassId = goog.functions.constant('myClassId');
- fieldObj.registerPlugin(plugin);
- }
- /**
- * Tear down the real editable field.
- */
- function tearDownRealEditableField() {
- if (fieldObj) {
- fieldObj.makeUneditable();
- fieldObj.dispose();
- fieldObj = null;
- }
- if (fieldElem && fieldElem.parentNode == document.body) {
- document.body.removeChild(fieldElem);
- }
- }
- /**
- * Tests that after the dialog is hidden via a keystroke, the editable field
- * doesn't fire an extra SELECTIONCHANGE event due to the keyup from that
- * keystroke.
- * There is also a robot test in dialog_robot.html to test debouncing the
- * SELECTIONCHANGE event when the dialog closes.
- */
- function testDebounceSelectionChange() {
- mockClock = new goog.testing.MockClock(true);
- // Initial time is 0 which evaluates to false in debouncing implementation.
- mockClock.tick(1);
- setUpRealEditableField();
- // Set up a mock event handler to make sure selection change isn't fired
- // more than once on close and a second time on close.
- var count = 0;
- fieldObj.addEventListener(
- goog.editor.Field.EventType.SELECTIONCHANGE, function(e) { count++; });
- assertEquals(0, count);
- plugin.execCommand(COMMAND);
- assertEquals(1, count);
- plugin.getDialog().hide();
- assertEquals(2, count);
- // Fake the keyup event firing on the field after the dialog closes.
- var e = new goog.events.Event('keyup', plugin.fieldObject.getElement());
- e.keyCode = 13;
- goog.testing.events.fireBrowserEvent(e);
- // Tick the mock clock so that selection change tries to fire.
- mockClock.tick(goog.editor.Field.SELECTION_CHANGE_FREQUENCY_ + 1);
- // Ensure the handler did not fire again.
- assertEquals(2, count);
- }
|