diff --git a/src/command/Menus.js b/src/command/Menus.js
index 7c7151e814..18fb38ea21 100644
--- a/src/command/Menus.js
+++ b/src/command/Menus.js
@@ -1369,10 +1369,9 @@ define(function (require, exports, module) {
$menuDropdownToggle.parent().removeClass('open');
const menuID = $menuDropdownToggle.parent().get(0).id;
const mainMenu = menuMap[menuID];
- const $dropdownToggles = $('#titlebar .dropdown-toggle');
+ const $dropdownToggles = $('#titlebar .dropdown:not(.hamburger-menu):visible > .dropdown-toggle');
let currentIndex = $dropdownToggles.index($menuDropdownToggle);
- currentIndex = event.key === KEY.ARROW_LEFT ? currentIndex - 1 : currentIndex + 1;
- let nextIndex = currentIndex;
+ let nextIndex = event.key === KEY.ARROW_LEFT ? currentIndex - 1 : currentIndex + 1;
if (nextIndex < 0) {
nextIndex = 0;
} else if (nextIndex >= $dropdownToggles.length) {
@@ -1762,6 +1761,323 @@ define(function (require, exports, module) {
return cmenu;
}
+ /**
+ * Hamburger menu: when the titlebar is too narrow to fit all menu items on one row,
+ * overflow items are hidden and a hamburger button appears with a dropdown listing them.
+ */
+ function _initHamburgerMenu() {
+ const $menubar = $("#titlebar .nav");
+ const $hamburger = $(`
`);
+ $menubar.append($hamburger);
+ const $hamburgerDropdown = $hamburger.find(".dropdown-menu");
+ const $hamburgerToggle = $hamburger.find(".hamburger-toggle");
+ let _activeSubmenuId = null;
+
+ // Sidebar collapse/expand toggle button before the File menu
+ const $sidebarToggle = $(``);
+ $menubar.prepend($sidebarToggle);
+ const $sidebarIcon = $sidebarToggle.find("a");
+
+ function _updateSidebarToggleIcon() {
+ const isVisible = $("#sidebar").is(":visible");
+ if (isVisible) {
+ $sidebarIcon.html('');
+ $sidebarIcon.attr("title", Strings.CMD_HIDE_SIDEBAR);
+ } else {
+ $sidebarIcon.html('');
+ $sidebarIcon.attr("title", Strings.CMD_SHOW_SIDEBAR);
+ }
+ }
+
+ $sidebarIcon.on("click", function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ CommandManager.execute(Commands.VIEW_HIDE_SIDEBAR);
+ });
+
+ $("#sidebar").on("panelCollapsed panelExpanded", _updateSidebarToggleIcon);
+ _updateSidebarToggleIcon();
+
+ function _resetMenuItemStyles($menuItem) {
+ const menu = menuMap[$menuItem.attr("id")];
+ if (menu) {
+ menu.closeSubMenu();
+ }
+ $menuItem.removeClass("open").css({
+ display: "none",
+ position: "",
+ visibility: "",
+ pointerEvents: "",
+ width: "",
+ height: "",
+ overflow: ""
+ });
+ $menuItem.find("> .dropdown-menu").css({
+ display: "",
+ visibility: "",
+ pointerEvents: "",
+ position: "",
+ top: "",
+ left: "",
+ margin: ""
+ });
+ }
+
+ function _closeHamburgerSubmenus() {
+ $hamburgerDropdown.find(".hamburger-submenu-open").removeClass("hamburger-submenu-open");
+ // Reset the active flyout menu item
+ if (_activeSubmenuId) {
+ _resetMenuItemStyles($(`#${_activeSubmenuId}`));
+ _activeSubmenuId = null;
+ }
+ // Safety: also reset any overflow menu items that might still have
+ // inline styles from a flyout that wasn't properly closed
+ $menubar.children("li.dropdown:not(.hamburger-menu)").each(function () {
+ const $item = $(this);
+ if ($item.css("display") !== "none" && $item.find("> .dropdown-menu").css("position") === "fixed") {
+ _resetMenuItemStyles($item);
+ }
+ });
+ }
+
+ function _closeHamburger() {
+ $hamburger.removeClass("hamburger-open");
+ _closeHamburgerSubmenus();
+ }
+
+ // Wire up hamburger click to toggle the dropdown
+ $hamburgerToggle.on("click", function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ const wasOpen = $hamburger.hasClass("hamburger-open");
+ closeAll();
+ _closeHamburger();
+ if (!wasOpen) {
+ $hamburger.addClass("hamburger-open");
+ }
+ });
+
+ // Close hamburger when clicking outside
+ $(document).on("mousedown", function (e) {
+ if (!$hamburger.hasClass("hamburger-open")) {
+ return;
+ }
+ // Check if click is inside hamburger
+ if ($(e.target).closest("#hamburger-menu").length) {
+ return;
+ }
+ // Check if click is inside the active flyout menu
+ if (_activeSubmenuId && $(e.target).closest(`#${_activeSubmenuId}`).length) {
+ return;
+ }
+ // Check if click is inside any open context menu (sub-submenus
+ // live in #context-menu-bar, not inside the flyout menu)
+ if ($(e.target).closest("#context-menu-bar .open").length) {
+ return;
+ }
+ _closeHamburger();
+ });
+
+ // Close hamburger and open the hovered normal menu
+ $menubar.on("mouseenter", ".dropdown:not(.hamburger-menu) > .dropdown-toggle", function () {
+ if ($hamburger.hasClass("hamburger-open")) {
+ _closeHamburger();
+ // Open the hovered menu and focus its toggle so keyboard nav works
+ const $toggle = $(this);
+ $toggle.parent().addClass("open");
+ $toggle.focus();
+ }
+ });
+
+ // Wire up hamburger toggle mouseenter like other menus.
+ // If the titlebar has focus (meaning a menu is already open),
+ // auto-open the hamburger on hover - matching normal menu behavior.
+ $hamburgerToggle.on("mouseenter", function () {
+ _closeAllSubMenus();
+ const $this = $(this);
+ if ($('#titlebar, #titlebar *').is(':focus')) {
+ // Close any open normal menus first
+ closeAll();
+ $this.addClass('selected').focus();
+ if (!$hamburger.hasClass("hamburger-open")) {
+ $hamburger.addClass("hamburger-open");
+ }
+ } else {
+ $this.addClass('selected');
+ }
+ });
+ $hamburgerToggle.on("mouseleave", function () {
+ $(this).removeClass('selected');
+ });
+
+ // Close hamburger when ESC is pressed
+ $(document).on("keydown", function (e) {
+ if (e.key === "Escape" && $hamburger.hasClass("hamburger-open")) {
+ _closeHamburger();
+ e.stopPropagation();
+ }
+ });
+
+ // Close hamburger when window loses focus
+ $(window).on("blur", _closeHamburger);
+
+ // Close hamburger when a menu item in a flyout is clicked.
+ // Use setTimeout so the command executes before we hide the menu.
+ $menubar.on("click", ".dropdown:not(.hamburger-menu) .menuAnchor", function () {
+ setTimeout(_closeHamburger, 0);
+ });
+
+ // Also close on beforeExecuteCommand (e.g. keyboard shortcuts while open)
+ CommandManager.on("beforeExecuteCommand", function () {
+ _closeHamburger();
+ });
+
+ let _updateScheduled = false;
+
+ function _updateHamburgerMenu() {
+ _updateScheduled = false;
+ // Don't re-layout while a flyout submenu is active - showing the
+ // hidden menu li triggers ResizeObserver which would reset everything
+ if (_activeSubmenuId) {
+ return;
+ }
+ _closeHamburgerSubmenus();
+ const $items = $menubar.children("li.dropdown:not(.hamburger-menu)");
+ // First, show all items and hide hamburger to measure natural layout
+ $items.css({display: "", position: "", visibility: "", pointerEvents: ""});
+ // Ensure hamburger is always the last item in the menu bar
+ $menubar.append($hamburger);
+ $hamburger.hide();
+ $hamburgerDropdown.empty();
+
+ if ($items.length === 0) {
+ return;
+ }
+
+ const firstItemTop = $items.first()[0].offsetTop;
+ let overflowStartIndex = -1;
+
+ for (let i = 0; i < $items.length; i++) {
+ if ($items[i].offsetTop > firstItemTop) {
+ overflowStartIndex = i;
+ break;
+ }
+ }
+
+ if (overflowStartIndex === -1) {
+ // Everything fits on one row
+ return;
+ }
+
+ // Show hamburger, then keep hiding items from the end until
+ // both visible items and the hamburger fit on one row
+ $hamburger.css("display", "");
+
+ while (overflowStartIndex > 0 && $hamburger[0].offsetTop > firstItemTop) {
+ overflowStartIndex--;
+ $($items[overflowStartIndex]).css("display", "none");
+ }
+
+ // Also check if any remaining items wrapped
+ for (let i = 0; i < overflowStartIndex; i++) {
+ if ($items[i].offsetTop > firstItemTop) {
+ overflowStartIndex = i;
+ break;
+ }
+ }
+
+ function _openFlyout($entry, menuId) {
+ if (_activeSubmenuId && _activeSubmenuId !== menuId) {
+ _closeHamburgerSubmenus();
+ }
+ $hamburgerDropdown.find(".hamburger-submenu-open").removeClass("hamburger-submenu-open");
+ $entry.addClass("hamburger-submenu-open");
+ _activeSubmenuId = menuId;
+
+ const $menuItem = $(`#${menuId}`);
+ // Add 'open' class so sub-submenus (ContextMenus) can open properly.
+ // Keep the li itself invisible and out of flow.
+ $menuItem.addClass("open").css({
+ display: "block",
+ position: "absolute",
+ visibility: "hidden",
+ pointerEvents: "none",
+ width: "0",
+ height: "0",
+ overflow: "visible"
+ });
+
+ const $realDropdown = $menuItem.find("> .dropdown-menu");
+ const entryRect = $entry[0].getBoundingClientRect();
+ const hamburgerRect = $hamburgerDropdown[0].getBoundingClientRect();
+ let flyoutLeft = hamburgerRect.right - 2;
+ if (flyoutLeft + 250 > window.innerWidth) {
+ flyoutLeft = hamburgerRect.left - $realDropdown.outerWidth() + 2;
+ }
+ $realDropdown.css({
+ display: "block",
+ visibility: "visible",
+ pointerEvents: "auto",
+ position: "fixed",
+ top: entryRect.top + "px",
+ left: flyoutLeft + "px",
+ margin: "0"
+ });
+ }
+
+ // Hide overflowing items and add them to hamburger dropdown as nested flyouts
+ for (let i = overflowStartIndex; i < $items.length; i++) {
+ const $item = $($items[i]);
+ const menuId = $item.attr("id");
+ const menuName = $item.find(".dropdown-toggle").text();
+ $item.css("display", "none");
+
+ const $entry = $(``);
+
+ $entry.on("mouseenter", function () {
+ _openFlyout($(this), menuId);
+ });
+
+ $hamburgerDropdown.append($entry);
+ }
+ }
+
+ function _scheduleUpdate() {
+ if (!_updateScheduled) {
+ _updateScheduled = true;
+ requestAnimationFrame(_updateHamburgerMenu);
+ }
+ }
+
+ // Observe titlebar resizes
+ const titlebar = document.getElementById("titlebar");
+ if (window.ResizeObserver) {
+ const resizeObserver = new ResizeObserver(_scheduleUpdate);
+ resizeObserver.observe(titlebar);
+ }
+ $(window).on("resize", _scheduleUpdate);
+
+ // Also update when menus are added/removed
+ exports.on(EVENT_MENU_ADDED, _scheduleUpdate);
+
+ // Initial check
+ _scheduleUpdate();
+ }
+
AppInit.htmlReady(function () {
$('#titlebar').on('focusin', function () {
KeyBindingManager.addGlobalKeydownHook(menuKeyboardNavigationHandler);
@@ -1769,6 +2085,19 @@ define(function (require, exports, module) {
$('#titlebar').on('focusout', function () {
KeyBindingManager.removeGlobalKeydownHook(menuKeyboardNavigationHandler);
});
+ _initHamburgerMenu();
+
+ // Close all menus, context menus, and popups when window loses focus
+ $(window).on("blur", function () {
+ closeAll();
+ // Close all context menus (editor, file tree, working set, etc.)
+ _.forEach(contextMenuMap, function (contextMenu) {
+ if (contextMenu.isOpen()) {
+ contextMenu.close();
+ }
+ });
+ PopUpManager.closeAllPopups();
+ });
});
EventDispatcher.makeEventDispatcher(exports);
diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less
index 5e6085c579..b1f19e0a26 100644
--- a/src/styles/brackets_patterns_override.less
+++ b/src/styles/brackets_patterns_override.less
@@ -211,6 +211,89 @@ a:focus {
animation: none;
}
}
+ .nav .sidebar-toggle-btn {
+ float: left;
+ a {
+ display: inline-block;
+ padding: @menubar-top-padding 6px @menubar-bottom-padding;
+ border: 1px solid transparent;
+ font-size: @menu-item-font-size;
+ color: fadeout(@bc-menu-text, 50%);
+ cursor: default;
+ outline: none;
+ .dark & {
+ color: fadeout(@dark-bc-menu-text, 50%);
+ }
+ &:hover {
+ color: @bc-menu-text;
+ .dark & {
+ color: @dark-bc-menu-text;
+ }
+ }
+ }
+ }
+
+ .nav .hamburger-menu {
+ float: left;
+ .hamburger-toggle, .hamburger-toggle:hover, .hamburger-toggle:focus {
+ padding: @menubar-top-padding @menubar-h-padding @menubar-bottom-padding;
+ border: 1px solid transparent;
+ font-size: 14px;
+ color: fadeout(@bc-menu-text, 25%);
+ background: transparent;
+ cursor: default;
+ outline: none;
+ .dark & {
+ color: fadeout(@dark-bc-menu-text, 25%);
+ background: transparent;
+ }
+ }
+ .hamburger-toggle:hover, &.hamburger-open .hamburger-toggle {
+ color: @bc-menu-text;
+ background: @bc-bg-highlight;
+ .dark & {
+ color: @dark-bc-menu-text;
+ background: @dark-bc-bg-highlight;
+ }
+ }
+ position: relative;
+ .hamburger-dropdown {
+ display: none;
+ min-width: 120px;
+ text-align: left;
+ right: 0;
+ left: auto;
+ }
+ &.hamburger-open .hamburger-dropdown {
+ display: block;
+ }
+ .hamburger-submenu-item {
+ a {
+ display: flex;
+ align-items: center;
+ padding: 2px 10px 0 6px;
+ white-space: nowrap;
+ cursor: default;
+ text-align: left;
+ }
+ .hamburger-submenu-arrow {
+ margin-left: auto;
+ padding-left: 12px;
+ font-size: 10px;
+ opacity: 0.7;
+ }
+ &.hamburger-submenu-open > a,
+ a:hover {
+ background: @bc-bg-highlight;
+ color: @bc-menu-text;
+ .dark & {
+ background: @dark-bc-bg-highlight;
+ color: @dark-bc-menu-text;
+ }
+ }
+ }
+ }
+
.title {
float: none;
display: inline; // must be an inline for JS to measure text size
diff --git a/src/widgets/Dialogs.js b/src/widgets/Dialogs.js
index 1eb8271519..82dc1beb8e 100644
--- a/src/widgets/Dialogs.js
+++ b/src/widgets/Dialogs.js
@@ -412,13 +412,14 @@ define(function (require, exports, module) {
autoDismiss = true;
}
- $("body").append("");
+ let $wrapper = $("");
+ $("body").append($wrapper);
let result = new $.Deferred(),
promise = result.promise(),
$dlg = $(template)
.addClass("instance")
- .appendTo(".modal-inner-wrapper:last");
+ .appendTo($wrapper.find(".modal-inner-wrapper"));
// Don't allow dialog to exceed viewport size
setDialogMaxSize();
@@ -463,7 +464,7 @@ define(function (require, exports, module) {
}
//Remove wrapper
- $(".modal-wrapper:last").remove();
+ $wrapper.remove();
}).one("shown", function () {
let $defaultOption = $dlg.find(".default-option"),
$primaryBtn = $dlg.find(".primary:enabled"),
diff --git a/test/spec/Menu-integ-test.js b/test/spec/Menu-integ-test.js
index fdd5140909..6e8454c9da 100644
--- a/test/spec/Menu-integ-test.js
+++ b/test/spec/Menu-integ-test.js
@@ -19,7 +19,7 @@
*
*/
-/*global describe, it, expect, beforeAll, afterAll*/
+/*global describe, it, expect, beforeAll, afterAll, afterEach*/
define(function (require, exports, module) {
@@ -1151,5 +1151,272 @@ define(function (require, exports, module) {
expect(element.getElementsByClassName("forced-hidden").length).toBe(0);
});
});
+
+ describe("Hamburger Menu", function () {
+
+ function getHamburger() {
+ return testWindow.$("#hamburger-menu");
+ }
+
+ function getHamburgerDropdown() {
+ return testWindow.$("#hamburger-menu .hamburger-dropdown");
+ }
+
+ function getHiddenMenuItems() {
+ return testWindow.$("#titlebar .nav > li.dropdown:not(.hamburger-menu)").filter(function () {
+ return testWindow.$(this).css("display") === "none";
+ });
+ }
+
+ function forceNarrowTitlebar() {
+ // Shrink the .content container so the titlebar becomes narrow
+ const $content = testWindow.$(".content");
+ $content.css({"right": "", "width": "200px"});
+ $content[0].offsetHeight;
+ }
+
+ function restoreTitlebar() {
+ const $content = testWindow.$(".content");
+ $content.css({"left": "", "right": "", "width": ""});
+ $content[0].offsetHeight;
+ }
+
+ function awaitsForCondition(conditionFn, message, timeout) {
+ timeout = timeout || 5000;
+ return new Promise(function (resolve, reject) {
+ const startTime = Date.now();
+ function check() {
+ if (conditionFn()) {
+ resolve();
+ } else if (Date.now() - startTime > timeout) {
+ reject(new Error("Timed out waiting for: " + (message || "condition")));
+ } else {
+ testWindow.requestAnimationFrame(check);
+ }
+ }
+ check();
+ });
+ }
+
+ afterEach(function () {
+ // Clean up: close hamburger properly (clears _activeSubmenuId)
+ const $hamburger = getHamburger();
+ if ($hamburger.hasClass("hamburger-open")) {
+ $hamburger.find(".hamburger-toggle").click();
+ }
+ Menus.closeAll();
+ restoreTitlebar();
+ });
+
+ it("should exist in the DOM", function () {
+ const $hamburger = getHamburger();
+ expect($hamburger.length).toBe(1);
+ expect($hamburger.find(".hamburger-toggle").length).toBe(1);
+ expect($hamburger.find(".hamburger-dropdown").length).toBe(1);
+ });
+
+ it("should be hidden when all menus fit on one row", async function () {
+ // The test window may have extra test menus from prior tests.
+ // Verify with a very wide container that hamburger hides.
+ const $content = testWindow.$(".content");
+ const $main = $content.parent();
+ $main.css("width", "4000px");
+ $content.css({"left": "0", "right": "0", "position": "relative", "width": "4000px"});
+ $content[0].offsetHeight;
+ await awaitsForCondition(function () {
+ return getHamburger().is(":hidden");
+ }, "hamburger to be hidden");
+ $main.css("width", "");
+ $content.css({"left": "", "right": "", "position": "", "width": ""});
+ });
+
+ it("should appear when titlebar is too narrow", async function () {
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+ const $hidden = getHiddenMenuItems();
+ expect($hidden.length).toBeGreaterThan(0);
+ const $entries = getHamburgerDropdown().find(".hamburger-submenu-item");
+ expect($entries.length).toBe($hidden.length);
+ });
+
+ it("should open dropdown on click", async function () {
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+ const $hamburger = getHamburger();
+ expect($hamburger.hasClass("hamburger-open")).toBe(false);
+
+ $hamburger.find(".hamburger-toggle").click();
+ expect($hamburger.hasClass("hamburger-open")).toBe(true);
+
+ const $dropdown = getHamburgerDropdown();
+ expect($dropdown.css("display")).toBe("block");
+ });
+
+ it("should close dropdown on second click", async function () {
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+ const $hamburger = getHamburger();
+ const $toggle = $hamburger.find(".hamburger-toggle");
+
+ $toggle.click();
+ expect($hamburger.hasClass("hamburger-open")).toBe(true);
+
+ $toggle.click();
+ expect($hamburger.hasClass("hamburger-open")).toBe(false);
+ });
+
+ it("should open flyout submenu on entry hover", async function () {
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+ const $hamburger = getHamburger();
+ $hamburger.find(".hamburger-toggle").click();
+
+ const $entries = getHamburgerDropdown().find(".hamburger-submenu-item");
+ expect($entries.length).toBeGreaterThan(0);
+
+ const $firstEntry = $entries.first();
+ $firstEntry.trigger("mouseenter");
+
+ expect($firstEntry.hasClass("hamburger-submenu-open")).toBe(true);
+
+ const menuId = $firstEntry.find("a").attr("data-menu-id");
+ const $menuDropdown = testWindow.$("#" + menuId + " > .dropdown-menu");
+ expect($menuDropdown.css("visibility")).toBe("visible");
+ expect($menuDropdown.css("position")).toBe("fixed");
+ });
+
+ it("should restore all menus when titlebar expands", async function () {
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+ expect(getHiddenMenuItems().length).toBeGreaterThan(0);
+
+ // Expand wide enough for all menus
+ restoreTitlebar();
+ const $content = testWindow.$(".content");
+ const $main = $content.parent();
+ $main.css("width", "4000px");
+ $content.css({"left": "0", "right": "0", "position": "relative", "width": "4000px"});
+ $content[0].offsetHeight;
+ await awaitsForCondition(function () {
+ return getHamburger().is(":hidden");
+ }, "hamburger to hide after expanding");
+ expect(getHiddenMenuItems().length).toBe(0);
+ $main.css("width", "");
+ $content.css({"left": "", "right": "", "position": "", "width": ""});
+ });
+
+ it("should execute command when clicking a flyout menu item", async function () {
+ // Register a test command and add it to a menu
+ let commandExecuted = false;
+ CommandManager.register("Hamburger Test Command", "Menu-test.hamburgerCmd1", function () {
+ commandExecuted = true;
+ });
+ const fileMenu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU);
+ fileMenu.addMenuItem("Menu-test.hamburgerCmd1");
+
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+
+ // Open hamburger and hover File menu entry
+ const $hamburger = getHamburger();
+ $hamburger.find(".hamburger-toggle").click();
+ const $entries = getHamburgerDropdown().find(".hamburger-submenu-item");
+ // Find the File menu entry
+ const $fileEntry = $entries.filter(function () {
+ return testWindow.$(this).find("a").attr("data-menu-id") === "file-menu";
+ });
+ expect($fileEntry.length).toBe(1);
+ $fileEntry.trigger("mouseenter");
+
+ // Click the test command in the flyout
+ const $menuItem = testWindow.$("#file-menu-Menu-test\\.hamburgerCmd1");
+ expect($menuItem.length).toBe(1);
+ $menuItem.click();
+
+ await awaitsForCondition(function () {
+ return commandExecuted;
+ }, "command to be executed");
+ await awaitsForCondition(function () {
+ return !$hamburger.hasClass("hamburger-open");
+ }, "hamburger to close after command");
+ });
+
+ it("should execute command when clicking a flyout submenu item", async function () {
+ // Register a command, create a submenu in the File menu
+ let subCommandExecuted = false;
+ CommandManager.register("Hamburger SubMenu Test", "Menu-test.hamburgerSubCmd1", function () {
+ subCommandExecuted = true;
+ });
+ const fileMenu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU);
+ const subMenu = fileMenu.addSubMenu("Hamburger Sub", "hamburger-test-submenu1");
+ subMenu.addMenuItem("Menu-test.hamburgerSubCmd1");
+
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+
+ // Open hamburger and hover File menu entry
+ const $hamburger = getHamburger();
+ $hamburger.find(".hamburger-toggle").click();
+ const $entries = getHamburgerDropdown().find(".hamburger-submenu-item");
+ const $fileEntry = $entries.filter(function () {
+ return testWindow.$(this).find("a").attr("data-menu-id") === "file-menu";
+ });
+ $fileEntry.trigger("mouseenter");
+
+ // Hover the submenu trigger to open it
+ const $subMenuTrigger = testWindow.$("#file-menu-hamburger-test-submenu1").closest("li");
+ expect($subMenuTrigger.length).toBe(1);
+ $subMenuTrigger.trigger("mouseenter");
+
+ // Wait for submenu to open
+ await awaitsForCondition(function () {
+ return testWindow.$("#hamburger-test-submenu1").hasClass("open");
+ }, "submenu to open");
+
+ // Click the command in the submenu
+ const $subItem = testWindow.$("#hamburger-test-submenu1-Menu-test\\.hamburgerSubCmd1");
+ expect($subItem.length).toBe(1);
+ $subItem.closest("li").click();
+
+ await awaitsForCondition(function () {
+ return subCommandExecuted;
+ }, "submenu command to be executed");
+ });
+
+ it("should close on ESC key", async function () {
+ forceNarrowTitlebar();
+ await awaitsForCondition(function () {
+ return !getHamburger().is(":hidden");
+ }, "hamburger to appear");
+ const $hamburger = getHamburger();
+ $hamburger.find(".hamburger-toggle").click();
+ expect($hamburger.hasClass("hamburger-open")).toBe(true);
+
+ // Dispatch ESC keydown event
+ const escEvent = new testWindow.KeyboardEvent("keydown", {
+ key: "Escape",
+ keyCode: KeyEvent.DOM_VK_ESCAPE,
+ bubbles: true,
+ cancelable: true
+ });
+ testWindow.document.dispatchEvent(escEvent);
+
+ expect($hamburger.hasClass("hamburger-open")).toBe(false);
+ });
+ });
});
});