/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

/* exported runTests */
// This file is imported into the same scope as head.js.
/* import-globals-from head.js */

{
  // At the moment extension language negotiation is tied to Firefox language
  // negotiation result. That means that to test an extension in `es-ES`, we need
  // to mock `es-ES` being available in Firefox and then request it.
  //
  // In the future, we should provide some way for tests to decouple their
  // language selection from that of Firefox.
  const avLocales = Services.locale.availableLocales;

  Services.locale.availableLocales = ["en-US", "es-ES"];
  registerCleanupFunction(() => {
    Services.locale.availableLocales = avLocales;
  });
}

async function runTests(options) {
  function background(getTests) {
    let tests;

    // Gets the current details of the page action, and returns a
    // promise that resolves to an object containing them.
    async function getDetails(tabId) {
      return {
        title: await browser.pageAction.getTitle({ tabId }),
        popup: await browser.pageAction.getPopup({ tabId }),
        isShown: await browser.pageAction.isShown({ tabId }),
      };
    }

    // Runs the next test in the `tests` array, checks the results,
    // and passes control back to the outer test scope.
    function nextTest() {
      let test = tests.shift();

      test(async expecting => {
        let [tab] = await browser.tabs.query({
          active: true,
          currentWindow: true,
        });
        let { id: tabId, windowId, url } = tab;

        browser.test.log(`Get details: tab={id: ${tabId}, url: ${url}}`);

        // Check that the API returns the expected values, and then
        // run the next test.
        let details = await getDetails(tabId);
        if (expecting) {
          browser.test.assertEq(
            expecting.title,
            details.title,
            "expected value from getTitle"
          );

          browser.test.assertEq(
            expecting.popup,
            details.popup,
            "expected value from getPopup"
          );
        }

        browser.test.assertEq(
          !!expecting,
          details.isShown,
          "expected value from isShown"
        );

        // Check that the actual icon has the expected values, then
        // run the next test.
        browser.test.sendMessage("nextTest", expecting, windowId, tests.length);
      });
    }

    async function runTests() {
      let tabs = [];
      let windows = [];
      tests = getTests(tabs, windows);

      let resultTabs = await browser.tabs.query({
        active: true,
        currentWindow: true,
      });

      tabs[0] = resultTabs[0].id;
      windows[0] = resultTabs[0].windowId;

      nextTest();
    }

    browser.test.onMessage.addListener(msg => {
      if (msg == "runTests") {
        runTests();
      } else if (msg == "runNextTest") {
        nextTest();
      } else {
        browser.test.fail(`Unexpected message: ${msg}`);
      }
    });

    runTests();
  }

  let extension = ExtensionTestUtils.loadExtension({
    manifest: options.manifest,

    files: options.files || {},

    background: `(${background})(${options.getTests})`,
  });

  let pageActionId;
  let currentWindow = window;
  let windows = [];

  async function waitForDetails(details, windowId) {
    function check() {
      let { document } = Services.wm.getOuterWindowWithId(windowId);
      let image = document.getElementById(pageActionId);
      if (details == null) {
        return image == null || image.getAttribute("disabled") == "true";
      }
      let title = details.title || options.manifest.name;
      return (
        !!image &&
        getListStyleImage(image) == details.icon &&
        image.getAttribute("tooltiptext") == title &&
        image.getAttribute("aria-label") == title
      );
      // TODO: Popup URL. If this is updated, modify also checkDetails.
    }

    // eslint-disable-next-line no-async-promise-executor
    return new Promise(async resolve => {
      let maxCounter = 10;
      while (!check() && --maxCounter > 0) {
        info("checks left: " + maxCounter);
        await promiseAnimationFrame(currentWindow);
      }
      resolve();
    });
  }

  function checkDetails(details, windowId) {
    let { document } = Services.wm.getOuterWindowWithId(windowId);
    let image = document.getElementById(pageActionId);
    if (details == null) {
      ok(
        image == null || image.getAttribute("disabled") == "true",
        "image is disabled"
      );
    } else {
      ok(image, "image exists");

      is(getListStyleImage(image), details.icon, "icon URL is correct");

      let title = details.title || options.manifest.name;
      is(image.getAttribute("tooltiptext"), title, "image title is correct");
      is(
        image.getAttribute("aria-label"),
        title,
        "image aria-label is correct"
      );
      // TODO: Popup URL. If this is updated, modify also waitForDetails.
    }
  }

  let testNewWindows = 1;

  let awaitFinish = new Promise(resolve => {
    extension.onMessage(
      "nextTest",
      async (expecting, windowId, testsRemaining) => {
        if (!pageActionId) {
          pageActionId = BrowserPageActions.urlbarButtonNodeIDForActionID(
            makeWidgetId(extension.id)
          );
        }

        await waitForDetails(expecting, windowId);

        checkDetails(expecting, windowId);

        if (testsRemaining) {
          extension.sendMessage("runNextTest");
        } else if (testNewWindows) {
          testNewWindows--;

          BrowserTestUtils.openNewBrowserWindow()
            .then(window => {
              windows.push(window);
              currentWindow = window;
              return focusWindow(window);
            })
            .then(() => {
              extension.sendMessage("runTests");
            });
        } else {
          resolve();
        }
      }
    );
  });

  let reqLoc = Services.locale.requestedLocales;
  Services.locale.requestedLocales = ["es-ES"];

  await extension.startup();

  await awaitFinish;

  await extension.unload();

  Services.locale.requestedLocales = reqLoc;

  let node = document.getElementById(pageActionId);
  is(node, null, "pageAction image removed from document");

  currentWindow = null;
  for (let win of windows.splice(0)) {
    node = win.document.getElementById(pageActionId);
    is(node, null, "pageAction image removed from second document");

    await BrowserTestUtils.closeWindow(win);
  }
}
