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

"use strict";

const TEST_URI =
  "data:text/html;charset=utf-8,<!DOCTYPE html>Web Console test for splitting";

// Test is slow on Linux EC2 instances - Bug 962931
requestLongerTimeout(4);

add_task(async function () {
  let toolbox;
  const getFluentString = await getFluentStringHelper([
    "devtools/client/toolbox.ftl",
  ]);
  const hideSplitConsoleLabel = getFluentString(
    "toolbox-meatball-menu-hideconsole-label"
  );

  await addTab(TEST_URI);
  await testConsoleLoadOnDifferentPanel();
  await testKeyboardShortcuts();
  await checkAllTools();

  info("Testing host types");
  checkHostType(Toolbox.HostType.BOTTOM);
  await checkToolboxUI();
  await toolbox.switchHost(Toolbox.HostType.RIGHT);
  checkHostType(Toolbox.HostType.RIGHT);
  await checkToolboxUI();
  await toolbox.switchHost(Toolbox.HostType.WINDOW);

  // checkHostType, below,  will open the meatball menu to read the "Split
  // console" menu item label. However, if we've just opened a new window then
  // on some platforms when we switch focus to the new window we might end up
  // triggering the auto-close behavior on the menu popup. To avoid that, wait
  // a moment before querying the menu.
  await new Promise(resolve => requestIdleCallback(resolve));

  checkHostType(Toolbox.HostType.WINDOW);
  await checkToolboxUI();
  await toolbox.switchHost(Toolbox.HostType.BOTTOM);

  async function testConsoleLoadOnDifferentPanel() {
    info("About to check console loads even when non-webconsole panel is open");

    await openPanel("inspector");
    const webconsoleReady = toolbox.once("webconsole-ready");
    await toolbox.toggleSplitConsole();
    await webconsoleReady;
    ok(
      true,
      "Webconsole has been triggered as loaded while another tool is active"
    );
  }

  async function testKeyboardShortcuts() {
    info("About to check that panel responds to ESCAPE keyboard shortcut");

    const splitConsoleReady = toolbox.once("split-console");
    EventUtils.sendKey("ESCAPE", toolbox.win);
    await splitConsoleReady;
    ok(true, "Split console has been triggered via ESCAPE keypress");
  }

  async function checkAllTools() {
    info("About to check split console with each panel individually.");
    await openAndCheckPanel("jsdebugger");
    await openAndCheckPanel("inspector");
    await openAndCheckPanel("styleeditor");
    await openAndCheckPanel("performance");
    await openAndCheckPanel("netmonitor");

    await checkWebconsolePanelOpened();
  }

  async function getCurrentUIState() {
    const deck = toolbox.doc.querySelector("#toolbox-deck");
    const webconsolePanel = toolbox.webconsolePanel;
    const splitter = toolbox.doc.querySelector("#toolbox-console-splitter");

    const containerHeight = deck.parentNode.getBoundingClientRect().height;
    const deckHeight = deck.getBoundingClientRect().height;
    const webconsoleHeight = webconsolePanel.getBoundingClientRect().height;
    const splitterVisibility = !splitter.hidden;
    // Splitter height will be 1px since the margin is negative.
    const splitterHeight = splitterVisibility ? 1 : 0;
    const openedConsolePanel = toolbox.currentToolId === "webconsole";
    const menuLabel = await getMenuLabel(toolbox);
    const isConsoleVisible =
      webconsolePanel.ownerGlobal.getComputedStyle(webconsolePanel)
        .visibility == "visible";
    const isDeckVisible =
      deck.ownerGlobal.getComputedStyle(deck).visibility == "visible";

    return {
      deckHeight,
      containerHeight,
      webconsoleHeight,
      splitterVisibility,
      splitterHeight,
      openedConsolePanel,
      menuLabel,
      isConsoleVisible,
      isDeckVisible,
    };
  }

  async function getMenuLabel() {
    const button = toolbox.doc.getElementById("toolbox-meatball-menu-button");
    const onPopupShown = new Promise(
      resolve => {
        toolbox.doc.addEventListener("popupshown", () => resolve());
      },
      { once: true }
    );
    info("Click on menu and wait for the popup to be visible");
    AccessibilityUtils.setEnv({
      // Toobox toolbar buttons are handled with arrow keys.
      nonNegativeTabIndexRule: false,
    });
    EventUtils.sendMouseEvent({ type: "click" }, button);
    AccessibilityUtils.resetEnv();
    await onPopupShown;

    const menuItem = toolbox.doc.getElementById(
      "toolbox-meatball-menu-splitconsole"
    );

    // Return undefined if the menu item is not available
    let label;
    if (menuItem && menuItem.querySelector(".label")) {
      label =
        menuItem.querySelector(".label").textContent === hideSplitConsoleLabel
          ? "hide"
          : "split";
    }

    // Wait for menu to close
    const onPopupHide = new Promise(resolve => {
      toolbox.doc.addEventListener(
        "popuphidden",
        () => {
          resolve(label);
        },
        { once: true }
      );
    });
    info("Hit escape and wait for the popup to be closed");
    EventUtils.sendKey("ESCAPE", toolbox.win);
    await onPopupHide;

    return label;
  }

  async function checkWebconsolePanelOpened() {
    info("About to check special cases when webconsole panel is open.");

    // Start with console split, so we can test for transition to main panel.
    await toolbox.toggleSplitConsole();

    let currentUIState = await getCurrentUIState();

    ok(
      currentUIState.splitterVisibility,
      "Splitter is visible when console is split"
    );
    Assert.greater(
      currentUIState.deckHeight,
      0,
      "Deck has a height > 0 when console is split"
    );
    Assert.greater(
      currentUIState.webconsoleHeight,
      75,
      "Web console height is at least set to its minimal height"
    );
    ok(currentUIState.isDeckVisible, "Deck is visible when console is split");

    ok(currentUIState.isConsoleVisible, "Web console is visible");
    ok(
      !currentUIState.openedConsolePanel,
      "The console panel is not the current tool"
    );
    is(
      currentUIState.menuLabel,
      "hide",
      "The menu item indicates the console is split"
    );

    await openPanel("webconsole");
    currentUIState = await getCurrentUIState();

    ok(
      !currentUIState.splitterVisibility,
      "Splitter is hidden when console is opened."
    );
    is(
      currentUIState.deckHeight,
      currentUIState.webconsoleHeight,
      "Deck has the same height as console"
    );
    is(
      currentUIState.webconsoleHeight,
      currentUIState.containerHeight,
      "Web console is full height."
    );
    ok(
      !currentUIState.isDeckVisible,
      "Deck is hidden when the Web console is opened"
    );
    ok(currentUIState.isConsoleVisible, "Web console is visible.");
    ok(
      currentUIState.openedConsolePanel,
      "The console panel is the current tool"
    );
    is(
      currentUIState.menuLabel,
      undefined,
      "The menu item is hidden when console is opened"
    );

    // Make sure splitting console does nothing while webconsole is opened
    await toolbox.toggleSplitConsole();

    currentUIState = await getCurrentUIState();

    ok(
      !currentUIState.splitterVisibility,
      "Splitter is hidden when console is opened."
    );
    is(
      currentUIState.deckHeight,
      currentUIState.webconsoleHeight,
      "Deck has the same height as console"
    );
    is(
      currentUIState.webconsoleHeight,
      currentUIState.containerHeight,
      "Web console is full height."
    );
    ok(
      !currentUIState.isDeckVisible,
      "Deck is hidden when the Web console is opened"
    );
    ok(currentUIState.isConsoleVisible, "Web console is visible.");
    ok(
      currentUIState.openedConsolePanel,
      "The console panel is the current tool"
    );
    is(
      currentUIState.menuLabel,
      undefined,
      "The menu item is hidden when console is opened"
    );

    // Make sure that split state is saved after opening another panel
    await openPanel("inspector");
    currentUIState = await getCurrentUIState();
    ok(
      currentUIState.splitterVisibility,
      "Splitter is visible when console is split"
    );
    Assert.greater(
      currentUIState.deckHeight,
      0,
      "Deck has a height > 0 when console is split"
    );
    Assert.greater(
      currentUIState.webconsoleHeight,
      0,
      "Web console has a height > 0 when console is split"
    );
    ok(currentUIState.isDeckVisible, "Deck is visible when console is split");
    ok(currentUIState.isConsoleVisible, "Web console is visible.");
    ok(
      !currentUIState.openedConsolePanel,
      "The console panel is not the current tool"
    );
    is(
      currentUIState.menuLabel,
      "hide",
      "The menu item still indicates the console is split"
    );

    await toolbox.toggleSplitConsole();
  }

  async function checkToolboxUI() {
    let currentUIState = await getCurrentUIState();

    ok(!currentUIState.splitterVisibility, "Splitter is hidden by default");
    is(
      currentUIState.deckHeight,
      currentUIState.containerHeight,
      "Deck has a height > 0 by default"
    );
    ok(
      currentUIState.isDeckVisible,
      "Deck is visible when the Web console is closed"
    );
    ok(!currentUIState.isConsoleVisible, "Web console is hidden.");
    ok(
      !currentUIState.openedConsolePanel,
      "The console panel is not the current tool"
    );
    is(
      currentUIState.menuLabel,
      "split",
      "The menu item indicates the console is not split"
    );

    await toolbox.toggleSplitConsole();

    currentUIState = await getCurrentUIState();

    ok(
      currentUIState.splitterVisibility,
      "Splitter is visible when console is split"
    );
    Assert.greater(
      currentUIState.deckHeight,
      0,
      "Deck has a height > 0 when console is split"
    );
    Assert.greater(
      currentUIState.webconsoleHeight,
      0,
      "Web console has a height > 0 when console is split"
    );
    ok(currentUIState.isDeckVisible, "Deck is visible when console is split");
    ok(currentUIState.isConsoleVisible, "Web console is visible.");
    is(
      Math.round(
        currentUIState.deckHeight +
          currentUIState.webconsoleHeight +
          currentUIState.splitterHeight
      ),
      Math.round(currentUIState.containerHeight),
      "Everything adds up to container height"
    );
    ok(
      !currentUIState.openedConsolePanel,
      "The console panel is not the current tool"
    );
    is(
      currentUIState.menuLabel,
      "hide",
      "The menu item indicates the console is split"
    );

    await toolbox.toggleSplitConsole();

    currentUIState = await getCurrentUIState();

    ok(!currentUIState.splitterVisibility, "Splitter is hidden after toggling");
    is(
      currentUIState.deckHeight,
      currentUIState.containerHeight,
      "Deck has a height > 0 after toggling"
    );
    ok(currentUIState.isDeckVisible, "Deck is visible when console is closed");
    ok(
      !currentUIState.isConsoleVisible,
      "Web console is hidden after toggling."
    );
    ok(
      !currentUIState.openedConsolePanel,
      "The console panel is not the current tool"
    );
    is(
      currentUIState.menuLabel,
      "split",
      "The menu item indicates the console is not split"
    );
  }

  async function openPanel(toolId) {
    const tab = gBrowser.selectedTab;
    toolbox = await gDevTools.showToolboxForTab(tab, { toolId });
  }

  async function openAndCheckPanel(toolId) {
    await openPanel(toolId);
    await checkToolboxUI(toolbox.getCurrentPanel());
  }

  function checkHostType(hostType) {
    is(toolbox.hostType, hostType, "host type is " + hostType);

    const pref = Services.prefs.getCharPref("devtools.toolbox.host");
    is(pref, hostType, "host pref is " + hostType);
  }
});
