// Copyright 2006 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.ui.ac.AutoCompleteTest');
goog.setTestOnly('goog.ui.ac.AutoCompleteTest');

goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.Role');
goog.require('goog.dom');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.string');
goog.require('goog.testing.MockControl');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.mockmatchers');
goog.require('goog.ui.ac.AutoComplete');
goog.require('goog.ui.ac.InputHandler');
goog.require('goog.ui.ac.RenderOptions');
goog.require('goog.ui.ac.Renderer');



/**
 * Mock DataStore
 * @constructor
 */
function MockDS(opt_autoHilite) {
  this.autoHilite_ = opt_autoHilite;
  var disabledRow = {
    match: function(str) {return this.text.match(str);},
    rowDisabled: true,
    text: 'hello@u.edu'
  };
  this.rows_ = [
    '"Slartibartfast Theadore" <fjordmaster@magrathea.com>',
    '"Zaphod Beeblebrox" <theprez@universe.gov>',
    '"Ford Prefect" <ford@theguide.com>',
    '"Arthur Dent" <has.no.tea@gmail.com>',
    '"Marvin The Paranoid Android" <marv@googlemail.com>',
    'the.mice@magrathea.com',
    'the.mice@myotherdomain.com',
    'hello@a.com',
    disabledRow,
    'row@u.edu',
    'person@a.edu'
  ];
  this.isRowDisabled = function(row) {
    return !!row.rowDisabled;
  };
}

MockDS.prototype.requestMatchingRows = function(token, maxMatches,
                                                matchHandler) {
  var escapedToken = goog.string.regExpEscape(token);
  var matcher = new RegExp('(^|\\W+)' + escapedToken);
  var matches = [];
  for (var i = 0; i < this.rows_.length && matches.length < maxMatches; ++i) {
    var row = this.rows_[i];
    if (row.match(matcher)) {
      matches.push(row);
    }
  }
  if (this.autoHilite_ === undefined) {
    matchHandler(token, matches);
  } else {
    var options = new goog.ui.ac.RenderOptions();
    options.setAutoHilite(this.autoHilite_);
    matchHandler(token, matches, options);
  }
};


/**
 * Mock Selection Handler
 */

function MockSelect() {
}
goog.inherits(MockSelect, goog.events.EventTarget);

MockSelect.prototype.selectRow = function(row) {
  this.selectedRow = row;
};



/**
 * Renderer subclass that exposes additional private members for testing.
 * @constructor
 */
function TestRend() {
  goog.ui.ac.Renderer.call(this, goog.dom.getElement('test-area'));
}
goog.inherits(TestRend, goog.ui.ac.Renderer);

TestRend.prototype.getRenderedRows = function() {
  return this.rows_;
};

TestRend.prototype.getHilitedRowIndex = function() {
  return this.hilitedRow_;
};

TestRend.prototype.getHilitedRowDiv = function() {
  return this.rowDivs_[this.hilitedRow_];
};

TestRend.prototype.getRowDiv = function(index) {
  return this.rowDivs_[index];
};

var handler;
var inputElement;
var mockControl;

function setUp() {
  inputElement = goog.dom.createDom('input', {type: 'text'});
  handler = new goog.events.EventHandler();
  mockControl = new goog.testing.MockControl();
}

function tearDown() {
  handler.dispose();
  mockControl.$tearDown();
  goog.dom.removeChildren(goog.dom.getElement('test-area'));
}


/**
 * Make sure results are truncated (or not) by setMaxMatches.
 */
function testMaxMatches() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);

  ac.setMaxMatches(2);
  ac.setToken('the');
  assertEquals(2, rend.getRenderedRows().length);
  ac.setToken('');

  ac.setMaxMatches(3);
  ac.setToken('the');
  assertEquals(3, rend.getRenderedRows().length);
  ac.setToken('');

  ac.setMaxMatches(1000);
  ac.setToken('the');
  assertEquals(4, rend.getRenderedRows().length);
  ac.setToken('');
}

function testHiliteViaMouse() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var updates = 0;
  var row = null;
  var rowNode = null;
  handler.listen(rend,
      goog.ui.ac.AutoComplete.EventType.ROW_HILITE,
      function(evt) {
        updates++;
        rowNode = evt.rowNode;
      });
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setMaxMatches(4);
  ac.setToken('the');
  // Need to set the startRenderingRows_ time to something long ago, otherwise
  // the mouse event will not be fired.  (The autocomplete logic waits for some
  // time to pass after rendering before firing mouseover events.)
  rend.startRenderingRows_ = -1;
  var hilitedRowDiv = rend.getRowDiv(3);
  goog.testing.events.fireMouseOverEvent(hilitedRowDiv);
  assertEquals(2, updates);
  assertTrue(goog.string.contains(rowNode.innerHTML, 'mice@myotherdomain.com'));
}

function testMouseClickBeforeHilite() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setMaxMatches(4);
  ac.setToken('the');
  // Need to set the startRenderingRows_ time to something long ago, otherwise
  // the mouse event will not be fired.  (The autocomplete logic waits for some
  // time to pass after rendering before firing mouseover events.)
  rend.startRenderingRows_ = -1;

  // hilite row 3...
  var hilitedRowDiv = rend.getRowDiv(3);
  goog.testing.events.fireMouseOverEvent(hilitedRowDiv);

  // but click row 2, to simulate mouse getting ahead of focus.
  var targetRowDiv = rend.getRowDiv(2);
  goog.testing.events.fireClickEvent(targetRowDiv);

  assertEquals('the.mice@magrathea.com', select.selectedRow);
}

function testMouseClickOnFirstRowBeforeHilite() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setAutoHilite(false);
  ac.setMaxMatches(4);
  ac.setToken('the');

  // Click the first row before highlighting it, to simulate mouse getting ahead
  // of focus.
  var targetRowDiv = rend.getRowDiv(0);
  goog.testing.events.fireClickEvent(targetRowDiv);

  assertEquals(
      '"Zaphod Beeblebrox" <theprez@universe.gov>', select.selectedRow);
}

function testMouseClickOnRowAfterBlur() {
  var ds = new MockDS();
  var rend = new TestRend();
  var ih = new goog.ui.ac.InputHandler();
  ih.attachInput(inputElement);

  var ac = new goog.ui.ac.AutoComplete(ds, rend, ih);
  goog.testing.events.fireFocusEvent(inputElement);
  ac.setToken('the');
  var targetRowDiv = rend.getRowDiv(0);

  // Simulate the user clicking on an autocomplete row in the short time between
  // blur and autocomplete dismissal.
  goog.testing.events.fireBlurEvent(inputElement);
  assertNotThrows(function() {
    goog.testing.events.fireClickEvent(targetRowDiv);
  });
}


/*
 * Send AutoComplete a SELECT event with empty string for the row. We can't
 * simulate with a simple mouse click, so we dispatch the event directly.
 */
function testSelectEventEmptyRow() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setMaxMatches(4);
  ac.setToken('the');
  rend.startRenderingRows_ = -1;

  // hilight row 2 ('the.mice@...')
  var hilitedRowDiv = rend.getRowDiv(2);
  goog.testing.events.fireMouseOverEvent(hilitedRowDiv);
  assertUndefined(select.selectedRow);

  // Dispatch an event that does not specify a row.
  rend.dispatchEvent({
    type: goog.ui.ac.AutoComplete.EventType.SELECT,
    row: ''
  });

  assertEquals('the.mice@magrathea.com', select.selectedRow);
}

function testSuggestionsUpdateEvent() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  var updates = 0;
  handler.listen(ac,
      goog.ui.ac.AutoComplete.EventType.SUGGESTIONS_UPDATE,
      function() {
        updates++;
      });

  ac.setToken('the');
  assertEquals(1, updates);

  ac.setToken('beeb');
  assertEquals(2, updates);

  ac.setToken('ford');
  assertEquals(3, updates);

  ac.dismiss();
  assertEquals(4, updates);

  ac.setToken('dent');
  assertEquals(5, updates);
}

function checkHilitedIndex(renderer, index) {
  assertEquals(index, renderer.getHilitedRowIndex());
}

function testGetRowCount() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  assertEquals(0, ac.getRowCount());

  ac.setToken('Zaphod');
  assertEquals(1, ac.getRowCount());

  ac.setMaxMatches(2);
  ac.setToken('the');
  assertEquals(2, ac.getRowCount());
}


/**
 * Try using next and prev to navigate past the ends with default behavior of
 * allowFreeSelect_ and wrap_.
 */
function testHiliteNextPrev_default() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);

  var updates = 0;
  handler.listen(rend,
      goog.ui.ac.AutoComplete.EventType.ROW_HILITE,
      function() {
        updates++;
      });

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('the');
    assertEquals(4, rend.getRenderedRows().length);
    // check to see if we can select the last of the 4 items
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    ac.hiliteNext();
    checkHilitedIndex(rend, 3);
    // try going over the edge
    ac.hiliteNext();
    checkHilitedIndex(rend, 3);

    // go back down
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
  }
  // 21 changes in the loop above (3 * 7)
  assertEquals(21, updates);
}


/**
 * Try using next and prev to navigate past the ends with default behavior of
 * allowFreeSelect_ and wrap_ and with a disabled first row.
 */
function testHiliteNextPrevWithDisabledFirstRow_default() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);

  var updates = 0;
  handler.listen(rend,
      goog.ui.ac.AutoComplete.EventType.ROW_HILITE,
      function() {
        updates++;
      });

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(3);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled first row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('edu');
    assertEquals(3, rend.getRenderedRows().length);
    // The first row is disabled, second should be highlighted.
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    // try going over the edge
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back down
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
    // First row is disabled, make sure we don't highlight it.
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
  }
  // 9 changes in the loop above (3 * 3)
  assertEquals(9, updates);
}


/**
 * Try using next and prev to navigate past the ends with default behavior of
 * allowFreeSelect_ and wrap_ and with a disabled middle row.
 */
function testHiliteNextPrevWithDisabledMiddleRow_default() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);

  var updates = 0;
  handler.listen(rend,
      goog.ui.ac.AutoComplete.EventType.ROW_HILITE,
      function() {
        updates++;
      });

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(3);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled middle row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('u');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    // Second row is disabled and should be skipped.
    checkHilitedIndex(rend, 2);
    // try going over the edge
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back down
    ac.hilitePrev();
    // Second row is disabled, make sure we don't highlight it.
    checkHilitedIndex(rend, 0);
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
  }
  // 9 changes in the loop above (3 * 3)
  assertEquals(9, updates);
}


/**
 * Try using next and prev to navigate past the ends with default behavior of
 * allowFreeSelect_ and wrap_ and with a disabled last row.
 */
function testHiliteNextPrevWithDisabledLastRow_default() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);

  var updates = 0;
  handler.listen(rend,
      goog.ui.ac.AutoComplete.EventType.ROW_HILITE,
      function() {
        updates++;
      });

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(3);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled last row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('h');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    // try going over the edge since last row is disabled
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back down
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
  }
  // 9 changes in the loop above (3 * 3)
  assertEquals(9, updates);
}


/**
 * Try using next and prev to navigate past the ends with wrap_ off and
 * allowFreeSelect_ on.
 */
function testHiliteNextPrev_allowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('the');
    assertEquals(4, rend.getRenderedRows().length);
    // check to see if we can select the last of the 4 items
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    ac.hiliteNext();
    checkHilitedIndex(rend, 3);
    // try going over the edge. Since allowFreeSelect is on, this will
    // deselect the last row.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, deselects first.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ off and
 * allowFreeSelect_ on, and a disabled first row.
 */
function testHiliteNextPrevWithDisabledFirstRow_allowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled first row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('edu');
    assertEquals(3, rend.getRenderedRows().length);
    // The first row is disabled, second should be highlighted.
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    // Try going over the edge. Since allowFreeSelect is on, this will
    // deselect the last row.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list, first row is disabled
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
    // first is disabled, so deselect the second.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ off and
 * allowFreeSelect_ on, and a disabled middle row.
 */
function testHiliteNextPrevWithDisabledMiddleRow_allowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled middle row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('u');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    // Second row is disabled and should be skipped.
    checkHilitedIndex(rend, 2);
    // try going over the edge. Since allowFreeSelect is on, this will
    // deselect the last row.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, deselects first.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ off and
 * allowFreeSelect_ on, and a disabled last row.
 */
function testHiliteNextPrevWithDisabledLastRow_allowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled last row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('h');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    // try going over the edge since last row is disabled. Since allowFreeSelect
    // is on, this will deselect the last row.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, deselects first.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ off.
 */
function testHiliteNextPrev_wrap() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('the');
    assertEquals(4, rend.getRenderedRows().length);
    // check to see if we can select the last of the 4 items
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    ac.hiliteNext();
    checkHilitedIndex(rend, 3);
    // try going over the edge. Since wrap is on, this will go back to 0.
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, selects last.
    ac.hilitePrev();
    checkHilitedIndex(rend, 3);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ off and a disabled first row.
 */
function testHiliteNextPrevWithDisabledFirstRow_wrap() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled first row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('edu');
    assertEquals(3, rend.getRenderedRows().length);
    // The first row is disabled, second should be highlighted.
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    // try going over the edge. Since wrap is on and first row is disabled,
    // this will go back to 1.
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
    // first is disabled, so wrap and select the last.
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ off and a disabled middle row.
 */
function testHiliteNextPrevWithDisabledMiddleRow_wrap() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled middle row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('u');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    // Second row is disabled and should be skipped.
    checkHilitedIndex(rend, 2);
    // try going over the edge. Since wrap is on, this will go back to 0.
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, selects last.
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ off and a disabled last row.
 */
function testHiliteNextPrevWithDisabledLastRow_wrap() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled last row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('h');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    // try going over the edge since last row is disabled. Since wrap is on,
    // this will go back to 0.
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, since wrap is on and last row is disabled, this
    // will select the second last.
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on.
 */
function testHiliteNextPrev_wrapAndAllowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('the');
    assertEquals(4, rend.getRenderedRows().length);
    // check to see if we can select the last of the 4 items
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    ac.hiliteNext();
    checkHilitedIndex(rend, 3);
    // try going over the edge. Since free select is on, this should go
    // to -1.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to last
    ac.hilitePrev();
    checkHilitedIndex(rend, 3);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on and a disabled first row.
 */
function testHiliteNextPrevWithDisabledFirstRow_wrapAndAllowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled first row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('edu');
    assertEquals(3, rend.getRenderedRows().length);
    // The first row is disabled, second should be highlighted.
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    // try going over the edge. Since free select is on, this should go to -1.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list, fist row is disabled
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to last
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on and a disabled middle row.
 */
function testHiliteNextPrevWithDisabledMiddleRow_wrapAndAllowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled middle row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('u');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    // Second row is disabled and should be skipped.
    checkHilitedIndex(rend, 2);
    // try going over the edge. Since free select is on, this should go to -1
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to last
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on and a disabled last row.
 */
function testHiliteNextPrevWithDisabledLastRow_wrapAndAllowFreeSelect() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled last row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('h');
    assertEquals(3, rend.getRenderedRows().length);
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    // try going over the edge since last row is disabled. Since free select is
    // on, this should go to -1
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to the second last, since last is disabled.
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on AND turn autoHilite_ off.
 */
function testHiliteNextPrev_wrapAndAllowFreeSelectNoAutoHilite() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setAutoHilite(false);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('the');
    assertEquals(4, rend.getRenderedRows().length);
    // check to see if we can select the last of the 4 items.
    // Initially nothing should be selected since autoHilite_ is off.
    checkHilitedIndex(rend, -1);
    ac.hilitePrev();
    checkHilitedIndex(rend, 3);
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    ac.hiliteNext();
    checkHilitedIndex(rend, 3);
    // try going over the edge. Since free select is on, this should go
    // to -1.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to last
    ac.hilitePrev();
    checkHilitedIndex(rend, 3);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on AND turn autoHilite_ off, and a disabled first row.
 */
function testHiliteNextPrevWithDisabledFirstRow_wrapAndAllowFreeSelectNoAutoHilite() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setAutoHilite(false);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled first row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('edu');
    assertEquals(3, rend.getRenderedRows().length);
    // Initially nothing should be selected since autoHilite_ is off.
    checkHilitedIndex(rend, -1);
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);
    ac.hiliteNext();
    // First row is disabled.
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);
    // try going over the edge. Since free select is on, this should go to -1
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list, first row is disabled
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to last
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on AND turn autoHilite_ off, and a disabled middle row.
 */
function testHiliteNextPrevWithDisabledMiddleRow_wrapAndAllowFreeSelectNoAutoHilite() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setAutoHilite(false);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled middle row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('u');
    assertEquals(3, rend.getRenderedRows().length);
    // Initially nothing should be selected since autoHilite_ is off.
    checkHilitedIndex(rend, -1);
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    // Second row is disabled
    checkHilitedIndex(rend, 2);
    // try going over the edge. Since free select is on, this should go to -1.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    // Second row is disabled.
    checkHilitedIndex(rend, 2);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to last
    ac.hilitePrev();
    checkHilitedIndex(rend, 2);
  }
}


/**
 * Try using next and prev to navigate past the ends with wrap_ on
 * allowFreeSelect_ on AND turn autoHilite_ off, and a disabled last row.
 */
function testHiliteNextPrevWithDisabledLastRow_wrapAndAllowFreeSelectNoAutoHilite() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setAutoHilite(false);

  // make sure 'next' and 'prev' don't explode before any token is set
  ac.hiliteNext();
  ac.hilitePrev();
  ac.setMaxMatches(4);
  assertEquals(0, rend.getRenderedRows().length);

  // check a few times with disabled last row
  for (var i = 0; i < 3; ++i) {
    ac.setToken('');
    ac.setToken('h');
    assertEquals(3, rend.getRenderedRows().length);
    // Initially nothing should be selected since autoHilite_ is off.
    checkHilitedIndex(rend, -1);
    ac.hilitePrev();
    // Last row is disabled
    checkHilitedIndex(rend, 1);
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);
    // try going over the edge. Since free select is on, this should go to -1.
    ac.hiliteNext();
    checkHilitedIndex(rend, -1);

    // go back down the list
    ac.hiliteNext();
    checkHilitedIndex(rend, 0);
    ac.hiliteNext();
    checkHilitedIndex(rend, 1);

    // go back up the list.
    ac.hilitePrev();
    checkHilitedIndex(rend, 0);
    // go back above the first, free select.
    ac.hilitePrev();
    checkHilitedIndex(rend, -1);
    // wrap to last
    ac.hilitePrev();
    checkHilitedIndex(rend, 1);
  }
}

function testHiliteWithChangingNumberOfRows() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setAutoHilite(true);
  ac.setMaxMatches(4);

  ac.setToken('m');
  assertEquals(4, rend.getRenderedRows().length);
  checkHilitedIndex(rend, 0);

  ac.setToken('ma');
  assertEquals(3, rend.getRenderedRows().length);
  checkHilitedIndex(rend, 0);

  // Hilite the second element
  var id = rend.getRenderedRows()[1].id;
  ac.hiliteId(id);

  ac.setToken('mar');
  assertEquals(1, rend.getRenderedRows().length);
  checkHilitedIndex(rend, 0);

  ac.setToken('ma');
  assertEquals(3, rend.getRenderedRows().length);
  checkHilitedIndex(rend, 0);

  // Hilite the second element
  var id = rend.getRenderedRows()[1].id;
  ac.hiliteId(id);

  ac.setToken('m');
  assertEquals(4, rend.getRenderedRows().length);
  checkHilitedIndex(rend, 0);
}


/**
 * Checks that autohilite is disabled when there is no token; this allows the
 * user to tab out of an empty autocomplete.
 */
function testNoAutoHiliteWhenTokenIsEmpty() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setAutoHilite(true);
  ac.setMaxMatches(4);

  ac.setToken('');
  assertEquals(4, rend.getRenderedRows().length);
  // No token; nothing should be hilited.
  checkHilitedIndex(rend, -1);

  ac.setToken('the');
  assertEquals(4, rend.getRenderedRows().length);
  // Now there is a token, so the first row should be highlighted.
  checkHilitedIndex(rend, 0);
}


/**
 * Checks that opt_preserveHilited works.
 */
function testPreserveHilitedWithoutAutoHilite() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setMaxMatches(4);
  ac.setAutoHilite(false);

  ac.setToken('m');
  assertEquals(4, rend.getRenderedRows().length);
  // No token; nothing should be hilited.
  checkHilitedIndex(rend, -1);

  // Hilite the second element
  var id = rend.getRenderedRows()[1].id;
  ac.hiliteId(id);

  checkHilitedIndex(rend, 1);

  // Re-render and check if the second element is still hilited
  ac.renderRows(rend.getRenderedRows(), true /* preserve hilite */);

  checkHilitedIndex(rend, 1);

  // Re-render without preservation
  ac.renderRows(rend.getRenderedRows());

  checkHilitedIndex(rend, -1);
}


/**
 * Checks that the autohilite argument "true" of the matcher is used.
 */
function testAutoHiliteFromMatcherTrue() {
  var ds = new MockDS(true);
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setAutoHilite(false);  // Will be overruled.
  ac.setMaxMatches(4);

  ac.setToken('the');
  assertEquals(4, rend.getRenderedRows().length);
  // The first row should be highlighted.
  checkHilitedIndex(rend, 0);
}


/**
 * Checks that the autohilite argument "false" of the matcher is used.
 */
function testAutoHiliteFromMatcherFalse() {
  var ds = new MockDS(false);
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setWrap(true);
  ac.setAllowFreeSelect(true);
  ac.setAutoHilite(true);  // Will be overruled.
  ac.setMaxMatches(4);

  ac.setToken('the');
  assertEquals(4, rend.getRenderedRows().length);
  // The first row should not be highlighted.
  checkHilitedIndex(rend, -1);
}


/**
 * Hilite using ids, the way mouse-based hiliting would work.
 */
function testHiliteId() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);

  // check a few times
  for (var i = 0; i < 3; ++i) {
    ac.setToken('m');
    assertEquals(4, rend.getRenderedRows().length);
    // try hiliting all 3
    for (var x = 0; x < 4; ++x) {
      var id = rend.getRenderedRows()[x].id;
      ac.hiliteId(id);
      assertEquals(ac.getIdOfIndex_(x), id);
    }
  }
}


/**
 * Test selecting the hilited row
 */
function testSelection() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac;

  // try with default selection
  ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setToken('m');
  ac.selectHilited();
  assertEquals('"Slartibartfast Theadore" <fjordmaster@magrathea.com>',
               select.selectedRow);

  // try second item
  ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setToken('the');
  ac.hiliteNext();
  ac.selectHilited();
  assertEquals('"Ford Prefect" <ford@theguide.com>',
               select.selectedRow);
}


/**
 * Dismiss when empty and non-empty
 */
function testDismiss() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();

  // dismiss empty
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  var dismissed = 0;
  handler.listen(ac,
      goog.ui.ac.AutoComplete.EventType.DISMISS,
      function() {
        dismissed++;
      });
  ac.dismiss();
  assertEquals(1, dismissed);

  ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setToken('sir not seen in this picture');
  ac.dismiss();

  // dismiss with contents
  ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setToken('t');
  ac.dismiss();
}

function testTriggerSuggestionsOnUpdate() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);

  var dismissCalled = 0;
  rend.dismiss = function() {
    dismissCalled++;
  };

  var updateCalled = 0;
  select.update = function(opt_force) {
    updateCalled++;
  };

  // Normally, menu is dismissed after selecting row (without updating).
  ac.setToken('the');
  ac.selectHilited();
  assertEquals(1, dismissCalled);
  assertEquals(0, updateCalled);

  // But not if we re-trigger on update.
  ac.setTriggerSuggestionsOnUpdate(true);
  ac.setToken('the');
  ac.selectHilited();
  assertEquals(1, dismissCalled);
  assertEquals(1, updateCalled);
}

function testDispose() {
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setToken('the');
  ac.dispose();
}


/**
 * Ensure that activedescendant is updated properly.
 */
function testRolesAndStates() {
  function checkActiveDescendant(activeDescendant) {
    assertNotNull(inputElement);
    assertEquals(
        goog.a11y.aria.getActiveDescendant(inputElement),
        activeDescendant);
  }
  function checkRole(el, role) {
    assertNotNull(el);
    assertEquals(goog.a11y.aria.getRole(el), role);
  }
  var ds = new MockDS();
  var rend = new TestRend();
  var select = new MockSelect();
  var ac = new goog.ui.ac.AutoComplete(ds, rend, select);
  ac.setTarget(inputElement);

  // initially activedescendant is not set
  checkActiveDescendant(null);

  // highlight the matching row and check that activedescendant updates
  ac.setToken('');
  ac.setToken('the');
  ac.hiliteNext();
  checkActiveDescendant(rend.getHilitedRowDiv());

  // highligted row should have a role of 'option'
  checkRole(rend.getHilitedRowDiv(), goog.a11y.aria.Role.OPTION);

  // closing the autocomplete should clear activedescendant
  ac.dismiss();
  checkActiveDescendant(null);
}

function testAttachInputWithAnchor() {
  var anchorElement = goog.dom.createDom('div', null, inputElement);

  var mockRenderer = mockControl.createLooseMock(
      goog.ui.ac.Renderer, true);
  mockRenderer.setAnchorElement(anchorElement);
  var ignore = goog.testing.mockmatchers.ignoreArgument;
  mockRenderer.renderRows(ignore, ignore, inputElement);

  var mockInputHandler = mockControl.createLooseMock(
      goog.ui.ac.InputHandler, true);
  mockInputHandler.attachInputs(inputElement);

  mockControl.$replayAll();
  var autoComplete = new goog.ui.ac.AutoComplete(
      null, mockRenderer, mockInputHandler);
  autoComplete.attachInputWithAnchor(inputElement, anchorElement);
  autoComplete.setTarget(inputElement);

  autoComplete.renderRows(['abc', 'def']);
  mockControl.$verifyAll();
}

function testDetachInputWithAnchor() {
  var mockRenderer = mockControl.createLooseMock(
      goog.ui.ac.Renderer, true);
  var mockInputHandler = mockControl.createLooseMock(
      goog.ui.ac.InputHandler, true);
  var anchorElement = goog.dom.createDom('div', null, inputElement);
  var inputElement2 = goog.dom.createDom('input', {type: 'text'});
  var anchorElement2 = goog.dom.createDom('div', null, inputElement2);

  mockControl.$replayAll();
  var autoComplete = new goog.ui.ac.AutoComplete(
      null, mockRenderer, mockInputHandler);

  autoComplete.attachInputWithAnchor(inputElement, anchorElement);
  autoComplete.attachInputWithAnchor(inputElement2, anchorElement2);
  autoComplete.detachInputs(inputElement, inputElement2);

  assertFalse(goog.getUid(inputElement) in autoComplete.inputToAnchorMap_);
  assertFalse(goog.getUid(inputElement2) in autoComplete.inputToAnchorMap_);
  mockControl.$verifyAll();
}
