<!DOCTYPE HTML>
<html>
<head>
  <title>Test for Form History Autocomplete</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script 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">
  <datalist id="suggest">
    <option value="Google" label="PASS1">FAIL</option>
    <option value="Reddit">PASS2</option>
    <option value="final"></option>
  </datalist>

  <!-- normal, basic form -->
  <form id="form1">
    <input list="suggest" type="text" name="field1">
    <button type="submit">Submit</button>
  </form>

  <!-- form with autocomplete=off on input -->
  <form id="form3">
    <input list="suggest" type="text" name="field2" autocomplete="off">
    <button type="submit">Submit</button>
  </form>

  <!-- form with autocomplete=off on form -->
  <form id="form4" autocomplete="off">
    <input list="suggest" type="text" name="field2">
    <button type="submit">Submit</button>
  </form>

  <!-- yet another normal, basic form, with 2 history entries -->
  <form id="form5">
    <input list="suggest" type="text" name="field3">
    <button type="submit">Submit</button>
  </form>

  <form id="show_datalist_for_text_inputs_only">
    <input list="suggest" type="button" popup="false" />
    <input list="suggest" type="checkbox" popup="false" />
    <input list="suggest" type="color" popup="false" />
    <input list="suggest" type="date" popup="false" />
    <input list="suggest" type="datetime-local" popup="false" />
    <input list="suggest" type="email" popup="true" />
    <input list="suggest" type="file" popup="false" />
    <input list="suggest" type="image" popup="false" />
    <input list="suggest" type="month" popup="true" />
    <input list="suggest" type="number" popup="false" />
    <input list="suggest" type="password" popup="true" />
    <input list="suggest" type="radio" popup="false" />
    <input list="suggest" type="range" popup="false" />
    <input list="suggest" type="reset" popup="false" />
    <input list="suggest" type="search" popup="true" />
    <input list="suggest" type="submit" popup="false" />
    <input list="suggest" type="tel" popup="true" />
    <input list="suggest" type="text" popup="true" />
    <input list="suggest" type="time" popup="false" />
    <input list="suggest" type="url" popup="true" />
    <input list="suggest" type="week" popup="true" />
  </form>
</div>

<script>

preventSubmitOnForms();

add_setup(async () => {
  await updateFormHistory([
    { op: "remove" },
    { op: "add", fieldname: "field1", value: "historyvalue" },
    { op: "add", fieldname: "field2", value: "othervalue" },
    { op: "add", fieldname: "field3", value: "history1" },
    { op: "add", fieldname: "field3", value: "history2" },
  ]);
});

add_task(async function no_changes_when_opening_popup(){
  const { input } = await openPopupOn("#form1 > input");
  assertValueAfterKeys(input, [], "");
});

add_task(async function use_1st_entry() {
  const { input } = await openPopupOn("#form1 > input");
  assertAutocompleteItems("historyvalue", "PASS1", "PASS2", "final");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "historyvalue");
});

add_task(async function use_2nd_entry(){
  const { input } = await openPopupOn("#form1 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "Google");
});

add_task(async function use_3rd_entry(){
  const { input } = await openPopupOn("#form1 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "Reddit");
});

add_task(async function use_4th_entry(){
  const { input } = await openPopupOn("#form1 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "final");
});

add_task(async function delete_1st_entry(){
  const { input } = await openPopupOn("#form1 > input");
  assertValueAfterKeys(input, "KEY_ArrowDown", "");
  deleteSelectedAutocompleteItem();
  await notifyMenuChanged(3);
  is(await countEntries("field1", "historyvalue"), 0, "item is absent");
});

add_task(async function can_use_next_item_after_deletion(){
  const { input } = await openPopupOn("#form1 > input");
  assertAutocompleteItems("PASS1", "PASS2", "final");
  assertValueAfterKeys(input, ["KEY_ArrowDown", "KEY_Enter"], "Google");
});

add_task(async function autocomplete_on_datalist_with_cached_results(){
  const { input } = await openPopupOn("#form1 > input");
  sendString("PAS");
  await notifyMenuChanged(2);
  sendString("S1");
  await notifyMenuChanged(1);
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "Google");
});

add_task(async function fills_with_autocomplete_off_on_form(){
  const { input } = await openPopupOn("#form4 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "Google");
  assertAutocompleteItems("PASS1", "PASS2", "final");
});

add_task(async function use_1st_entry_with_autocomplete_off_on_form(){
  const { input } = await openPopupOn("#form4 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "Google");
});

add_task(async function use_2nd_entry_with_autocomplete_off_on_form(){
  const { input } = await openPopupOn("#form4 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "Reddit");
});

add_task(async function use_3rd_entry_with_autocomplete_off_on_form(){
  const { input } = await openPopupOn("#form4 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "final");
});

add_task(async function fills_with_autocomplete_off_on_input(){
  const { input } = await openPopupOn("#form3 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "Google");
  assertAutocompleteItems("PASS1", "PASS2", "final");
});

add_task(async function use_1st_entry_with_autocomplete_off_on_input(){
  const { input } = await openPopupOn("#form3 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "Google");
});

add_task(async function use_2nd_entry_with_autocomplete_off_on_input(){
  const { input } = await openPopupOn("#form3 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "Reddit");
});

add_task(async function use_3rd_entry_with_autocomplete_off_on_input(){
  const { input } = await openPopupOn("#form3 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "final");
});

add_task(async function remove_item_from_datalist(){
  // When there is an update of the list, the selection is lost so we need to
  // go down like if we were at the beginning of the list again.
  //
  // Removing the second element while on the first then going down and
  // push enter. Value should be one from the third suggesion.
  const { input } = await openPopupOn("#form3 > input");
  synthesizeKey("KEY_ArrowDown");
  const datalist = document.getElementById("suggest");
  const toRemove = datalist.children[1];
  datalist.removeChild(toRemove);

  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "final");

  // Restore the element.
  datalist.insertBefore(toRemove, datalist.children[1]);
});

add_task(async function add_item_to_datalist(){
  const { input } = await openPopupOn("#form3 > input");
  const datalist = document.getElementById("suggest");

  // Adding an attribute after the first one while on the first then going
  // down and push enter. Value should be the on from the new suggestion.
  synthesizeKey("KEY_ArrowDown");
  datalist.insertBefore(new Option("New value"), datalist.children[1]);
  await notifyMenuChanged(4);
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "New value");
});

add_task(async function change_datalist_option_value(){
  const datalist = document.getElementById("suggest");

  // Remove the element.
  datalist.removeChild(datalist.children[1]);
  await notifyMenuChanged(0);

  // Change the first element value attribute.
  const prevValue = datalist.children[0].value;
  datalist.children[0].value = "foo";

  const { input } = await openPopupOn("#form3 > input");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "foo");

  datalist.children[0].value = prevValue;
  await notifyMenuChanged(0);
});

add_task(async function change_datalist_option_text_content(){
  const datalist = document.getElementById("suggest");
  const prevValue = datalist.children[0].getAttribute("value");
  datalist.children[0].removeAttribute("value");
  datalist.children[0].textContent = "foobar";
  const { input } = await openPopupOn("#form3 > input");

  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_Enter"],
    "foobar");

  datalist.children[0].setAttribute("value", prevValue);
  await notifyMenuChanged(0);
});

add_task(async function filters_with_1st_letter(){
  const { input } = await openPopupOn("#form3 > input");
  sendString("f");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "final");
});

add_task(async function filters_with_letter_in_the_middle(){
  const { input } = await openPopupOn("#form3 > input");
  sendString("in");
  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
    "final");
});

add_task(async function input_events(){
  const { input } = await openPopupOn("#form3 > input");

  let beforeInputFired = false;
  input.addEventListener("beforeinput", (event) => {
    beforeInputFired = true;
    ok(event instanceof InputEvent, "beforeinput event has InputEvent interface");
    ok(event.bubbles, "beforeinput event should bubble");
    is(
      event.cancelable,
      SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input"),
      "beforeinput event for insertReplacementText should be cancelable if not suppressed");
    is(event.inputType, "insertReplacementText", "inputType of beforeinput event should be insertReplacementText");
    is(event.data, "Google", "data of beforeinput event should be Google");
    is(event.dataTransfer, null, "dataTransfer of beforeinput event should be null");
    is(event.getTargetRanges().length, 0, "getTargetRanges() of beforeinput event should be empty array");
  }, { once: true });

  let inputFired = false;
  input.addEventListener("input", function(event) {
    inputFired = true;
    ok(event instanceof InputEvent,
        "input event should be dispatched with InputEvent interface");
    ok(event.bubbles, "input event should bubble");
    ok(!event.cancelable, "input event should be cancelable");
    is(event.inputType, "insertReplacementText", "inputType of input event should be insertReplacementText");
    is(event.data, "Google", "data of input event should be Google");
    is(event.dataTransfer, null, "dataTransfer of input event should be null");
    is(event.getTargetRanges().length, 0, "getTargetRanges() of input event should be empty array");
  }, { once: true });

  assertValueAfterKeys(input, ["KEY_ArrowDown", "KEY_Enter"], "Google");
  ok(beforeInputFired, "beforeinput event should've been fired");
  ok(inputFired, "input event should've been fired");
});

add_task(async function cancelled_beforeinput_event(){
  const { input } = await openPopupOn("#form3 > input");
  await SpecialPowers.pushPrefEnv({
    set: [["dom.input_event.allow_to_cancel_set_user_input", true]],
  });
  input.addEventListener("beforeinput", e => e.preventDefault(), { once: true });
  let inputFired = false;
  input.addEventListener("input", () => inputFired = true, { once: true });

  assertValueAfterKeys(input, "KEY_ArrowDown", "");
  assertValueAfterKeys(input, "KEY_Enter", "");
  ok(!inputFired, "no input event when beforeinput is canceled");

  input.blur();
  await SpecialPowers.pushPrefEnv({
    clear: [["dom.input_event.allow_to_cancel_set_user_input"]],
  });
});

add_task(async function attempt_to_delete_datalist_entries(){
  const { input } = await openPopupOn("#form5 > input");
  assertAutocompleteItems("history1", "history2", "PASS1", "PASS2", "final");

  assertValueAfterKeys(
    input,
    ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown"],
    "");

  deleteSelectedAutocompleteItem();
  synthesizeKey("KEY_ArrowUp");
  deleteSelectedAutocompleteItem();
  await notifyMenuChanged(4);
  assertAutocompleteItems("history1", "PASS1", "PASS2", "final");
  // Delete the second entry, that is the 1st entry of datalist.
  // This has no effect.
  deleteSelectedAutocompleteItem();

  // Delete the first entry, that is the 1nd entry of history.
  synthesizeKey("KEY_ArrowUp");
  deleteSelectedAutocompleteItem();
  await notifyMenuChanged(3);

  assertAutocompleteItems("PASS1", "PASS2", "final");
  assertValueAfterKeys(input, "KEY_Enter", "Google");
});

for (const input of document
  .querySelectorAll("form#show_datalist_for_text_inputs_only input")) {
  const expectPopup = input.getAttribute("popup") == "true";
  add_named_task(
    `datalist_is_${expectPopup ? "" : "not_"}shown_for_${input.type}_input`,
    async () => {
      input.focus();
      is(document.activeElement, input, "Input is focused.");
      await (expectPopup ? popupByArrowDown() : noPopupByArrowDown());
    }
  );
}
</script>
</body>
</html>
