toolkit/components/satchel/test/test_form_autocomplete.html
author Mounir Lamouri <mounir.lamouri@gmail.com>
Sat, 22 Jan 2011 00:06:27 +0100
changeset 61119 3f36d14effd1dc1baf2b4c8f731793424db2de35
parent 59458 dfdbfacc2a2b6c56a126c7d256a995614c9e4078
child 75488 1188456710819d4606e74396be333ead9fe63261
permissions -rw-r--r--
Bug 320462 - Send an input event when a value is selected from the autocomple or list dropdown. r=smaug a=sicking

<!DOCTYPE HTML>
<html>
<head>
  <title>Test for Form History Autocomplete</title>
  <script type="text/javascript" src="/MochiKit/packed.js"></script>
  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
  <script type="text/javascript" src="satchel_common.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Form History test: form field autocomplete
<p id="display"></p>

<!-- we presumably can't hide the content for this test. -->
<div id="content">

  <!-- normal, basic form -->
  <form id="form1" onsubmit="return false;">
    <input  type="text" name="field1">
    <button type="submit">Submit</button>
  </form>

  <!-- normal, basic form (new fieldname) -->
  <form id="form2" onsubmit="return false;">
    <input  type="text" name="field2">
    <button type="submit">Submit</button>
  </form>

  <!-- form with autocomplete=off on input -->
  <form id="form3" onsubmit="return false;">
    <input  type="text" name="field2" autocomplete="off">
    <button type="submit">Submit</button>
  </form>

  <!-- form with autocomplete=off on form -->
  <form id="form4" autocomplete="off" onsubmit="return false;">
    <input  type="text" name="field2">
    <button type="submit">Submit</button>
  </form>

  <!-- normal form for testing filtering -->
  <form id="form5" onsubmit="return false;">
    <input  type="text" name="field3">
    <button type="submit">Submit</button>
  </form>

  <!-- normal form for testing word boundary filtering -->
  <form id="form6" onsubmit="return false;">
    <input  type="text" name="field4">
    <button type="submit">Submit</button>
  </form>

  <!-- form with maxlength attribute on input -->
  <form id="form7" onsubmit="return false;">
    <input  type="text" name="field5" maxlength="10">
    <button type="submit">Submit</button>
  </form>

  <!-- form with input type='email' -->
  <form id="form8" onsubmit="return false;">
    <input  type="email" name="field6">
    <button type="submit">Submit</button>
  </form>

  <!-- form with input type='tel' -->
  <form id="form9" onsubmit="return false;">
    <input  type="tel" name="field7">
    <button type="submit">Submit</button>
  </form>

  <!-- form with input type='url' -->
  <form id="form10" onsubmit="return false;">
    <input  type="url" name="field8">
    <button type="submit">Submit</button>
  </form>

  <!-- form with input type='search' -->
  <form id="form11" onsubmit="return false;">
    <input  type="search" name="field9">
    <button type="submit">Submit</button>
  </form>

</div>

<pre id="test">
<script class="testbody" type="text/javascript">

/** Test for Form History autocomplete **/

netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');

var input = $_(1, "field1");
const shiftModifier = Components.interfaces.nsIDOMNSEvent.SHIFT_MASK;

// Get the form history service
var fh = Components.classes["@mozilla.org/satchel/form-history;1"].
         getService(Components.interfaces.nsIFormHistory2);
ok(fh != null, "got form history service");

fh.removeAllEntries();
fh.addEntry("field1", "value1");
fh.addEntry("field1", "value2");
fh.addEntry("field1", "value3");
fh.addEntry("field1", "value4");
fh.addEntry("field2", "value1");
fh.addEntry("field3", "a");
fh.addEntry("field3", "aa");
fh.addEntry("field3", "aaz");
fh.addEntry("field3", "aa\xe6"); // 0xae == latin ae pair (0xc6 == AE)
fh.addEntry("field3", "az");
fh.addEntry("field3", "z");
fh.addEntry("field4", "a\xe6");
fh.addEntry("field4", "aa a\xe6");
fh.addEntry("field4", "aba\xe6");
fh.addEntry("field4", "bc d\xe6");
fh.addEntry("field5", "1");
fh.addEntry("field5", "12");
fh.addEntry("field5", "123");
fh.addEntry("field5", "1234");
fh.addEntry("field6", "value");
fh.addEntry("field7", "value");
fh.addEntry("field8", "value");
fh.addEntry("field9", "value");

// All these non-implemeted types might need autocomplete tests in the future.
var todoTypes = [ "datetime", "date", "month", "week", "time", "datetime-local",
                  "number", "range", "color" ];
var todoInput = document.createElement("input");
for each (var type in todoTypes) {
  todoInput.type = type;
  todo_is(todoInput.type, type, type + " type shouldn't be implemented");
}


function setForm(value) {
  input.value = value;
  input.focus();
}

var autocompleteMenu = getAutocompletePopup();

// Restore the form to the default state.
function restoreForm() {
  setForm("");
}

// Check for expected form data.
function checkForm(expectedValue) {
  var formID = input.parentNode.id;
  is(input.value, expectedValue, "Checking " + formID + " input");
}


/*
 * Main section of test...
 *
 * This is a bit hacky, because the events are either being sent or
 * processed asynchronously, so we need to interrupt our flow with lots of
 * setTimeout() calls. The case statements are executed in order, one per
 * timeout.
 */
function runTest(testNum) {
  // Seems we need to enable this again, or sendKeyEvent() complaints.
  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
  ok(true, "Starting test #" + testNum);

  switch(testNum) {
    case 1:
        // Make sure initial form is empty.
        checkForm("");
        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 2:
        checkMenuEntries(["value1", "value2", "value3", "value4"]);
        // Check first entry
        doKey("down");
        checkForm(""); // value shouldn't update
        doKey("return"); // not "enter"!
        checkForm("value1");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 3:
        // Check second entry
        doKey("down");
        doKey("down");
        doKey("return"); // not "enter"!
        checkForm("value2");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 4:
        // Check third entry
        doKey("down");
        doKey("down");
        doKey("down");
        doKey("return");
        checkForm("value3");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 5:
        // Check fourth entry
        doKey("down");
        doKey("down");
        doKey("down");
        doKey("down");
        doKey("return");
        checkForm("value4");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 6:
        // Check first entry (wraparound)
        doKey("down");
        doKey("down");
        doKey("down");
        doKey("down");
        doKey("down"); // deselects
        doKey("down");
        doKey("return");
        checkForm("value1");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 7:
        // Check the last entry via arrow-up
        doKey("up");
        doKey("return");
        checkForm("value4");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 8:
        // Check the last entry via arrow-up
        doKey("down"); // select first entry
        doKey("up");   // selects nothing!
        doKey("up");   // select last entry
        doKey("return");
        checkForm("value4");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 9:
        // Check the last entry via arrow-up (wraparound)
        doKey("down");
        doKey("up"); // deselects
        doKey("up"); // last entry
        doKey("up");
        doKey("up");
        doKey("up"); // first entry
        doKey("up"); // deselects
        doKey("up"); // last entry
        doKey("return");
        checkForm("value4");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 10:
        // Set first entry w/o triggering autocomplete
        doKey("down");
        doKey("right");
        checkForm("value1");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 11:
        // Set first entry w/o triggering autocomplete
        doKey("down");
        doKey("left");
        checkForm("value1");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 12:
        // Check first entry (page up)
        doKey("down");
        doKey("down");
        doKey("page_up");
        doKey("return");
        checkForm("value1");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 13:
        // Check last entry (page down)
        doKey("down");
        doKey("page_down");
        doKey("return");
        checkForm("value4");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        testNum = 49;
        break;

    /* Test removing entries from the dropdown */

    case 50:
        checkMenuEntries(["value1", "value2", "value3", "value4"]);
        // Delete the first entry (of 4)
        setForm("value");
        doKey("down");

        // On OS X, shift-backspace and shift-delete work, just delete does not.
        // On Win/Linux, shift-backspace does not work, delete and shift-delete do.
        var osString = Components.classes["@mozilla.org/xre/app-info;1"].
                       getService(Components.interfaces.nsIXULRuntime).OS;
        if (osString == "Darwin")
          doKey("back_space", shiftModifier);
        else
          doKey("delete", shiftModifier);

        // This tests that on OS X shift-backspace didn't delete the last character
        // in the input (bug 480262).
        checkForm("value");

        ok(!fh.entryExists("field1", "value1"), "checking that f1/v1 was deleted");
        doKey("return");
        checkForm("value2");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 51:
        checkMenuEntries(["value2", "value3", "value4"]);
        // Check the new first entry (of 3)
        doKey("down");
        doKey("return");
        checkForm("value2");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 52:
        // Delete the second entry (of 3)
        doKey("down");
        doKey("down");
        doKey("delete", shiftModifier);
        checkForm("");
        ok(!fh.entryExists("field1", "value3"), "checking that f1/v3 was deleted");
        doKey("return");
        checkForm("value4")

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 53:
        checkMenuEntries(["value2", "value4"]);
        // Check the new first entry (of 2)
        doKey("down");
        doKey("return");
        checkForm("value2");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 54:
        // Delete the last entry (of 2)
        doKey("down");
        doKey("down");
        doKey("delete", shiftModifier);
        checkForm("");
        ok(!fh.entryExists("field1", "value4"), "checking that f1/v4 was deleted");
        doKey("return");
        checkForm("value2");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 55:
        checkMenuEntries(["value2"]);
        // Check the new first entry (of 1)
        doKey("down");
        doKey("return");
        checkForm("value2");

        // Trigger autocomplete popup
        restoreForm();
        doKey("down");
        break;

    case 56:
        // Delete the only remaining entry
        doKey("down");
        doKey("delete", shiftModifier);
        checkForm("");
        ok(!fh.entryExists("field1", "value2"), "checking that f1/v2 was deleted");

        // Look at form 2, trigger autocomplete popup
        input = $_(2, "field2");
        restoreForm();
        doKey("down");
        testNum = 99;
        break;

    /* Test entries with autocomplete=off */

    case 100:
        // Select first entry
        doKey("down");
        doKey("return");
        checkForm("value1");

        // Look at form 3, try to trigger autocomplete popup
        input = $_(3, "field2");
        restoreForm();
        doKey("down");
        break;

    case 101:
        // Ensure there's no autocomplete dropdown (autocomplete=off is present)
        doKey("down");
        doKey("return");
        checkForm("");

        // Look at form 4, try to trigger autocomplete popup
        input = $_(4, "field2");
        restoreForm();
        doKey("down");
        break;

    case 102:
        // Ensure there's no autocomplete dropdown (autocomplete=off is present)
        doKey("down");
        doKey("return");
        checkForm("");

        // Look at form 5, try to trigger autocomplete popup
        input = $_(5, "field3");
        restoreForm();
        testNum = 199;
        sendChar("a", input);
        break;

    /* Test filtering as characters are typed. */

    case 200:
        checkMenuEntries(["a", "aa", "aaz", "aa\xe6", "az"]);
        sendChar("a", input);
        break;

    case 201:
        checkMenuEntries(["aa", "aaz", "aa\xe6"]);
        sendChar("\xc6", input);
        break;

    case 202:
        checkMenuEntries(["aa\xe6"]);
        doKey("back_space");
        break;

    case 203:
        checkMenuEntries(["aa", "aaz", "aa\xe6"]);
        doKey("back_space");
        break;

    case 204:
        checkMenuEntries(["a", "aa", "aaz", "aa\xe6", "az"]);
        sendChar("z", input);
        break;

    case 205:
        ok(getMenuEntries().length > 0, "checking typing in middle of text");
        doKey("left");
        sendChar("a", input);
        break;

    case 206:
        checkMenuEntries(["aaz"]);
        fh.addEntry("field3", "aazq");
        doKey("right");
        sendChar("q", input);
        break;

    case 207:
        // check that results were cached
        checkMenuEntries([]);
        fh.addEntry("field3", "aazqq");
        sendChar("q", input);
        break;

    case 208:
        // check that empty results were cached - bug 496466
        checkMenuEntries([]);
        doKey("escape");

        // Look at form 6, try to trigger autocomplete popup
        input = $_(6, "field4");
        restoreForm();
        testNum = 249;
        sendChar("a", input);
        break;

    /* Test substring matches and word boundary bonuses */

    case 250:
        // alphabetical results for first character
        checkMenuEntries(["aa a\xe6", "aba\xe6", "a\xe6"]);
        sendChar("\xc6", input);
        break;

    case 251:
        // prefix match comes first, then word boundary match
        // followed by substring match
        checkMenuEntries(["a\xe6", "aa a\xe6", "aba\xe6"]);

        restoreForm();
        sendChar("b", input);
        break;

    case 252:
        checkMenuEntries(["bc d\xe6"]);
        sendChar(" ", input);
        break;

    case 253:
        // check that trailing space has no effect after single char.
        checkMenuEntries(["bc d\xe6"]);
        sendChar("\xc6", input);
        break;

    case 254:
        // check multi-word substring matches
        checkMenuEntries(["bc d\xe6", "aba\xe6"]);
        doKey("left");
        sendChar("d", input);
        break;

    case 255:
        // check inserting in multi-word searches
        checkMenuEntries(["bc d\xe6"]);
        sendChar("z", input);
        break;

    case 256:
        checkMenuEntries([]);

        // Look at form 7, try to trigger autocomplete popup
        input = $_(7, "field5");
        restoreForm();
        doKey("down");
        testNum = 299;
        break;

    case 300:
        checkMenuEntries(["1", "12", "123", "1234"]);
        input.maxLength = 4;
        doKey("escape");
        doKey("down");
        break;

    case 301:
        checkMenuEntries(["1", "12", "123", "1234"]);
        input.maxLength = 3;
        doKey("escape");
        doKey("down");
        break;

    case 302:
        checkMenuEntries(["1", "12", "123"]);
        input.maxLength = 2;
        doKey("escape");
        doKey("down");
        break;

    case 303:
        checkMenuEntries(["1", "12"]);
        input.maxLength = 1;
        doKey("escape");
        doKey("down");
        break;

    case 304:
        checkMenuEntries(["1"]);
        input.maxLength = 0;
        doKey("escape");
        doKey("down");
        break;

    case 305:
        checkMenuEntries([]);
        input.maxLength = 4;

        // now again with a character typed
        sendChar("1", input);
        doKey("escape");
        doKey("down");
        break;

    case 306:
        checkMenuEntries(["1", "12", "123", "1234"]);
        input.maxLength = 3;
        doKey("escape");
        doKey("down");
        break;

    case 307:
        checkMenuEntries(["1", "12", "123"]);
        input.maxLength = 2;
        doKey("escape");
        doKey("down");
        break;

    case 308:
        checkMenuEntries(["1", "12"]);
        input.maxLength = 1;
        doKey("escape");
        doKey("down");
        break;

    case 309:
        checkMenuEntries(["1"]);
        input.maxLength = 0;
        doKey("escape");
        doKey("down");
        break;

    case 310:
        checkMenuEntries([]);

        input = $_(8, "field6");
        testNum = 399;
        restoreForm();
        doKey("down");
        break;

    case 400:
    case 401:
    case 402:
    case 403:
        checkMenuEntries(["value"]);
        doKey("down");
        doKey("return");
        checkForm("value");

        if (testNum == 400) {
          input = $_(9, "field7");
        } else if (testNum == 401) {
          input = $_(10, "field8");
        } else if (testNum == 402) {
          input = $_(11, "field9");
        } else if (testNum == 403) {
          // Go to test 500.
          fh.addEntry("field1", "value1");
          input = $_(1, "field1");
          testNum = 499;
        }

        restoreForm();
        doKey("down");
        break;

    // Check that the input event is fired.
    case 500:
      input.addEventListener("input", function(event) {
        input.removeEventListener("input", arguments.callee, false);
        ok(true, "oninput should have been received");
        ok(event.bubbles, "input event should bubble");
        ok(event.cancelable, "input event should be cancelable");
        SimpleTest.finish();
      }, false);

      doKey("down");
      checkForm("");
      doKey("return");
      checkForm("value1");
      return;

    default:
        ok(false, "Unexpected invocation of test #" + testNum);
        SimpleTest.finish();
        return;
  }

  setTimeout(runTest, 50, testNum + 1); // XXX 40ms was too slow, why?
}

function checkMenuEntries(expectedValues) {
    var actualValues = getMenuEntries();
    is(actualValues.length, expectedValues.length, "Checking length of expected menu");
    for (var i = 0; i < expectedValues.length; i++)
        is(actualValues[i], expectedValues[i], "Checking menu entry #"+i);
}

function getMenuEntries() {
    var entries = [];

    // Could perhaps pull values directly from the controller, but it seems
    // more reliable to test the values that are actually in the tree?
    var column = autocompleteMenu.tree.columns[0];
    var numRows = autocompleteMenu.tree.view.rowCount;
    for (var i = 0; i < numRows; i++) {
        entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
    }
    return entries;
}

function startTest() {
    runTest(1);
}

window.onload = startTest;

SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>