aboutsummaryrefslogtreecommitdiff
path: root/contexts/data/lib/closure-library/closure/goog/editor/plugins/abstractdialogplugin_test.html
blob: 094a859c10e4ffd91f683e211e4fe87e23910117 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
<!DOCTYPE html>
<!--

  @author marcosalmeida@google.com (Marcos Almeida)
-->
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.

Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Unit Tests - goog.editor.plugins.AbstractDialogPlugin</title>
<script src="../../base.js"></script>
<script>
  goog.require('goog.dom.SavedRange');
  goog.require('goog.editor.Field');
  goog.require('goog.editor.Field.EventType');
  goog.require('goog.editor.plugins.AbstractDialogPlugin');
  goog.require('goog.editor.plugins.AbstractDialogPlugin.EventType');
  goog.require('goog.events.EventHandler');
  goog.require('goog.functions');
  goog.require('goog.testing.LooseMock');
  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.ui.editor.AbstractDialog');
  goog.require('goog.ui.editor.AbstractDialog.Builder');
  goog.require('goog.userAgent');

</script>
</head>
<body>
<script>

  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.set(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} dialogDomHelper 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(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(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} 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} 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() {
    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 = document.createElement('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);
  }

</script>
</body>
</html>