/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

// This test makes sure that the autofill placeholder value is autofilled
// correctly.  The placeholder is a string that we immediately autofill when a
// search starts and before its first result arrives in order to prevent text
// flicker in the input.
//
// Because this test specifically checks autofill *before* searches complete, we
// can't use promiseAutocompleteResultPopup() or other helpers that wait for
// searches to complete.  Instead the test uses fireInputEvent() to trigger
// placeholder autofill and then immediately checks autofill status.

"use strict";

// Allow more time for verify mode.
requestLongerTimeout(5);

add_setup(async function () {
  await cleanUp();
});

// Basic origin autofill test.
add_task(async function origin() {
  await addVisits("http://example.com/");

  await search({
    searchString: "ex",
    valueBefore: "ex",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });
  await search({
    searchString: "exa",
    valueBefore: "example.com/",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });
  await search({
    searchString: "EXAM",
    valueBefore: "EXAMple.com/",
    valueAfter: "EXAMple.com/",
    placeholderAfter: "EXAMple.com/",
  });
  await search({
    searchString: "eXaMp",
    valueBefore: "eXaMple.com/",
    valueAfter: "eXaMple.com/",
    placeholderAfter: "eXaMple.com/",
  });
  await search({
    searchString: "exampL",
    valueBefore: "exampLe.com/",
    valueAfter: "exampLe.com/",
    placeholderAfter: "exampLe.com/",
  });
  await search({
    searchString: "example.com",
    valueBefore: "example.com/",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });
  await search({
    searchString: "example.com/",
    valueBefore: "example.com/",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  await cleanUp();
});

// Basic URL autofill test.
add_task(async function url() {
  await addVisits("http://example.com/aaa/bbb/ccc");

  await search({
    searchString: "example.com/a",
    valueBefore: "example.com/a",
    valueAfter: "example.com/aaa/",
    placeholderAfter: "example.com/aaa/",
  });
  await search({
    searchString: "EXAmple.com/aA",
    valueBefore: "EXAmple.com/aAa/",
    valueAfter: "EXAmple.com/aAa/",
    placeholderAfter: "EXAmple.com/aAa/",
  });
  await search({
    searchString: "example.com/aAa",
    valueBefore: "example.com/aAa/",
    valueAfter: "example.com/aAa/",
    placeholderAfter: "example.com/aAa/",
  });
  await search({
    searchString: "example.com/aaa/",
    valueBefore: "example.com/aaa/",
    valueAfter: "example.com/aaa/",
    placeholderAfter: "example.com/aaa/",
  });
  await search({
    searchString: "example.com/aaa/b",
    valueBefore: "example.com/aaa/b",
    valueAfter: "example.com/aaa/bbb/",
    placeholderAfter: "example.com/aaa/bbb/",
  });
  await search({
    searchString: "example.com/aAa/bB",
    valueBefore: "example.com/aAa/bBb/",
    valueAfter: "example.com/aAa/bBb/",
    placeholderAfter: "example.com/aAa/bBb/",
  });
  await search({
    searchString: "example.com/aAa/bBb",
    valueBefore: "example.com/aAa/bBb/",
    valueAfter: "example.com/aAa/bBb/",
    placeholderAfter: "example.com/aAa/bBb/",
  });
  await search({
    searchString: "example.com/aaa/bbb/",
    valueBefore: "example.com/aaa/bbb/",
    valueAfter: "example.com/aaa/bbb/",
    placeholderAfter: "example.com/aaa/bbb/",
  });
  await search({
    searchString: "example.com/aaa/bbb/c",
    valueBefore: "example.com/aaa/bbb/c",
    valueAfter: "example.com/aaa/bbb/ccc",
    placeholderAfter: "example.com/aaa/bbb/ccc",
  });
  await search({
    searchString: "example.com/aAa/bBb/cC",
    valueBefore: "example.com/aAa/bBb/cCc",
    valueAfter: "example.com/aAa/bBb/cCc",
    placeholderAfter: "example.com/aAa/bBb/cCc",
  });
  await search({
    searchString: "example.com/aaa/bbb/ccc",
    valueBefore: "example.com/aaa/bbb/ccc",
    valueAfter: "example.com/aaa/bbb/ccc",
    placeholderAfter: "example.com/aaa/bbb/ccc",
  });

  await cleanUp();
});

// Basic adaptive history autofill test.
add_task(async function adaptiveHistory() {
  UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", true);

  await addVisits("http://example.com/test");
  await UrlbarUtils.addToInputHistory("http://example.com/test", "exa");

  await search({
    searchString: "exa",
    valueBefore: "exa",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });
  await search({
    searchString: "EXAM",
    valueBefore: "EXAMple.com/test",
    valueAfter: "EXAMple.com/test",
    placeholderAfter: "EXAMple.com/test",
  });
  await search({
    searchString: "eXaMpLe",
    valueBefore: "eXaMpLe.com/test",
    valueAfter: "eXaMpLe.com/test",
    placeholderAfter: "eXaMpLe.com/test",
  });
  await search({
    searchString: "example.",
    valueBefore: "example.com/test",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });
  await search({
    searchString: "example.c",
    valueBefore: "example.com/test",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });
  await search({
    searchString: "example.com",
    valueBefore: "example.com/test",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });
  await search({
    searchString: "example.com/",
    valueBefore: "example.com/test",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });
  await search({
    searchString: "example.com/T",
    valueBefore: "example.com/Test",
    valueAfter: "example.com/Test",
    placeholderAfter: "example.com/Test",
  });
  await search({
    searchString: "eXaMple.com/tE",
    valueBefore: "eXaMple.com/tEst",
    valueAfter: "eXaMple.com/tEst",
    placeholderAfter: "eXaMple.com/tEst",
  });
  await search({
    searchString: "example.com/tes",
    valueBefore: "example.com/test",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });
  await search({
    searchString: "example.com/test",
    valueBefore: "example.com/test",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });

  UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
  await cleanUp();
});

// Search engine token alias test (aliases that start with "@").
add_task(async function tokenAlias() {
  // We have built-in engine aliases that may conflict with the one we choose
  // here in terms of autofill, so be careful and choose a weird alias.
  await SearchTestUtils.installSearchExtension({ keyword: "@__example" });

  await search({
    searchString: "@__ex",
    valueBefore: "@__ex",
    valueAfter: "@__example ",
    placeholderAfter: "@__example ",
  });
  await search({
    searchString: "@__exa",
    valueBefore: "@__example ",
    valueAfter: "@__example ",
    placeholderAfter: "@__example ",
  });
  await search({
    searchString: "@__EXAM",
    valueBefore: "@__EXAMple ",
    valueAfter: "@__EXAMple ",
    placeholderAfter: "@__EXAMple ",
  });
  await search({
    searchString: "@__eXaMp",
    valueBefore: "@__eXaMple ",
    valueAfter: "@__eXaMple ",
    placeholderAfter: "@__eXaMple ",
  });
  await search({
    searchString: "@__exampl",
    valueBefore: "@__example ",
    valueAfter: "@__example ",
    placeholderAfter: "@__example ",
  });

  await cleanUp();
});

// The placeholder should not be used for a search that does not autofill, and
// it should be cleared after the search completes.
add_task(async function noAutofill() {
  await addVisits("http://example.com/");

  // Do an initial search that triggers autofill so that the placeholder has an
  // initial value.
  await search({
    searchString: "ex",
    valueBefore: "ex",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  // Search with a string that does not match the placeholder.  Placeholder
  // autofill shouldn't happen.
  await search({
    searchString: "moz",
    valueBefore: "moz",
    valueAfter: "moz",
    placeholderAfter: "",
  });

  // Search for "ex" again. It should be autofilled when the search completes
  // but the placeholder will not be autofilled.
  await search({
    searchString: "ex",
    valueBefore: "ex",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  // Continue with a series of searches that should all use the placeholder.
  await search({
    searchString: "exa",
    valueBefore: "example.com/",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });
  await search({
    searchString: "EXAM",
    valueBefore: "EXAMple.com/",
    valueAfter: "EXAMple.com/",
    placeholderAfter: "EXAMple.com/",
  });
  await search({
    searchString: "eXaMp",
    valueBefore: "eXaMple.com/",
    valueAfter: "eXaMple.com/",
    placeholderAfter: "eXaMple.com/",
  });
  await search({
    searchString: "exampl",
    valueBefore: "example.com/",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  await cleanUp();
});

// The placeholder should not be used for a search that autofills a different
// value.
add_task(async function differentAutofill() {
  await addVisits("http://mozilla.org/", "http://example.com/");

  // Do an initial search that triggers autofill so that the placeholder has an
  // initial value.
  await search({
    searchString: "moz",
    valueBefore: "moz",
    valueAfter: "mozilla.org/",
    placeholderAfter: "mozilla.org/",
  });

  // Search with a string that does not match the placeholder but does trigger
  // autofill.  Placeholder autofill shouldn't happen.
  await search({
    searchString: "ex",
    valueBefore: "ex",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  // Continue with a series of searches that should all use the placeholder.
  await search({
    searchString: "exa",
    valueBefore: "example.com/",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });
  await search({
    searchString: "EXAm",
    valueBefore: "EXAmple.com/",
    valueAfter: "EXAmple.com/",
    placeholderAfter: "EXAmple.com/",
  });

  // Search for "moz" again.  It should be autofilled.  Placeholder autofill
  // shouldn't happen.
  await search({
    searchString: "moz",
    valueBefore: "moz",
    valueAfter: "mozilla.org/",
    placeholderAfter: "mozilla.org/",
  });

  // Continue with a series of searches that should all use the placeholder.
  await search({
    searchString: "mozi",
    valueBefore: "mozilla.org/",
    valueAfter: "mozilla.org/",
    placeholderAfter: "mozilla.org/",
  });
  await search({
    searchString: "MOZil",
    valueBefore: "MOZilla.org/",
    valueAfter: "MOZilla.org/",
    placeholderAfter: "MOZilla.org/",
  });

  await cleanUp();
});

// The placeholder should not be used for a search that uses a bookmark keyword
// even when the keyword matches the placeholder, and the placeholder should be
// cleared after the search completes.
add_task(async function bookmarkKeyword() {
  // Add a visit to example.com.
  await addVisits("https://example.com/");

  // Add a bookmark keyword that is a prefix of example.com.
  await PlacesUtils.keywords.insert({
    keyword: "ex",
    url: "https://somekeyword.com/",
  });

  // Do an initial search that triggers autofill for the visit so that the
  // placeholder has an initial value of "example.com/".
  await search({
    searchString: "e",
    valueBefore: "e",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  // Do a search that matches the bookmark keyword. The placeholder from the
  // search above should be autofilled since the autofill placeholder
  // ("example.com/") starts with the keyword ("ex"), but then when the bookmark
  // result arrives, the autofilled value and placeholder should be cleared.
  await search({
    searchString: "ex",
    valueBefore: "example.com/",
    valueAfter: "ex",
    placeholderAfter: "",
  });

  // Do another search that simulates the user continuing to type "example". No
  // placeholder should be autofilled, but once the autofill result arrives for
  // the visit, "example.com/" should be autofilled.
  await search({
    searchString: "exa",
    valueBefore: "exa",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  await PlacesUtils.keywords.remove("ex");
  await cleanUp();
});

// The placeholder should not be used for a search that doesn't match its URI
// fragment. This task uses a URL whose path is "/".
add_task(async function noURIFragmentMatch1() {
  await addVisits("https://example.com/#TEST");

  const testData = [
    {
      desc: "Autofill example.com/#TEST then search for example.com/#Te",
      searches: [
        {
          searchString: "example.com/#T",
          valueBefore: "example.com/#T",
          valueAfter: "example.com/#TEST",
          placeholderAfter: "example.com/#TEST",
        },
        {
          searchString: "example.com/#Te",
          valueBefore: "example.com/#Te",
          valueAfter: "example.com/#Te",
          placeholderAfter: "",
        },
      ],
    },
    {
      desc: "Autofill https://example.com/#TEST then search for https://example.com/#Te",
      searches: [
        {
          searchString: "https://example.com/#T",
          valueBefore: "https://example.com/#T",
          valueAfter: "https://example.com/#TEST",
          placeholderAfter: "https://example.com/#TEST",
        },
        {
          searchString: "https://example.com/#Te",
          valueBefore: "https://example.com/#Te",
          valueAfter: "https://example.com/#Te",
          placeholderAfter: "",
        },
      ],
    },
    {
      desc: "Autofill example.com/#TEST then search for example.com/",
      searches: [
        {
          searchString: "example.com/#T",
          valueBefore: "example.com/#T",
          valueAfter: "example.com/#TEST",
          placeholderAfter: "example.com/#TEST",
        },
        {
          searchString: "example.com/",
          valueBefore: "example.com/",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
  ];

  for (const { desc, searches } of testData) {
    info("Running subtest: " + desc);

    for (let i = 0; i < searches.length; i++) {
      info("Doing search at index " + i);
      await search(searches[i]);
    }

    // Clear the placeholder for the next subtest.
    info("Doing extra search to clear placeholder");
    await search({
      searchString: "no match",
      valueBefore: "no match",
      valueAfter: "no match",
      placeholderAfter: "",
    });
  }

  await cleanUp();
});

// The placeholder should not be used for a search that doesn't match its URI
// fragment. This task uses a URL whose path is "/foo".
add_task(async function noURIFragmentMatch2() {
  await addVisits("https://example.com/foo#TEST");

  const testData = [
    {
      desc: "Autofill example.com/foo#TEST then search for example.com/foo#Te",
      searches: [
        {
          searchString: "example.com/foo#T",
          valueBefore: "example.com/foo#T",
          valueAfter: "example.com/foo#TEST",
          placeholderAfter: "example.com/foo#TEST",
        },
        {
          searchString: "example.com/foo#Te",
          valueBefore: "example.com/foo#Te",
          valueAfter: "example.com/foo#Te",
          placeholderAfter: "",
        },
      ],
    },
    {
      desc: "Autofill https://example.com/foo#TEST then search for https://example.com/foo#Te",
      searches: [
        {
          searchString: "https://example.com/foo#T",
          valueBefore: "https://example.com/foo#T",
          valueAfter: "https://example.com/foo#TEST",
          placeholderAfter: "https://example.com/foo#TEST",
        },
        {
          searchString: "https://example.com/foo#Te",
          valueBefore: "https://example.com/foo#Te",
          valueAfter: "https://example.com/foo#Te",
          placeholderAfter: "",
        },
      ],
    },
    {
      desc: "Autofill example.com/foo#TEST then search for example.com/",
      searches: [
        {
          searchString: "example.com/foo#T",
          valueBefore: "example.com/foo#T",
          valueAfter: "example.com/foo#TEST",
          placeholderAfter: "example.com/foo#TEST",
        },
        {
          searchString: "example.com/",
          valueBefore: "example.com/",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
  ];

  for (const { desc, searches } of testData) {
    info("Running subtest: " + desc);

    for (let i = 0; i < searches.length; i++) {
      info("Doing search at index " + i);
      await search(searches[i]);
    }

    // Clear the placeholder for the next subtest.
    info("Doing extra search to clear placeholder");
    await search({
      searchString: "no match",
      valueBefore: "no match",
      valueAfter: "no match",
      placeholderAfter: "",
    });
  }

  await cleanUp();
});

// The placeholder should not be used for a search that does not autofill its
// URL path.
add_task(async function noPathMatch() {
  await addVisits("http://example.com/shallow/deep/file");

  const testData = [
    {
      desc: "Autofill example.com/shallow/ then search for exam",
      searches: [
        {
          searchString: "example.com/s",
          valueBefore: "example.com/s",
          valueAfter: "example.com/shallow/",
          placeholderAfter: "example.com/shallow/",
        },
        {
          searchString: "exam",
          valueBefore: "exam",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/ then search for example.com/",
      searches: [
        {
          searchString: "example.com/s",
          valueBefore: "example.com/s",
          valueAfter: "example.com/shallow/",
          placeholderAfter: "example.com/shallow/",
        },
        {
          searchString: "example.com/",
          valueBefore: "example.com/",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/ then search for exam",
      searches: [
        {
          searchString: "example.com/shallow/d",
          valueBefore: "example.com/shallow/d",
          valueAfter: "example.com/shallow/deep/",
          placeholderAfter: "example.com/shallow/deep/",
        },
        {
          searchString: "exam",
          valueBefore: "exam",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/ then search for example.com/",
      searches: [
        {
          searchString: "example.com/shallow/d",
          valueBefore: "example.com/shallow/d",
          valueAfter: "example.com/shallow/deep/",
          placeholderAfter: "example.com/shallow/deep/",
        },
        {
          searchString: "example.com/",
          valueBefore: "example.com/",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/ then search for example.com/s",
      searches: [
        {
          searchString: "example.com/shallow/d",
          valueBefore: "example.com/shallow/d",
          valueAfter: "example.com/shallow/deep/",
          placeholderAfter: "example.com/shallow/deep/",
        },
        {
          searchString: "example.com/s",
          valueBefore: "example.com/s",
          valueAfter: "example.com/shallow/",
          placeholderAfter: "example.com/shallow/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/ then search for example.com/shallow/",
      searches: [
        {
          searchString: "example.com/shallow/d",
          valueBefore: "example.com/shallow/d",
          valueAfter: "example.com/shallow/deep/",
          placeholderAfter: "example.com/shallow/deep/",
        },
        {
          searchString: "example.com/shallow/",
          valueBefore: "example.com/shallow/",
          valueAfter: "example.com/shallow/",
          placeholderAfter: "example.com/shallow/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/file then search for exam",
      searches: [
        {
          searchString: "example.com/shallow/deep/f",
          valueBefore: "example.com/shallow/deep/f",
          valueAfter: "example.com/shallow/deep/file",
          placeholderAfter: "example.com/shallow/deep/file",
        },
        {
          searchString: "exam",
          valueBefore: "exam",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/file then search for example.com/",
      searches: [
        {
          searchString: "example.com/shallow/deep/f",
          valueBefore: "example.com/shallow/deep/f",
          valueAfter: "example.com/shallow/deep/file",
          placeholderAfter: "example.com/shallow/deep/file",
        },
        {
          searchString: "example.com/",
          valueBefore: "example.com/",
          valueAfter: "example.com/",
          placeholderAfter: "example.com/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/file then search for example.com/s",
      searches: [
        {
          searchString: "example.com/shallow/deep/f",
          valueBefore: "example.com/shallow/deep/f",
          valueAfter: "example.com/shallow/deep/file",
          placeholderAfter: "example.com/shallow/deep/file",
        },
        {
          searchString: "example.com/s",
          valueBefore: "example.com/s",
          valueAfter: "example.com/shallow/",
          placeholderAfter: "example.com/shallow/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/file then search for example.com/shallow/",
      searches: [
        {
          searchString: "example.com/shallow/deep/f",
          valueBefore: "example.com/shallow/deep/f",
          valueAfter: "example.com/shallow/deep/file",
          placeholderAfter: "example.com/shallow/deep/file",
        },
        {
          searchString: "example.com/shallow/",
          valueBefore: "example.com/shallow/",
          valueAfter: "example.com/shallow/",
          placeholderAfter: "example.com/shallow/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/file then search for example.com/shallow/d",
      searches: [
        {
          searchString: "example.com/shallow/deep/f",
          valueBefore: "example.com/shallow/deep/f",
          valueAfter: "example.com/shallow/deep/file",
          placeholderAfter: "example.com/shallow/deep/file",
        },
        {
          searchString: "example.com/shallow/d",
          valueBefore: "example.com/shallow/d",
          valueAfter: "example.com/shallow/deep/",
          placeholderAfter: "example.com/shallow/deep/",
        },
      ],
    },
    {
      desc: "Autofill example.com/shallow/deep/file then search for example.com/shallow/deep/",
      searches: [
        {
          searchString: "example.com/shallow/deep/f",
          valueBefore: "example.com/shallow/deep/f",
          valueAfter: "example.com/shallow/deep/file",
          placeholderAfter: "example.com/shallow/deep/file",
        },
        {
          searchString: "example.com/shallow/deep/fi",
          valueBefore: "example.com/shallow/deep/file",
          valueAfter: "example.com/shallow/deep/file",
          placeholderAfter: "example.com/shallow/deep/file",
        },
        {
          searchString: "example.com/shallow/deep/",
          valueBefore: "example.com/shallow/deep/",
          valueAfter: "example.com/shallow/deep/",
          placeholderAfter: "example.com/shallow/deep/",
        },
      ],
    },
  ];

  for (const { desc, searches } of testData) {
    info("Running subtest: " + desc);

    for (let i = 0; i < searches.length; i++) {
      info("Doing search at index " + i);
      await search(searches[i]);
    }

    // Clear the placeholder for the next subtest.
    info("Doing extra search to clear placeholder");
    await search({
      searchString: "no match",
      valueBefore: "no match",
      valueAfter: "no match",
      placeholderAfter: "",
    });
  }

  await cleanUp();
});

// An adaptive history placeholder should not be used for a search that does not
// autofill it.
add_task(async function noAdaptiveHistoryMatch() {
  UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", true);

  await addVisits("http://example.com/test");
  await UrlbarUtils.addToInputHistory("http://example.com/test", "exam");

  // Search for a longer string than the adaptive history input. Adaptive
  // history autofill should be triggered.
  await search({
    searchString: "example",
    valueBefore: "example",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });

  // Search for the same string as the adaptive history input. The placeholder
  // from the previous search should be used and adaptive history autofill
  // should be triggered.
  await search({
    searchString: "exam",
    valueBefore: "example.com/test",
    valueAfter: "example.com/test",
    placeholderAfter: "example.com/test",
  });

  // Search for a shorter string than the adaptive history input. The
  // placeholder from the previous search should not be used since the search
  // string is shorter than the adaptive history input.
  await search({
    searchString: "ex",
    valueBefore: "ex",
    valueAfter: "example.com/",
    placeholderAfter: "example.com/",
  });

  UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
  await cleanUp();
});

/**
 * This function does the following:
 *
 * 1. Starts a search with `searchString` but doesn't wait for it to complete.
 * 2. Compares the input value to `valueBefore`. If anything is autofilled at
 *    this point, it will be due to the placeholder.
 * 3. Waits for the search to complete.
 * 4. Compares the input value to `valueAfter`. If anything is autofilled at
 *    this point, it will be due to the autofill result fetched by the search.
 * 5. Compares the placeholder to `placeholderAfter`.
 *
 * @param {object} options
 *   The options object.
 * @param {string} options.searchString
 *   The search string.
 * @param {string} options.valueBefore
 *   The expected input value before the search completes.
 * @param {string} options.valueAfter
 *   The expected input value after the search completes.
 * @param {string} options.placeholderAfter
 *   The expected placeholder value after the search completes.
 * @returns {Promise}
 */
async function search({
  searchString,
  valueBefore,
  valueAfter,
  placeholderAfter,
}) {
  info(
    "Searching: " +
      JSON.stringify({
        searchString,
        valueBefore,
        valueAfter,
        placeholderAfter,
      })
  );

  await SimpleTest.promiseFocus(window);
  gURLBar.inputField.focus();

  // Set the input value and move the caret to the end to simulate the user
  // typing. It's important the caret is at the end because otherwise autofill
  // won't happen.
  gURLBar.value = searchString;
  gURLBar.inputField.setSelectionRange(
    searchString.length,
    searchString.length
  );

  // Placeholder autofill is done on input, so fire an input event. We can't use
  // `promiseAutocompleteResultPopup()` or other helpers that wait for the
  // search to complete because we are specifically checking placeholder
  // autofill before the search completes.
  UrlbarTestUtils.fireInputEvent(window);

  // Check the input value and selection immediately, before waiting on the
  // search to complete.
  Assert.equal(
    gURLBar.value,
    valueBefore,
    "gURLBar.value before the search completes"
  );
  Assert.equal(
    gURLBar.selectionStart,
    searchString.length,
    "gURLBar.selectionStart before the search completes"
  );
  Assert.equal(
    gURLBar.selectionEnd,
    valueBefore.length,
    "gURLBar.selectionEnd before the search completes"
  );

  // Wait for the search to complete.
  info("Waiting for the search to complete");
  await UrlbarTestUtils.promiseSearchComplete(window);

  // Check the final value after the results arrived.
  Assert.equal(
    gURLBar.value,
    valueAfter,
    "gURLBar.value after the search completes"
  );
  Assert.equal(
    gURLBar.selectionStart,
    searchString.length,
    "gURLBar.selectionStart after the search completes"
  );
  Assert.equal(
    gURLBar.selectionEnd,
    valueAfter.length,
    "gURLBar.selectionEnd after the search completes"
  );

  // Check the placeholder.
  if (placeholderAfter) {
    Assert.ok(
      gURLBar._autofillPlaceholder,
      "gURLBar._autofillPlaceholder exists after the search completes"
    );
    Assert.strictEqual(
      gURLBar._autofillPlaceholder.value,
      placeholderAfter,
      "gURLBar._autofillPlaceholder.value after the search completes"
    );
  } else {
    Assert.strictEqual(
      gURLBar._autofillPlaceholder,
      null,
      "gURLBar._autofillPlaceholder does not exist after the search completes"
    );
  }

  // Check the first result.
  let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
  Assert.equal(
    !!details.autofill,
    !!placeholderAfter,
    "First result is an autofill result iff a placeholder is expected"
  );
}

/**
 * Adds enough visits to URLs so their origins start autofilling.
 *
 * @param {...string} urls The URLs to add visits to.
 */
async function addVisits(...urls) {
  for (let url of urls) {
    for (let i = 0; i < 5; i++) {
      await PlacesTestUtils.addVisits(url);
    }
  }
}

async function cleanUp() {
  await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
  await PlacesUtils.bookmarks.eraseEverything();
  await PlacesUtils.history.clear();
}
