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

"use strict";

// Test that the Menu API works

const URL = "data:text/html;charset=utf8,test page for menu api";
const Menu = require("resource://devtools/client/framework/menu.js");
const MenuItem = require("resource://devtools/client/framework/menu-item.js");

add_task(async function () {
  info("Create a test tab and open the toolbox");
  const tab = await addTab(URL);
  const toolbox = await gDevTools.showToolboxForTab(tab, {
    toolId: "webconsole",
  });

  // This test will involve localized strings, make sure the necessary FTL file is
  // available in the toolbox top window.
  toolbox.topWindow.MozXULElement.insertFTLIfNeeded(
    "toolkit/global/textActions.ftl"
  );

  loadFTL(toolbox, "toolkit/global/textActions.ftl");

  await testMenuItems();
  await testMenuPopup(toolbox);
  await testSubmenu(toolbox);
});

function testMenuItems() {
  const menu = new Menu();
  const menuItem1 = new MenuItem();
  const menuItem2 = new MenuItem();

  menu.append(menuItem1);
  menu.append(menuItem2);

  is(menu.items.length, 2, "Correct number of 'items'");
  is(menu.items[0], menuItem1, "Correct reference to MenuItem");
  is(menu.items[1], menuItem2, "Correct reference to MenuItem");
}

async function testMenuPopup(toolbox) {
  let clickFired = false;

  const menu = new Menu({
    id: "menu-popup",
  });
  menu.append(new MenuItem({ type: "separator" }));

  const MENU_ITEMS = [
    new MenuItem({
      id: "menu-item-1",
      label: "Normal Item",
      click: () => {
        info("Click callback has fired for menu item");
        clickFired = true;
      },
    }),
    new MenuItem({
      label: "Checked Item",
      type: "checkbox",
      checked: true,
    }),
    new MenuItem({
      label: "Radio Item",
      type: "radio",
    }),
    new MenuItem({
      label: "Disabled Item",
      disabled: true,
    }),
    new MenuItem({
      l10nID: "text-action-undo",
    }),
  ];

  for (const item of MENU_ITEMS) {
    menu.append(item);
  }

  // Append an invisible MenuItem, which shouldn't show up in the DOM
  menu.append(
    new MenuItem({
      label: "Invisible",
      visible: false,
    })
  );

  menu.popup(0, 0, toolbox.doc);
  const popup = toolbox.topDoc.querySelector("#menu-popup");
  ok(popup, "A popup is in the DOM");

  const menuSeparators = toolbox.topDoc.querySelectorAll(
    "#menu-popup > menuseparator"
  );
  is(menuSeparators.length, 1, "A separator is in the menu");

  const menuItems = toolbox.topDoc.querySelectorAll("#menu-popup > menuitem");
  is(menuItems.length, MENU_ITEMS.length, "Correct number of menuitems");

  is(menuItems[0].id, MENU_ITEMS[0].id, "Correct id for menuitem");
  is(menuItems[0].getAttribute("label"), MENU_ITEMS[0].label, "Correct label");

  is(menuItems[1].getAttribute("label"), MENU_ITEMS[1].label, "Correct label");
  is(menuItems[1].getAttribute("type"), "checkbox", "Correct type attr");
  is(menuItems[1].getAttribute("checked"), "true", "Has checked attr");

  is(menuItems[2].getAttribute("label"), MENU_ITEMS[2].label, "Correct label");
  is(menuItems[2].getAttribute("type"), "radio", "Correct type attr");
  ok(!menuItems[2].hasAttribute("checked"), "Doesn't have checked attr");

  is(menuItems[3].getAttribute("label"), MENU_ITEMS[3].label, "Correct label");
  is(menuItems[3].getAttribute("disabled"), "true", "disabled attr menuitem");

  is(
    menuItems[4].getAttribute("data-l10n-id"),
    MENU_ITEMS[4].l10nID,
    "Correct localization attribute"
  );

  await once(menu, "open");
  const closed = once(menu, "close");
  popup.activateItem(menuItems[0]);
  await closed;
  ok(clickFired, "Click has fired");

  ok(
    !toolbox.topDoc.querySelector("#menu-popup"),
    "Popup removed from the DOM"
  );
}

async function testSubmenu(toolbox) {
  let clickFired = false;
  const menu = new Menu({
    id: "menu-popup",
  });
  const submenu = new Menu({
    id: "submenu-popup",
  });
  submenu.append(
    new MenuItem({
      label: "Submenu item",
      click: () => {
        info("Click callback has fired for submenu item");
        clickFired = true;
      },
    })
  );
  menu.append(
    new MenuItem({
      l10nID: "text-action-copy",
      submenu,
    })
  );
  menu.append(
    new MenuItem({
      label: "Submenu parent with attributes",
      id: "submenu-parent-with-attrs",
      submenu,
      accesskey: "A",
      disabled: true,
    })
  );

  menu.popup(0, 0, toolbox.doc);
  const popup = toolbox.topDoc.querySelector("#menu-popup");
  ok(popup, "A popup is in the DOM");
  is(
    toolbox.topDoc.querySelectorAll("#menu-popup > menuitem").length,
    0,
    "No menuitem children"
  );

  const menus = toolbox.topDoc.querySelectorAll("#menu-popup > menu");
  is(menus.length, 2, "Correct number of menus");
  ok(
    !menus[0].hasAttribute("label"),
    "No label: should be set by localization"
  );
  ok(!menus[0].hasAttribute("disabled"), "Correct disabled state");
  is(
    menus[0].getAttribute("data-l10n-id"),
    "text-action-copy",
    "Correct localization attribute"
  );

  is(menus[1].getAttribute("accesskey"), "A", "Correct accesskey");
  ok(menus[1].hasAttribute("disabled"), "Correct disabled state");
  is(menus[1].id, "submenu-parent-with-attrs", "Correct id");

  const subMenuItems = menus[0].querySelectorAll("menupopup > menuitem");
  is(subMenuItems.length, 1, "Correct number of submenu items");
  is(subMenuItems[0].getAttribute("label"), "Submenu item", "Correct label");

  await once(menu, "open");
  const closed = once(menu, "close");

  // The following section tests keyboard navigation of the context menus.
  // This doesn't work on macOS when native context menus are enabled.
  if (Services.prefs.getBoolPref("widget.macos.native-context-menus", false)) {
    info("Using openMenu semantics because of macOS native context menus.");
    let shown = once(menus[0], "popupshown");
    menus[0].openMenu(true);
    await shown;

    const hidden = once(menus[0], "popuphidden");
    menus[0].openMenu(false);
    await hidden;

    shown = once(menus[0], "popupshown");
    menus[0].openMenu(true);
    await shown;
  } else {
    info("Using keyboard navigation to open, close, and reopen the submenu");
    let shown = once(menus[0], "popupshown");
    EventUtils.synthesizeKey("KEY_ArrowDown");
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await shown;

    const hidden = once(menus[0], "popuphidden");
    EventUtils.synthesizeKey("KEY_ArrowLeft");
    await hidden;

    shown = once(menus[0], "popupshown");
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await shown;
  }

  info("Clicking the submenu item");
  const subMenu = subMenuItems[0].closest("menupopup");
  subMenu.activateItem(subMenuItems[0]);

  await closed;
  ok(clickFired, "Click has fired");
}
