/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// These tests check the behavior of the Urlbar when loading a page
// whose url matches that of the default search engine.

let defaultTestEngine;

// The main search string used in tests
const SEARCH_STRING = "chocolate cake";

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.showSearchTerms.featureGate", true]],
  });

  await SearchTestUtils.installSearchExtension(
    {
      name: "MozSearch",
      search_url: "https://www.example.com/",
      search_url_get_params: "q={searchTerms}&pc=fake_code",
    },
    { setAsDefault: true }
  );
  defaultTestEngine = Services.search.getEngineByName("MozSearch");

  registerCleanupFunction(async function () {
    await PlacesUtils.history.clear();
  });
});

// Starts a search with a tab and asserts that
// the state of the Urlbar contains the search term
async function searchWithTab(
  searchString,
  tab = null,
  engine = defaultTestEngine
) {
  if (!tab) {
    tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
  }

  let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, searchString);
  let browserLoadedPromise = BrowserTestUtils.browserLoaded(
    tab.linkedBrowser,
    false,
    expectedSearchUrl
  );

  gURLBar.focus();
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: searchString,
    fireInputEvent: true,
  });
  EventUtils.synthesizeKey("KEY_Enter");
  await browserLoadedPromise;

  assertSearchStringIsInUrlbar(searchString);

  return { tab, expectedSearchUrl };
}

// Search terms should show up in the url bar if the pref is on
// and the SERP url matches the one constructed in Firefox
add_task(async function list_of_search_strings() {
  const searches = [
    {
      // Single word
      searchString: "chocolate",
    },
    {
      // Word with space
      searchString: "chocolate cake",
    },
    {
      // Special characters
      searchString: "chocolate;,?:@&=+$-_.!~*'()#cake",
    },
    {
      searchString: '"chocolate cake" -recipes',
    },
    {
      // Search with special characters
      searchString: "site:example.com chocolate -cake",
    },
  ];

  for (let { searchString } of searches) {
    let { tab } = await searchWithTab(searchString);
    BrowserTestUtils.removeTab(tab);
  }
});

// If a user does a search, goes to another page, and then
// goes back to the SERP, the search term should show.
add_task(async function go_back() {
  let { tab } = await searchWithTab(SEARCH_STRING);

  let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
  BrowserTestUtils.loadURIString(
    tab.linkedBrowser,
    "http://www.example.com/some_url"
  );
  await browserLoadedPromise;

  let pageShowPromise = BrowserTestUtils.waitForContentEvent(
    tab.linkedBrowser,
    "pageshow"
  );
  tab.linkedBrowser.goBack();
  await pageShowPromise;

  assertSearchStringIsInUrlbar(SEARCH_STRING);

  BrowserTestUtils.removeTab(tab);
});

// Manually loading a url that matches a search query url
// should show the search term in the Urlbar.
add_task(async function load_url() {
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
  let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
    defaultTestEngine,
    SEARCH_STRING
  );
  let browserLoadedPromise = BrowserTestUtils.browserLoaded(
    tab.linkedBrowser,
    false,
    expectedSearchUrl
  );
  BrowserTestUtils.loadURIString(tab.linkedBrowser, expectedSearchUrl);
  await browserLoadedPromise;
  assertSearchStringIsInUrlbar(SEARCH_STRING);

  BrowserTestUtils.removeTab(tab);
});

// Focusing and blurring the urlbar while the search terms
// persist should change the pageproxystate.
add_task(async function focus_and_unfocus() {
  let { tab } = await searchWithTab(SEARCH_STRING);

  gURLBar.focus();
  Assert.equal(
    gURLBar.getAttribute("pageproxystate"),
    "invalid",
    "Should have matching pageproxystate."
  );

  gURLBar.blur();
  Assert.equal(
    gURLBar.getAttribute("pageproxystate"),
    "valid",
    "Should have matching pageproxystate."
  );

  BrowserTestUtils.removeTab(tab);
});

// If the user modifies the search term, blurring the
// urlbar should keep the urlbar in an invalid pageproxystate.
add_task(async function focus_and_unfocus_modified() {
  let { tab } = await searchWithTab(SEARCH_STRING);

  gURLBar.focus();
  Assert.equal(
    gURLBar.getAttribute("pageproxystate"),
    "invalid",
    "Should have matching pageproxystate."
  );

  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: "another search term",
    fireInputEvent: true,
  });

  gURLBar.blur();
  Assert.equal(
    gURLBar.getAttribute("pageproxystate"),
    "invalid",
    "Should have matching pageproxystate."
  );

  BrowserTestUtils.removeTab(tab);
});

// If Top Sites is cached in the UrlbarView, don't show it if the search terms
// persist in the Urlbar.
add_task(async function focus_after_top_sites() {
  await SpecialPowers.pushPrefEnv({
    set: [
      // Prevent the persist tip from interrupting clicking the Urlbar
      // after the the SERP has been loaded.
      [
        `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}`,
        10000,
      ],
      ["browser.newtabpage.activity-stream.feeds.topsites", true],
    ],
  });

  // Populate Top Sites on a clean version of Places.
  await PlacesUtils.history.clear();
  await PlacesUtils.bookmarks.eraseEverything();
  await PlacesTestUtils.promiseAsyncUpdates();
  await TestUtils.waitForTick();

  const urls = [];
  const N_TOP_SITES = 5;
  const N_VISITS = 5;

  for (let i = 0; i < N_TOP_SITES; i++) {
    let url = `https://${i}.example.com/hello_world${i}`;
    urls.unshift(url);
    // Each URL needs to be added several times to boost its frecency enough to
    // qualify as a top site.
    for (let j = 0; j < N_VISITS; j++) {
      await PlacesTestUtils.addVisits(url);
    }
  }

  let changedPromise = TestUtils.topicObserved("newtab-top-sites-changed").then(
    () => info("Observed newtab-top-sites-changed")
  );
  await updateTopSites(sites => sites?.length == N_TOP_SITES);
  await changedPromise;

  // Ensure Top Sites is cached.
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
  await UrlbarTestUtils.promisePopupOpen(window, () => {
    EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
  });
  Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
  await UrlbarTestUtils.promiseSearchComplete(window);
  Assert.equal(
    UrlbarTestUtils.getResultCount(window),
    N_TOP_SITES,
    `The number of results should be the same as the number of Top Sites ${N_TOP_SITES}.`
  );
  for (let i = 0; i < urls.length; i++) {
    let { url } = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
    Assert.equal(url, urls[i], "The result url should be a Top Site.");
  }

  let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
    defaultTestEngine,
    SEARCH_STRING
  );
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: SEARCH_STRING,
    fireInputEvent: true,
  });
  EventUtils.synthesizeKey("KEY_Enter");

  await BrowserTestUtils.browserLoaded(
    gBrowser.selectedBrowser,
    false,
    expectedSearchUrl
  );
  Assert.equal(
    gBrowser.selectedBrowser.searchTerms,
    SEARCH_STRING,
    "The search term should be in the Urlbar."
  );

  await UrlbarTestUtils.promisePopupOpen(window, () => {
    EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
  });
  let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
  Assert.notEqual(
    details.url,
    urls[0],
    "The first result should not be a Top Site."
  );
  Assert.equal(
    details.heuristic,
    true,
    "The first result should be the heuristic result."
  );
  Assert.equal(
    details.url,
    expectedSearchUrl,
    "The first result url should be the same as the SERP."
  );
  Assert.equal(
    details.type,
    UrlbarUtils.RESULT_TYPE.SEARCH,
    "The first result be a search result."
  );
  Assert.equal(
    details.searchParams?.query,
    SEARCH_STRING,
    "The first result should have a matching query."
  );

  // Clean up.
  SpecialPowers.popPrefEnv();
  BrowserTestUtils.removeTab(tab);
});
