summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/test/browser_menu_api.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/test/browser_menu_api.js')
-rw-r--r--devtools/client/framework/test/browser_menu_api.js239
1 files changed, 239 insertions, 0 deletions
diff --git a/devtools/client/framework/test/browser_menu_api.js b/devtools/client/framework/test/browser_menu_api.js
new file mode 100644
index 0000000000..daa69cf8dd
--- /dev/null
+++ b/devtools/client/framework/test/browser_menu_api.js
@@ -0,0 +1,239 @@
+/* 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");
+}