"use strict";
/**
 * This file contains tests for the Preferences search bar.
 */

/**
 * Test that we only search the selected child of a XUL deck.
 * When we search "Remove Account",
 * it should not show the "Remove Account" button if the Firefox account is not logged in yet.
 */
add_task(async function () {
  await openPreferencesViaOpenPreferencesAPI("paneSync", { leaveOpen: true });

  let weavePrefsDeck =
    gBrowser.contentDocument.getElementById("weavePrefsDeck");
  is(
    weavePrefsDeck.selectedIndex,
    0,
    "Should select the #noFxaAccount child node"
  );

  // Performs search.
  let searchInput = gBrowser.contentDocument.getElementById("searchInput");

  is(
    searchInput,
    gBrowser.contentDocument.activeElement.closest("#searchInput"),
    "Search input should be focused when visiting preferences"
  );

  let query = "Sync";
  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
    gBrowser.contentWindow,
    "PreferencesSearchCompleted",
    evt => evt.detail == query
  );
  EventUtils.sendString(query);
  await searchCompletedPromise;

  let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
  for (let i = 0; i < mainPrefTag.childElementCount; i++) {
    let child = mainPrefTag.children[i];
    if (child.id == "header-searchResults" || child.id == "weavePrefsDeck") {
      is_element_visible(child, "Should be in search results");
    } else if (child.id) {
      is_element_hidden(child, "Should not be in search results");
    }
  }

  // Ensure the "Remove Account" button exists in the hidden child of the <xul:deck>.
  let unlinkFxaAccount = weavePrefsDeck.children[1].querySelector(
    "#unverifiedUnlinkFxaAccount"
  );
  is(
    unlinkFxaAccount.label,
    "Remove Account",
    "The Remove Account button should exist"
  );

  // Performs search.
  searchInput.focus();
  query = "Remove Account";
  searchCompletedPromise = BrowserTestUtils.waitForEvent(
    gBrowser.contentWindow,
    "PreferencesSearchCompleted",
    evt => evt.detail == query
  );
  EventUtils.sendString(query);
  await searchCompletedPromise;

  let noResultsEl = gBrowser.contentDocument.querySelector(
    "#no-results-message"
  );
  is_element_visible(noResultsEl, "Should be reporting no results");

  BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

/**
 * Test that we search using `search-l10n-ids`.
 *
 * The test uses element `showUpdateHistory` and
 * l10n id `language-and-appearance-header` and expects the element
 * to be matched on the first word from the l10n id value ("Language" in en-US).
 */
add_task(async function () {
  let l10nId = "language-and-appearance-header";

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  // First, lets make sure that the element is not matched without
  // `search-l10n-ids`.
  {
    let searchInput = gBrowser.contentDocument.getElementById("searchInput");
    let suhElem = gBrowser.contentDocument.getElementById("showUpdateHistory");

    is(
      searchInput,
      gBrowser.contentDocument.activeElement.closest("#searchInput"),
      "Search input should be focused when visiting preferences"
    );

    ok(
      !suhElem.getAttribute("search-l10n-ids").includes(l10nId),
      "showUpdateHistory element should not contain the l10n id here."
    );

    let query = "Language";
    let searchCompletedPromise = BrowserTestUtils.waitForEvent(
      gBrowser.contentWindow,
      "PreferencesSearchCompleted",
      evt => evt.detail == query
    );
    EventUtils.sendString(query);
    await searchCompletedPromise;

    is_element_hidden(
      suhElem,
      "showUpdateHistory should not be in search results"
    );
  }

  await BrowserTestUtils.removeTab(gBrowser.selectedTab);

  // Now, let's add the l10n id to the element and perform the same search again.

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  {
    let searchInput = gBrowser.contentDocument.getElementById("searchInput");

    is(
      searchInput,
      gBrowser.contentDocument.activeElement.closest("#searchInput"),
      "Search input should be focused when visiting preferences"
    );

    let suhElem = gBrowser.contentDocument.getElementById("showUpdateHistory");
    suhElem.setAttribute("search-l10n-ids", l10nId);

    let query = "Language";
    let searchCompletedPromise = BrowserTestUtils.waitForEvent(
      gBrowser.contentWindow,
      "PreferencesSearchCompleted",
      evt => evt.detail == query
    );
    EventUtils.sendString(query);
    await searchCompletedPromise;

    if (
      AppConstants.platform === "win" &&
      Services.sysinfo.getProperty("hasWinPackageId")
    ) {
      is_element_hidden(
        suhElem,
        "showUpdateHistory should not be in search results"
      );
    } else {
      is_element_visible(
        suhElem,
        "showUpdateHistory should be in search results"
      );
    }
  }

  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

/**
 * Test that search works as expected for custom elements that utilize both
 * slots and shadow DOM. We should be able to find text the shadow DOM.
 */
add_task(async function testSearchShadowDOM() {
  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  // Create the toggle.
  let { mozElements, SHADOW_DOM_TEXT } = createMozCustomElements(gBrowser);

  mozElements.forEach(el => {
    ok(
      !BrowserTestUtils.isVisible(el),
      `${el.localName} is not visible prior to search.`
    );
  });

  // Perform search with text found in moz-toggle's shadow DOM.
  let query = SHADOW_DOM_TEXT;
  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
    gBrowser.contentWindow,
    "PreferencesSearchCompleted",
    evt => evt.detail == query
  );
  EventUtils.sendString(query);
  await searchCompletedPromise;
  mozElements.forEach(el => {
    ok(
      BrowserTestUtils.isVisible(el),
      `${el.localName} is visible after searching for string in the shadow DOM.`
    );
  });

  BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

/**
 * Test that search works as expected for custom elements that utilize both
 * slots and shadow DOM. We should be able to find text the light DOM.
 */
add_task(async function testSearchLightDOM() {
  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  // Create the toggle.
  let { mozElements, LIGHT_DOM_TEXT } = createMozCustomElements(gBrowser, [
    "moz-toggle",
  ]);
  let toggle = mozElements[0];

  // Perform search with text found in moz-toggle's slotted content.
  let query = LIGHT_DOM_TEXT;
  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
    gBrowser.contentWindow,
    "PreferencesSearchCompleted",
    evt => evt.detail == query
  );
  EventUtils.sendString(query);
  await searchCompletedPromise;
  ok(
    BrowserTestUtils.isVisible(toggle),
    "Toggle is visible again after searching for text found in slotted content."
  );

  BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

const MOZ_CUSTOM_ELEMENTS = [
  "moz-toggle",
  "moz-radio-group",
  "moz-radio",
  "moz-checkbox",
];

// Create multiple moz- custom elements with the same label.
function createMozCustomElements(gBrowser, elements = MOZ_CUSTOM_ELEMENTS) {
  const SHADOW_DOM_TEXT = "This text lives in the shadow DOM";
  const LIGHT_DOM_TEXT = "This text lives in the light DOM";

  let doc = gBrowser.contentDocument;
  let mozElements = elements.map(tag => {
    let el = doc.createElement(tag);
    el.label = SHADOW_DOM_TEXT;
    return el;
  });
  let [toggle, radioGroup, radioButton, ...rest] = mozElements;
  let protectionsGroup = doc.getElementById("trackingGroup");

  if (toggle) {
    let link = doc.createElement("a");
    link.href = "https://mozilla.org/";
    link.textContent = LIGHT_DOM_TEXT;
    toggle.append(link);
    link.slot = "support-link";
    protectionsGroup.append(toggle);
  }

  if (radioGroup && radioButton) {
    radioGroup.appendChild(radioButton);
    protectionsGroup.append(radioGroup);
  }

  protectionsGroup.append(...rest);

  return { SHADOW_DOM_TEXT, LIGHT_DOM_TEXT, mozElements };
}
