toolkit/content/tests/chrome/test_autocomplete_placehold_last_complete.xul
author Masayuki Nakano <masayuki@d-toybox.com>
Tue, 20 Nov 2018 14:35:38 +0000
changeset 447458 09fd7845a50bad9fa7a579a9e7088828d8155a20
parent 429780 3403701f0727a33f927e46da1eef2950a93b0e1a
child 469642 0d9b9b96f5475adbed73922da696aeff7cbbaed3
permissions -rw-r--r--
Bug 1504911 - part 4: Make all script for web content dispatch "input" event with proper event interface r=smaug Currently, some "input" event dispatchers in our script dispatch "input" event with UIEvent. This is completely wrong. For conforming to HTML spec, Event is proper event. Additionally, for conforming to Input Events, InputEvent is proper event only on <textarea> or <input> element which has a single line editor. For making us to maintain easier, this patch adds new API, "isInputEventTarget" to MozEditableElement which returns true when "input" event dispatcher should use InputEvent for the input element. Finally, this makes some dispatchers use setUserInput() instead of setting value and dispatching event by themselves. This also makes us to maintain them easier. Note that this does not touch "input" event dispatchers which dispatch events only for chrome (such as URL bar, some pages in about: scheme) for making this change safer as far as possible. Differential Revision: https://phabricator.services.mozilla.com/D12247

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>

<window title="Autocomplete Widget Test"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        onload="runTest();">

  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
  <script type="application/javascript"
          src="chrome://global/content/globalOverlay.js"/>

<textbox id="autocomplete"
         type="autocomplete"
         completedefaultindex="true"
         timeout="0"
         autocompletesearch="simple"/>

<script class="testbody" type="application/javascript">
<![CDATA[

ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");

function autoCompleteSimpleResult(aString, searchId) {
  this.searchString = aString;
  this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
  this.matchCount = 1;
  if (aString.startsWith('ret')) {
    this._param = autoCompleteSimpleResult.retireCompletion;
  } else {
    this._param = "Result";
  }
  this._searchId = searchId;
}
autoCompleteSimpleResult.retireCompletion = "Retire";
autoCompleteSimpleResult.prototype = {
  _param: "",
  searchString: null,
  searchResult: Ci.nsIAutoCompleteResult.RESULT_FAILURE,
  defaultIndex: 0,
  errorDescription: null,
  matchCount: 0,
  getValueAt: function() { return this._param; },
  getCommentAt: function() { return null; },
  getStyleAt: function() { return null; },
  getImageAt: function() { return null; },
  getLabelAt: function() { return null; },
  removeValueAt: function() {}
};

var searchCounter = 0;

// A basic autocomplete implementation that returns one result.
let autoCompleteSimple = {
  classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"),
  contractID: "@mozilla.org/autocomplete/search;1?name=simple",
  searchAsync: false,
  pendingSearch: null,

  QueryInterface: ChromeUtils.generateQI([
    Ci.nsIFactory,
    Ci.nsIAutoCompleteSearch
  ]),
  createInstance: function (outer, iid) {
    return this.QueryInterface(iid);
  },

  registerFactory: function () {
    let registrar =
      Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
    registrar.registerFactory(this.classID, "Test Simple Autocomplete",
                              this.contractID, this);
  },
  unregisterFactory: function () {
    let registrar =
      Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
    registrar.unregisterFactory(this.classID, this);
  },

  startSearch: function (aString, aParam, aResult, aListener) {
    let result = new autoCompleteSimpleResult(aString);

    if (this.searchAsync) {
      // Simulate an async search by using a timeout before invoking the
      // |onSearchResult| callback.
      // Store the searchTimeout such that it can be canceled if stopSearch is called.
      this.pendingSearch = setTimeout(() => {
        this.pendingSearch = null;

        aListener.onSearchResult(this, result);

         // Move to the next step in the async test.
        asyncTest.next();
      }, 0);
    } else {
      aListener.onSearchResult(this, result);
    }
  },
  stopSearch: function () {
    clearTimeout(this.pendingSearch);
  }
};

SimpleTest.waitForExplicitFinish();

let gACTimer;
let gAutoComplete;
let asyncTest;

let searchCompleteTimeoutId = null;

function finishTest() {
  // Unregister the factory so that we don't get in the way of other tests
  autoCompleteSimple.unregisterFactory();
  SimpleTest.finish();
}

function runTest() {
  autoCompleteSimple.registerFactory();
  gAutoComplete = $("autocomplete");
  gAutoComplete.focus();

  // Return the search results synchronous, which also makes the completion
  // happen synchronous.
  autoCompleteSimple.searchAsync = false;

  sendString("r");
  is(gAutoComplete.value, "result", "Value should be autocompleted immediately");

  sendString("e");
  is(gAutoComplete.value, "result", "Value should be autocompleted immediately");

  synthesizeKey("KEY_Delete");
  is(gAutoComplete.value, "re", "Deletion should not complete value");

  synthesizeKey("KEY_Backspace");
  is(gAutoComplete.value, "r", "Backspace should not complete value");

  synthesizeKey("KEY_ArrowLeft");
  is(gAutoComplete.value, "r", "Value should stay same when navigating with cursor");

  runAsyncTest();
}

function* asyncTestGenerator() {
  sendString("re");
  is(gAutoComplete.value, "re", "Value should not be autocompleted immediately");

  // Calling |yield undefined| makes this generator function wait until
  // |asyncTest.next();| is called. This happens from within the
  // |autoCompleteSimple.startSearch()| function once the simulated async
  // search has finished.
  // Therefore, the effect of the |yield undefined;| here (and the ones) below
  // is to wait until the async search result comes back.
  yield undefined;

  is(gAutoComplete.value, "result", "Value should be autocompleted");

  // Test if typing the `s` character completes directly based on the last
  // completion
  sendString("s");
  is(gAutoComplete.value, "result", "Value should be completed immediately");

  yield undefined;

  is(gAutoComplete.value, "result", "Value should be autocompleted to same value");
  synthesizeKey("KEY_Delete");
  is(gAutoComplete.value, "res", "Deletion should not complete value");

  // No |yield undefined| needed here as no completion is triggered by the deletion.

  is(gAutoComplete.value, "res", "Still no complete value after deletion");

  synthesizeKey("KEY_Backspace");
  is(gAutoComplete.value, "re", "Backspace should not complete value");

  yield undefined;

  is(gAutoComplete.value, "re", "Value after search due to backspace should stay the same"); (3)

  // Typing a character that is not like the previous match. In this case, the
  // completion cannot happen directly and therefore the value will be completed
  // only after the search has finished.
  sendString("t");
  is(gAutoComplete.value, "ret", "Value should not be autocompleted immediately");

  yield undefined;

  is(gAutoComplete.value, "retire", "Value should be autocompleted");

  sendString("i");
  is(gAutoComplete.value, "retire", "Value should be autocompleted immediately");

  yield undefined;

  is(gAutoComplete.value, "retire", "Value should be autocompleted to the same value");

  // Setup the scene to test how the completion behaves once the placeholder
  // completion and the result from the search do not agree with each other.
  gAutoComplete.value = 'r';
  // Need to type two characters as the input was reset and the autocomplete
  // controller things, ther user hit the backspace button, in which case
  // no completion is performed. But as a completion is desired, another
  // character `t` is typed afterwards.
  sendString("e");
  yield undefined;
  sendString("t");
  is(gAutoComplete.value, "ret", "Value should not be autocompleted");

  yield undefined;

  is(gAutoComplete.value, "retire", "Value should be autocompleted");

  // The placeholder string is now set to "retire". Changing the completion
  // string to "retirement" and see what the completion will turn out like.
  autoCompleteSimpleResult.retireCompletion = "Retirement";
  sendString("i");
  is(gAutoComplete.value, "retire", "Value should be autocompleted based on placeholder");

  yield undefined;

  is(gAutoComplete.value, "retirement", "Value should be autocompleted based on search result");

  // Change the search result to `Retire` again and see if the new result is
  // complited.
  autoCompleteSimpleResult.retireCompletion = "Retire";
  sendString("r");
  is(gAutoComplete.value, "retirement", "Value should be autocompleted based on placeholder");

  yield undefined;

  is(gAutoComplete.value, "retire", "Value should be autocompleted based on search result");

  // Complete the value
  gAutoComplete.value = 're';
  sendString("t");
  yield undefined;
  sendString("i");
  is(gAutoComplete.value, "reti", "Value should not be autocompleted");

  yield undefined;

  is(gAutoComplete.value, "retire", "Value should be autocompleted");

  // Remove the selected text "re" (1) and the "et" (2). Afterwards, add it again (3).
  // This should not cause the completion to kick in.
  synthesizeKey("KEY_Delete"); // (1)

  is(gAutoComplete.value, "reti", "Value should not complete after deletion");

  gAutoComplete.selectionStart = 1;
  gAutoComplete.selectionEnd = 3;
  synthesizeKey("KEY_Delete"); // (2)

  is(gAutoComplete.value, "ri", "Value should stay unchanged after removing character in the middle");

  yield undefined;

  sendString("e"); // (3.1)
  is(gAutoComplete.value, "rei", "Inserting a character in the middle should not complete the value");

  yield undefined;

  sendString("t"); // (3.2)
  is(gAutoComplete.value, "reti", "Inserting a character in the middle should not complete the value");

  yield undefined;

  // Adding a new character at the end should not cause the completion to happen again
  // as the completion failed before.
  gAutoComplete.selectionStart = 4;
  gAutoComplete.selectionEnd = 4;
  sendString("r");
  is(gAutoComplete.value, "retir", "Value should not be autocompleted immediately");

  yield undefined;

  is(gAutoComplete.value, "retire", "Value should be autocompleted");

  finishTest();
  yield undefined;
}

function runAsyncTest() {
  gAutoComplete.value = '';
  autoCompleteSimple.searchAsync = true;

  asyncTest = asyncTestGenerator();
  asyncTest.next();
}
]]>
</script>

<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display">
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>

</window>