summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler_test.unitjs
blob: aeec1fb08d5994ae3faf9d212fbeefa10f9e79ee (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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Include test fixture.
GEN_INCLUDE(['../testing/chromevox_unittest_base.js']);

GEN_INCLUDE(['../testing/fake_objects.js']);

// Fake out the Chrome API namespace we depend on.
var chrome = {};
/** Fake chrome.runtime object. */
chrome.runtime = {};
/** Fake chrome.virtualKeyboardPrivate object. */
chrome.virtualKeyboardPrivate = {};


/**
 * A fake input field that behaves like the Braille IME and also updates
 * the input manager's knowledge about the display content when text changes
 * in the edit field.
 * @param {FakePort} port A fake port.
 * @param {cvox.BrailleInputHandler} inputHandler to work with.
 * @constructor
 */
function FakeEditor(port, inputHandler) {
  /** @private {FakePort} */
  this.port_ = port;
  /** @private {cvox.BrailleInputHandler} */
  this.inputHandler_ = inputHandler;
  /** @private {string} */
  this.text_ = '';
  /** @private {number} */
  this.selectionStart_ = 0;
  /** @private {number} */
  this.selectionEnd_ = 0;
  /** @private {number} */
  this.contextID_ = 0;
  /** @private {boolean} */
  this.allowDeletes_ = false;
  /** @private {string} */
  this.uncommittedText_ = '';
  /** @private {?Array<number>} */
  this.extraCells_ = [];
  port.postMessage = goog.bind(this.handleMessage_, this);
}


/**
 * Sets the content and selection (or cursor) of the edit field.
 * This fakes what happens when the field is edited by other means than
 * via the braille keyboard.
 * @param {string} text Text to replace the current content of the field.
 * @param {number} selectionStart Start of the selection or cursor position.
 * @param {number=} opt_selectionEnd End of selection, or ommited if the
 *     selection is a cursor.
 */
FakeEditor.prototype.setContent = function(
    text, selectionStart, opt_selectionEnd) {
  this.text_ = text;
  this.selectionStart_ = selectionStart;
  this.selectionEnd_ = goog.isDef(opt_selectionEnd) ?
      opt_selectionEnd : selectionStart;
  this.callOnDisplayContentChanged_();
};


/**
 * Sets the selection in the editor.
 * @param {number} selectionStart Start of the selection or cursor position.
 * @param {number=} opt_selectionEnd End of selection, or ommited if the
 *     selection is a cursor.
 */
FakeEditor.prototype.select = function(selectionStart, opt_selectionEnd) {
  this.setContent(this.text_, selectionStart, opt_selectionEnd);
};


/**
 * Inserts text into the edit field, optionally selecting the inserted
 * text.
 * @param {string} newText Text to insert.
 * @param {boolean=} opt_select If {@code true}, selects the inserted text,
 *     otherwise leaves the cursor at the end of the new text.
 */
FakeEditor.prototype.insert = function(newText, opt_select) {
  this.text_ =
      this.text_.substring(0, this.selectionStart_) +
      newText +
      this.text_.substring(this.selectionEnd_);
  if (opt_select) {
    this.selectionEnd_ = this.selectionStart_ + newText.length;
  } else {
    this.selectionStart_ += newText.length;
    this.selectionEnd_ = this.selectionStart_;
  }
  this.callOnDisplayContentChanged_();
};


/**
 * Sets whether the editor should cause a test failure if the input handler
 * tries to delete text before the cursor.  By default, thi value is
 * {@code false}.
 * @param {boolean} allowDeletes The new value.
 */
FakeEditor.prototype.setAllowDeletes = function(allowDeletes) {
  this.allowDeletes_ = allowDeletes;
};


/**
 * Signals to the input handler that the Braille IME is active or not active,
 * depending on the argument.
 * @param {boolean} value Whether the IME is active or not.
 */
FakeEditor.prototype.setActive = function(value) {
  this.message_({type: 'activeState', active: value});
};


/**
 * Fails if the current editor content and selection range don't match
 * the arguments to this function.
 * @param {string} text Text that should be in the field.
 * @param {number} selectionStart Start of selection.
 * @param {number+} opt_selectionEnd End of selection, default to selection
 *     start to indicate a cursor.
 */
FakeEditor.prototype.assertContentIs = function(
    text, selectionStart, opt_selectionEnd) {
  var selectionEnd = goog.isDef(opt_selectionEnd) ? opt_selectionEnd :
      selectionStart;
  assertEquals(text, this.text_);
  assertEquals(selectionStart, this.selectionStart_);
  assertEquals(selectionEnd, this.selectionEnd_);
};


/**
 * Asserts that the uncommitted text last sent to the IME is the given text.
 * @param {string} text
 */
FakeEditor.prototype.assertUncommittedTextIs = function(text) {
  assertEquals(text, this.uncommittedText_);
};


/**
 * Asserts that the input handler has added 'extra cells' for uncommitted
 * text into the braille content.
 * @param {string} cells Cells as a space-separated list of numbers.
 */
FakeEditor.prototype.assertExtraCellsAre = function(cells) {
  assertEqualsJSON(cellsToArray(cells), this.extraCells_);
};


/**
 * Sends a message from the IME to the input handler.
 * @param {Object} msg The message to send.
 * @private
 */
FakeEditor.prototype.message_ = function(msg) {
  var listener = this.port_.onMessage.getListener();
  assertNotEquals(null, listener);
  listener(msg);
};


/**
 * Calls the {@code onDisplayContentChanged} method of the input handler
 * with the current editor content and selection.
 * @private
 */
FakeEditor.prototype.callOnDisplayContentChanged_ = function() {
  var content = cvox.BrailleUtil.createValue(
      this.text_, this.selectionStart_, this.selectionEnd_)
  var grabExtraCells = function() {
    var span = content.getSpanInstanceOf(cvox.ExtraCellsSpan);
    assertNotEquals(null, span);
    // Convert the ArrayBuffer to a normal array for easier comparision.
    this.extraCells_ = Array.prototype.map.call(new Uint8Array(span.cells),
                                                function(a) {return a;});
  }.bind(this);
  this.inputHandler_.onDisplayContentChanged(content, grabExtraCells);
  grabExtraCells();
};


/**
 * Informs the input handler that a new text field is focused.  The content
 * of the field is not cleared and should be updated separately.
 * @param {string} fieldType The type of the field (see the documentation
 *     for the {@code chrome.input.ime} API).
 */
FakeEditor.prototype.focus = function(fieldType) {
  this.contextID_++;
  this.message_({type: 'inputContext',
                 context: {type: fieldType,
                           contextID: this.contextID_}});
};


/**
 * Inform the input handler that focus left the input field.
 */
FakeEditor.prototype.blur = function() {
  this.message_({type: 'inputContext', context: null});
  this.contextID_ = 0;
};


/**
 * Handles a message from the input handler to the IME.
 * @param {Object} msg The message.
 * @private
 */
FakeEditor.prototype.handleMessage_ = function(msg) {
  assertEquals(this.contextID_, msg.contextID);
  switch(msg.type) {
    case 'replaceText':
      var deleteBefore = msg.deleteBefore;
      var newText = msg.newText;
      assertTrue(goog.isNumber(deleteBefore));
      assertTrue(goog.isString(newText));
      assertTrue(deleteBefore <= this.selectionStart_);
      if (deleteBefore > 0) {
        assertTrue(this.allowDeletes_);
        this.text_ =
            this.text_.substring(0, this.selectionStart_ - deleteBefore) +
            this.text_.substring(this.selectionEnd_);
        this.selectionStart_ -= deleteBefore;
        this.selectionEnd_ = this.selectionStart_;
        this.callOnDisplayContentChanged_();
      }
      this.insert(newText);
      break;
    case 'setUncommitted':
      assertTrue(goog.isString(msg.text));
      this.uncommittedText_ = msg.text;
      break;
    case 'commitUncommitted':
      this.insert(this.uncommittedText_);
      this.uncommittedText_ = '';
      break;
    default:
      throw new Error('Unexpected message to IME: ' + JSON.stringify(msg));
  }
};

/*
 * Fakes a {@code Port} used for message passing in the Chrome extension APIs.
 * @constructor
 */
function FakePort() {
  /** @type {FakeChromeEvent} */
  this.onDisconnect = new FakeChromeEvent();
  /** @type {FakeChromeEvent} */
  this.onMessage = new FakeChromeEvent();
  /** @type {string} */
  this.name = cvox.BrailleInputHandler.IME_PORT_NAME_;
  /** @type {{id: string}} */
  this.sender = {id: cvox.BrailleInputHandler.IME_EXTENSION_ID_};
}

/**
 * Mapping from braille cells to Unicode characters.
 * @const Array<Array<string> >
 */
var UNCONTRACTED_TABLE = [
  ['0', ' '],
  ['1', 'a'], ['12', 'b'], ['14', 'c'], ['145', 'd'], ['15', 'e'],
  ['124', 'f'], ['1245', 'g'], ['125', 'h'], ['24', 'i'], ['245', 'j'],
  ['13', 'k'], ['123', 'l'], ['134', 'm'], ['1345', 'n'], ['135', 'o'],
  ['1234', 'p'], ['12345', 'q'], ['1235', 'r'], ['234', 's'], ['2345', 't']
];


/**
 * Mapping of braille cells to the corresponding word in Grade 2 US English
 * braille.  This table also includes the uncontracted table above.
 * If a match 'pattern' starts with '^', it must be at the beginning of
 * the string or be preceded by a blank cell.  Similarly, '$' at the end
 * of a 'pattern' means that the match must be at the end of the string
 * or be followed by a blank cell.  Note that order is significant in the
 * table.  First match wins.
 * @const
 */
var CONTRACTED_TABLE = [
  ['12 1235 123', 'braille'],
  ['^12$', 'but'],
  ['1456', 'this']].concat(UNCONTRACTED_TABLE);

/**
 * A fake braille translator that can do back translation according
 * to one of the tables above.
 * @param {Array<Array<number>>} table Backtranslation mapping.
 * @param {boolean=} opt_capitalize Whether the result should be capitalized.
 * @constructor
 */
function FakeTranslator(table, opt_capitalize) {
  /** @private */
  this.table_ = table.map(function(entry) {
    var cells = entry[0];
    var result = [];
    if (cells[0] === '^') {
      result.start = true;
      cells = cells.substring(1);
    }
    if (cells[cells.length - 1] === '$') {
      result.end = true;
      cells = cells.substring(0, cells.length - 1);
    }
    result[0] = cellsToArray(cells);
    result[1] = entry[1];
    return result;
  });
  /** @private {boolean} */
  this.capitalize_ = opt_capitalize || false;
}


/**
 * Implements the {@code cvox.LibLouis.BrailleTranslator.backTranslate} method.
 * @param {!ArrayBuffer} cells Cells to be translated.
 * @param {function(?string)} callback Callback for result.
 */
FakeTranslator.prototype.backTranslate = function(cells, callback) {
  var cellsArray = new Uint8Array(cells);
  var result = '';
  var pos = 0;
  while (pos < cellsArray.length) {
    var match = null;
    outer: for (var i = 0, entry; entry = this.table_[i]; ++i) {
      if (pos + entry[0].length > cellsArray.length) {
        continue;
      }
      if (entry.start && pos > 0 && cellsArray[pos - 1] !== 0) {
        continue;
      }
      for (var j = 0; j < entry[0].length; ++j) {
        if (entry[0][j] !== cellsArray[pos + j]) {
          continue outer;
        }
      }
      if (entry.end && pos + j < cellsArray.length &&
          cellsArray[pos + j] !== 0) {
        continue;
      }
      match = entry;
      break;
    }
    assertNotEquals(
        null, match,
        'Backtranslating ' + cellsArray[pos] + ' at ' + pos);
    result += match[1];
    pos += match[0].length;
  }
  if (this.capitalize_) {
    result = result.toUpperCase();
  }
  callback(result);
};

/** @extends {cvox.BrailleTranslatorManager} */
function FakeTranslatorManager() {
}

FakeTranslatorManager.prototype = {
  defaultTranslator: null,
  uncontractedTranslator: null,
  changeListener: null,

  /** @override */
  getDefaultTranslator: function() {
    return this.defaultTranslator;
  },

  /** @override */
  getUncontractedTranslator: function() {
    return this.uncontractedTranslator;
  },

  /** @override */
  addChangeListener: function(listener) {
    assertEquals(null, this.changeListener);
  },

  setTranslators: function(defaultTranslator, uncontractedTranslator) {
    this.defaultTranslator = defaultTranslator;
    this.uncontractedTranslator = uncontractedTranslator;
    if (this.changeListener) {
      this.changeListener();
    }
  }
};

/**
 * Converts a list of cells, represented as a string, to an array.
 * @param {string} cells A string with space separated groups of digits.
 *     Each group corresponds to one braille cell and each digit in a group
 *     corresponds to a particular dot in the cell (1 to 8).  As a special
 *     case, the digit 0 by itself represents a blank cell.
 * @return {Array<number>} An array with each cell encoded as a bit
 *     pattern (dot 1 uses bit 0, etc).
 */
function cellsToArray(cells) {
  if (!cells)
    return [];
  return cells.split(/\s+/).map(function(cellString) {
    var cell = 0;
    assertTrue(cellString.length > 0);
    if (cellString != '0') {
      for (var i = 0; i < cellString.length; ++i) {
        var dot = cellString.charCodeAt(i) - '0'.charCodeAt(0);
        assertTrue(dot >= 1);
        assertTrue(dot <= 8);
        cell |= 1 << (dot - 1);
      }
    }
    return cell;
  });
}

/**
 * Test fixture.
 * @constructor
 * @extends {ChromeVoxUnitTestBase}
 */
function CvoxBrailleInputHandlerUnitTest() {}

CvoxBrailleInputHandlerUnitTest.prototype = {
  __proto__: ChromeVoxUnitTestBase.prototype,

  /** @override */
  closureModuleDeps: [
    'cvox.BrailleInputHandler',
    'cvox.BrailleUtil',
  ],

  /**
   * Creates an editor and establishes a connection from the IME.
   * @return {FakeEditor}
   */
  createEditor: function() {
    chrome.runtime.onConnectExternal.getListener()(this.port);
    return new FakeEditor(this.port, this.inputHandler);
  },

  /**
   * Sends a series of braille cells to the input handler.
   * @param {string} cells Braille cells, encoded as described in
   *     {@code cellsToArray}.
   * @return {boolean} {@code true} iff all cells were sent successfully.
   */
  sendCells: function(cells) {
    return cellsToArray(cells).reduce(function(prevResult, cell) {
      var event = {command: cvox.BrailleKeyCommand.DOTS, brailleDots: cell};
      return prevResult && this.inputHandler.onBrailleKeyEvent(event);
    }.bind(this), true);
  },

  /**
   * Sends a standard key event (such as backspace) to the braille input
   * handler.
   * @param {string} keyCode The key code name.
   * @return {boolean} Whether the event was handled.
   */
  sendKeyEvent: function(keyCode) {
    var event = {command: cvox.BrailleKeyCommand.STANDARD_KEY,
                 standardKeyCode: keyCode};
    return this.inputHandler.onBrailleKeyEvent(event);
  },

  /**
   * Shortcut for asserting that the value expansion mode is {@code NONE}.
   */
  assertExpandingNone: function() {
    assertEquals(cvox.ExpandingBrailleTranslator.ExpansionType.NONE,
                 this.inputHandler.getExpansionType());
  },

  /**
   * Shortcut for asserting that the value expansion mode is {@code SELECTION}.
   */
  assertExpandingSelection: function() {
    assertEquals(cvox.ExpandingBrailleTranslator.ExpansionType.SELECTION,
                 this.inputHandler.getExpansionType());
  },

  /**
   * Shortcut for asserting that the value expansion mode is {@code ALL}.
   */
  assertExpandingAll: function() {
    assertEquals(cvox.ExpandingBrailleTranslator.ExpansionType.ALL,
                 this.inputHandler.getExpansionType());
  },

  storeKeyEvent: function(event, opt_callback) {
    var storedCopy = {keyCode: event.keyCode, keyName: event.keyName,
                      charValue: event.charValue};
    if (event.type == 'keydown') {
      this.keyEvents.push(storedCopy);
    } else {
      assertEquals('keyup', event.type);
      assertTrue(this.keyEvents.length > 0);
      assertEqualsJSON(storedCopy, this.keyEvents[this.keyEvents.length - 1]);
    }
    if (goog.isDef(opt_callback)) {
      callback();
    }
  },

  /** @override */
  setUp: function() {
    chrome.runtime.onConnectExternal = new FakeChromeEvent();
    this.port = new FakePort();
    this.translatorManager = new FakeTranslatorManager();
    this.inputHandler = new cvox.BrailleInputHandler(this.translatorManager);
    this.inputHandler.init();
    this.uncontractedTranslator = new FakeTranslator(UNCONTRACTED_TABLE);
    this.contractedTranslator = new FakeTranslator(CONTRACTED_TABLE, true);
    chrome.virtualKeyboardPrivate.sendKeyEvent =
      this.storeKeyEvent.bind(this);
    this.keyEvents = [];
  }
};

TEST_F('CvoxBrailleInputHandlerUnitTest', 'ConnectFromUnknownExtension',
  function() {
  this.port.sender.id = 'your unknown friend';
  chrome.runtime.onConnectExternal.getListener()(this.port);
  this.port.onMessage.assertNoListener();
});


TEST_F('CvoxBrailleInputHandlerUnitTest', 'NoTranslator', function() {
  var editor = this.createEditor();
  editor.setContent('blah', 0);
  editor.setActive(true);
  editor.focus('email');
  assertFalse(this.sendCells('145 135 125'));
  editor.setActive(false);
  editor.blur();
  editor.assertContentIs('blah', 0);
});


TEST_F('CvoxBrailleInputHandlerUnitTest', 'InputUncontracted', function() {
  this.translatorManager.setTranslators(this.uncontractedTranslator, null);
  var editor = this.createEditor();
  editor.setActive(true);

  // Focus and type in a text field.
  editor.focus('text');
  assertTrue(this.sendCells('125 15 123 123 135'));  // hello
  editor.assertContentIs('hello', 'hello'.length);
  this.assertExpandingNone();

  // Move the cursor and type in the middle.
  editor.select(2);
  assertTrue(this.sendCells('0 2345 125 15 1235 15 0'));  // ' there '
  editor.assertContentIs('he there llo', 'he there '.length);

  // Field changes by some other means.
  editor.insert('you!');
  // Then type on the braille keyboard again.
  assertTrue(this.sendCells('0 125 15'));  // ' he'
  editor.assertContentIs('he there you! hello', 'he there you! he'.length);

  editor.blur();
  editor.setActive(false);
});


TEST_F('CvoxBrailleInputHandlerUnitTest', 'InputContracted', function() {
  var editor = this.createEditor();
  this.translatorManager.setTranslators(this.contractedTranslator,
                                        this.uncontractedTranslator);
  editor.setContent('', 0);
  editor.setActive(true);
  editor.focus('text');
  this.assertExpandingSelection();

  // First, type a 'b'.
  assertTrue(this.sendCells('12'));
  editor.assertContentIs('', 0);
  // Remember that the contracted translator produces uppercase.
  editor.assertUncommittedTextIs('BUT');
  editor.assertExtraCellsAre('12');
  this.assertExpandingNone();

  // Typing 'rl' changes to a different contraction.
  assertTrue(this.sendCells('1235 123'));
  editor.assertUncommittedTextIs('BRAILLE');
  editor.assertContentIs('', 0);
  editor.assertExtraCellsAre('12 1235 123');
  this.assertExpandingNone();

  // Now, finish the word.
  assertTrue(this.sendCells('0'));
  editor.assertContentIs('BRAILLE ', 'BRAILLE '.length);
  editor.assertUncommittedTextIs('');
  editor.assertExtraCellsAre('');
  this.assertExpandingSelection();

  // Move the cursor to the beginning.
  editor.select(0);
  this.assertExpandingSelection();

  // Typing now uses the uncontracted table.
  assertTrue(this.sendCells('12'));  // 'b'
  editor.assertContentIs('bBRAILLE ', 1);
  this.assertExpandingSelection();
  editor.select('bBRAILLE'.length);
  this.assertExpandingSelection();
  assertTrue(this.sendCells('12')); // 'b'
  editor.assertContentIs('bBRAILLEb ', 'bBRAILLEb'.length);
  // Move to the end, where contracted typing should work.
  editor.select('bBRAILLEb '.length);
  assertTrue(this.sendCells('1456 0'));  // Symbol for 'this', then space.
  this.assertExpandingSelection();
  editor.assertContentIs('bBRAILLEb THIS ', 'bBRAILLEb THIS '.length);

  // Move to between the two words.
  editor.select('bBRAILLEb'.length);
  this.assertExpandingSelection();
  assertTrue(this.sendCells('0 12'));  // Space plus 'b' for 'but'
  editor.assertUncommittedTextIs('BUT');
  editor.assertExtraCellsAre('12');
  editor.assertContentIs('bBRAILLEb  THIS ', 'bBRAILLEb '.length);
  this.assertExpandingNone();
});


TEST_F('CvoxBrailleInputHandlerUnitTest', 'TypingUrlWithContracted',
       function() {
  var editor = this.createEditor();
  this.translatorManager.setTranslators(this.contractedTranslator,
                                        this.uncontractedTranslator);
  editor.setActive(true);
  editor.focus('url');
  this.assertExpandingAll();
  assertTrue(this.sendCells('1245'));  // 'g'
  editor.insert('oogle.com', true /*select*/);
  editor.assertContentIs('google.com', 1, 'google.com'.length);
  this.assertExpandingAll();
  this.sendCells('135');  // 'o'
  editor.insert('ogle.com', true /*select*/);
  editor.assertContentIs('google.com', 2, 'google.com'.length);
  this.assertExpandingAll();
  this.sendCells('0');
  editor.assertContentIs('go ', 'go '.length);
  // In a URL, even when the cursor is in whitespace, all of the value
  // is expanded to uncontracted braille.
  this.assertExpandingAll();
});


TEST_F('CvoxBrailleInputHandlerUnitTest', 'Backspace', function() {
  var editor = this.createEditor();
  this.translatorManager.setTranslators(this.contractedTranslator,
                                        this.uncontractedTranslator);
  editor.setActive(true);
  editor.focus('text');

  // Add some text that we can delete later.
  editor.setContent('Text ', 'Text '.length);

  // Type 'brl' to make sure replacement works when deleting text.
  assertTrue(this.sendCells('12 1235 123'));
  editor.assertUncommittedTextIs('BRAILLE');

  // Delete what we just typed, one cell at a time.
  this.sendKeyEvent('Backspace');
  editor.assertUncommittedTextIs('BR');
  this.sendKeyEvent('Backspace');
  editor.assertUncommittedTextIs('BUT');
  this.sendKeyEvent('Backspace');
  editor.assertUncommittedTextIs('');

  // Now, backspace should be handled as usual, synthetizing key events.
  assertEquals(0, this.keyEvents.length);
  this.sendKeyEvent('Backspace');
  assertEqualsJSON([{keyCode: 8, keyName: 'Backspace', charValue: 8}],
                   this.keyEvents);
});


TEST_F('CvoxBrailleInputHandlerUnitTest', 'KeysImeNotActive', function() {
  var editor = this.createEditor();
  this.sendKeyEvent('Enter');
  this.sendKeyEvent('ArrowUp');
  assertEqualsJSON([{keyCode: 13, keyName: 'Enter', charValue: 0x0A},
                    {keyCode: 38, keyName: 'ArrowUp', charValue: 0x41}],
                   this.keyEvents);
});