239 lines
6.7 KiB
JavaScript
239 lines
6.7 KiB
JavaScript
/* 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");
|
|
}
|