|
19 | 19 | * |
20 | 20 | */ |
21 | 21 |
|
22 | | -/*global describe, it, expect, beforeAll, afterAll*/ |
| 22 | +/*global describe, it, expect, beforeAll, afterAll, afterEach*/ |
23 | 23 |
|
24 | 24 | define(function (require, exports, module) { |
25 | 25 |
|
@@ -1151,5 +1151,272 @@ define(function (require, exports, module) { |
1151 | 1151 | expect(element.getElementsByClassName("forced-hidden").length).toBe(0); |
1152 | 1152 | }); |
1153 | 1153 | }); |
| 1154 | + |
| 1155 | + describe("Hamburger Menu", function () { |
| 1156 | + |
| 1157 | + function getHamburger() { |
| 1158 | + return testWindow.$("#hamburger-menu"); |
| 1159 | + } |
| 1160 | + |
| 1161 | + function getHamburgerDropdown() { |
| 1162 | + return testWindow.$("#hamburger-menu .hamburger-dropdown"); |
| 1163 | + } |
| 1164 | + |
| 1165 | + function getHiddenMenuItems() { |
| 1166 | + return testWindow.$("#titlebar .nav > li.dropdown:not(.hamburger-menu)").filter(function () { |
| 1167 | + return testWindow.$(this).css("display") === "none"; |
| 1168 | + }); |
| 1169 | + } |
| 1170 | + |
| 1171 | + function forceNarrowTitlebar() { |
| 1172 | + // Shrink the .content container so the titlebar becomes narrow |
| 1173 | + const $content = testWindow.$(".content"); |
| 1174 | + $content.css({"right": "", "width": "200px"}); |
| 1175 | + $content[0].offsetHeight; |
| 1176 | + } |
| 1177 | + |
| 1178 | + function restoreTitlebar() { |
| 1179 | + const $content = testWindow.$(".content"); |
| 1180 | + $content.css({"left": "", "right": "", "width": ""}); |
| 1181 | + $content[0].offsetHeight; |
| 1182 | + } |
| 1183 | + |
| 1184 | + function awaitsForCondition(conditionFn, message, timeout) { |
| 1185 | + timeout = timeout || 5000; |
| 1186 | + return new Promise(function (resolve, reject) { |
| 1187 | + const startTime = Date.now(); |
| 1188 | + function check() { |
| 1189 | + if (conditionFn()) { |
| 1190 | + resolve(); |
| 1191 | + } else if (Date.now() - startTime > timeout) { |
| 1192 | + reject(new Error("Timed out waiting for: " + (message || "condition"))); |
| 1193 | + } else { |
| 1194 | + testWindow.requestAnimationFrame(check); |
| 1195 | + } |
| 1196 | + } |
| 1197 | + check(); |
| 1198 | + }); |
| 1199 | + } |
| 1200 | + |
| 1201 | + afterEach(function () { |
| 1202 | + // Clean up: close hamburger properly (clears _activeSubmenuId) |
| 1203 | + const $hamburger = getHamburger(); |
| 1204 | + if ($hamburger.hasClass("hamburger-open")) { |
| 1205 | + $hamburger.find(".hamburger-toggle").click(); |
| 1206 | + } |
| 1207 | + Menus.closeAll(); |
| 1208 | + restoreTitlebar(); |
| 1209 | + }); |
| 1210 | + |
| 1211 | + it("should exist in the DOM", function () { |
| 1212 | + const $hamburger = getHamburger(); |
| 1213 | + expect($hamburger.length).toBe(1); |
| 1214 | + expect($hamburger.find(".hamburger-toggle").length).toBe(1); |
| 1215 | + expect($hamburger.find(".hamburger-dropdown").length).toBe(1); |
| 1216 | + }); |
| 1217 | + |
| 1218 | + it("should be hidden when all menus fit on one row", async function () { |
| 1219 | + // The test window may have extra test menus from prior tests. |
| 1220 | + // Verify with a very wide container that hamburger hides. |
| 1221 | + const $content = testWindow.$(".content"); |
| 1222 | + const $main = $content.parent(); |
| 1223 | + $main.css("width", "4000px"); |
| 1224 | + $content.css({"left": "0", "right": "0", "position": "relative", "width": "4000px"}); |
| 1225 | + $content[0].offsetHeight; |
| 1226 | + await awaitsForCondition(function () { |
| 1227 | + return getHamburger().is(":hidden"); |
| 1228 | + }, "hamburger to be hidden"); |
| 1229 | + $main.css("width", ""); |
| 1230 | + $content.css({"left": "", "right": "", "position": "", "width": ""}); |
| 1231 | + }); |
| 1232 | + |
| 1233 | + it("should appear when titlebar is too narrow", async function () { |
| 1234 | + forceNarrowTitlebar(); |
| 1235 | + await awaitsForCondition(function () { |
| 1236 | + return !getHamburger().is(":hidden"); |
| 1237 | + }, "hamburger to appear"); |
| 1238 | + const $hidden = getHiddenMenuItems(); |
| 1239 | + expect($hidden.length).toBeGreaterThan(0); |
| 1240 | + const $entries = getHamburgerDropdown().find(".hamburger-submenu-item"); |
| 1241 | + expect($entries.length).toBe($hidden.length); |
| 1242 | + }); |
| 1243 | + |
| 1244 | + it("should open dropdown on click", async function () { |
| 1245 | + forceNarrowTitlebar(); |
| 1246 | + await awaitsForCondition(function () { |
| 1247 | + return !getHamburger().is(":hidden"); |
| 1248 | + }, "hamburger to appear"); |
| 1249 | + const $hamburger = getHamburger(); |
| 1250 | + expect($hamburger.hasClass("hamburger-open")).toBe(false); |
| 1251 | + |
| 1252 | + $hamburger.find(".hamburger-toggle").click(); |
| 1253 | + expect($hamburger.hasClass("hamburger-open")).toBe(true); |
| 1254 | + |
| 1255 | + const $dropdown = getHamburgerDropdown(); |
| 1256 | + expect($dropdown.css("display")).toBe("block"); |
| 1257 | + }); |
| 1258 | + |
| 1259 | + it("should close dropdown on second click", async function () { |
| 1260 | + forceNarrowTitlebar(); |
| 1261 | + await awaitsForCondition(function () { |
| 1262 | + return !getHamburger().is(":hidden"); |
| 1263 | + }, "hamburger to appear"); |
| 1264 | + const $hamburger = getHamburger(); |
| 1265 | + const $toggle = $hamburger.find(".hamburger-toggle"); |
| 1266 | + |
| 1267 | + $toggle.click(); |
| 1268 | + expect($hamburger.hasClass("hamburger-open")).toBe(true); |
| 1269 | + |
| 1270 | + $toggle.click(); |
| 1271 | + expect($hamburger.hasClass("hamburger-open")).toBe(false); |
| 1272 | + }); |
| 1273 | + |
| 1274 | + it("should open flyout submenu on entry hover", async function () { |
| 1275 | + forceNarrowTitlebar(); |
| 1276 | + await awaitsForCondition(function () { |
| 1277 | + return !getHamburger().is(":hidden"); |
| 1278 | + }, "hamburger to appear"); |
| 1279 | + const $hamburger = getHamburger(); |
| 1280 | + $hamburger.find(".hamburger-toggle").click(); |
| 1281 | + |
| 1282 | + const $entries = getHamburgerDropdown().find(".hamburger-submenu-item"); |
| 1283 | + expect($entries.length).toBeGreaterThan(0); |
| 1284 | + |
| 1285 | + const $firstEntry = $entries.first(); |
| 1286 | + $firstEntry.trigger("mouseenter"); |
| 1287 | + |
| 1288 | + expect($firstEntry.hasClass("hamburger-submenu-open")).toBe(true); |
| 1289 | + |
| 1290 | + const menuId = $firstEntry.find("a").attr("data-menu-id"); |
| 1291 | + const $menuDropdown = testWindow.$("#" + menuId + " > .dropdown-menu"); |
| 1292 | + expect($menuDropdown.css("visibility")).toBe("visible"); |
| 1293 | + expect($menuDropdown.css("position")).toBe("fixed"); |
| 1294 | + }); |
| 1295 | + |
| 1296 | + it("should restore all menus when titlebar expands", async function () { |
| 1297 | + forceNarrowTitlebar(); |
| 1298 | + await awaitsForCondition(function () { |
| 1299 | + return !getHamburger().is(":hidden"); |
| 1300 | + }, "hamburger to appear"); |
| 1301 | + expect(getHiddenMenuItems().length).toBeGreaterThan(0); |
| 1302 | + |
| 1303 | + // Expand wide enough for all menus |
| 1304 | + restoreTitlebar(); |
| 1305 | + const $content = testWindow.$(".content"); |
| 1306 | + const $main = $content.parent(); |
| 1307 | + $main.css("width", "4000px"); |
| 1308 | + $content.css({"left": "0", "right": "0", "position": "relative", "width": "4000px"}); |
| 1309 | + $content[0].offsetHeight; |
| 1310 | + await awaitsForCondition(function () { |
| 1311 | + return getHamburger().is(":hidden"); |
| 1312 | + }, "hamburger to hide after expanding"); |
| 1313 | + expect(getHiddenMenuItems().length).toBe(0); |
| 1314 | + $main.css("width", ""); |
| 1315 | + $content.css({"left": "", "right": "", "position": "", "width": ""}); |
| 1316 | + }); |
| 1317 | + |
| 1318 | + it("should execute command when clicking a flyout menu item", async function () { |
| 1319 | + // Register a test command and add it to a menu |
| 1320 | + let commandExecuted = false; |
| 1321 | + CommandManager.register("Hamburger Test Command", "Menu-test.hamburgerCmd1", function () { |
| 1322 | + commandExecuted = true; |
| 1323 | + }); |
| 1324 | + const fileMenu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU); |
| 1325 | + fileMenu.addMenuItem("Menu-test.hamburgerCmd1"); |
| 1326 | + |
| 1327 | + forceNarrowTitlebar(); |
| 1328 | + await awaitsForCondition(function () { |
| 1329 | + return !getHamburger().is(":hidden"); |
| 1330 | + }, "hamburger to appear"); |
| 1331 | + |
| 1332 | + // Open hamburger and hover File menu entry |
| 1333 | + const $hamburger = getHamburger(); |
| 1334 | + $hamburger.find(".hamburger-toggle").click(); |
| 1335 | + const $entries = getHamburgerDropdown().find(".hamburger-submenu-item"); |
| 1336 | + // Find the File menu entry |
| 1337 | + const $fileEntry = $entries.filter(function () { |
| 1338 | + return testWindow.$(this).find("a").attr("data-menu-id") === "file-menu"; |
| 1339 | + }); |
| 1340 | + expect($fileEntry.length).toBe(1); |
| 1341 | + $fileEntry.trigger("mouseenter"); |
| 1342 | + |
| 1343 | + // Click the test command in the flyout |
| 1344 | + const $menuItem = testWindow.$("#file-menu-Menu-test\\.hamburgerCmd1"); |
| 1345 | + expect($menuItem.length).toBe(1); |
| 1346 | + $menuItem.click(); |
| 1347 | + |
| 1348 | + await awaitsForCondition(function () { |
| 1349 | + return commandExecuted; |
| 1350 | + }, "command to be executed"); |
| 1351 | + await awaitsForCondition(function () { |
| 1352 | + return !$hamburger.hasClass("hamburger-open"); |
| 1353 | + }, "hamburger to close after command"); |
| 1354 | + }); |
| 1355 | + |
| 1356 | + it("should execute command when clicking a flyout submenu item", async function () { |
| 1357 | + // Register a command, create a submenu in the File menu |
| 1358 | + let subCommandExecuted = false; |
| 1359 | + CommandManager.register("Hamburger SubMenu Test", "Menu-test.hamburgerSubCmd1", function () { |
| 1360 | + subCommandExecuted = true; |
| 1361 | + }); |
| 1362 | + const fileMenu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU); |
| 1363 | + const subMenu = fileMenu.addSubMenu("Hamburger Sub", "hamburger-test-submenu1"); |
| 1364 | + subMenu.addMenuItem("Menu-test.hamburgerSubCmd1"); |
| 1365 | + |
| 1366 | + forceNarrowTitlebar(); |
| 1367 | + await awaitsForCondition(function () { |
| 1368 | + return !getHamburger().is(":hidden"); |
| 1369 | + }, "hamburger to appear"); |
| 1370 | + |
| 1371 | + // Open hamburger and hover File menu entry |
| 1372 | + const $hamburger = getHamburger(); |
| 1373 | + $hamburger.find(".hamburger-toggle").click(); |
| 1374 | + const $entries = getHamburgerDropdown().find(".hamburger-submenu-item"); |
| 1375 | + const $fileEntry = $entries.filter(function () { |
| 1376 | + return testWindow.$(this).find("a").attr("data-menu-id") === "file-menu"; |
| 1377 | + }); |
| 1378 | + $fileEntry.trigger("mouseenter"); |
| 1379 | + |
| 1380 | + // Hover the submenu trigger to open it |
| 1381 | + const $subMenuTrigger = testWindow.$("#file-menu-hamburger-test-submenu1").closest("li"); |
| 1382 | + expect($subMenuTrigger.length).toBe(1); |
| 1383 | + $subMenuTrigger.trigger("mouseenter"); |
| 1384 | + |
| 1385 | + // Wait for submenu to open |
| 1386 | + await awaitsForCondition(function () { |
| 1387 | + return testWindow.$("#hamburger-test-submenu1").hasClass("open"); |
| 1388 | + }, "submenu to open"); |
| 1389 | + |
| 1390 | + // Click the command in the submenu |
| 1391 | + const $subItem = testWindow.$("#hamburger-test-submenu1-Menu-test\\.hamburgerSubCmd1"); |
| 1392 | + expect($subItem.length).toBe(1); |
| 1393 | + $subItem.closest("li").click(); |
| 1394 | + |
| 1395 | + await awaitsForCondition(function () { |
| 1396 | + return subCommandExecuted; |
| 1397 | + }, "submenu command to be executed"); |
| 1398 | + }); |
| 1399 | + |
| 1400 | + it("should close on ESC key", async function () { |
| 1401 | + forceNarrowTitlebar(); |
| 1402 | + await awaitsForCondition(function () { |
| 1403 | + return !getHamburger().is(":hidden"); |
| 1404 | + }, "hamburger to appear"); |
| 1405 | + const $hamburger = getHamburger(); |
| 1406 | + $hamburger.find(".hamburger-toggle").click(); |
| 1407 | + expect($hamburger.hasClass("hamburger-open")).toBe(true); |
| 1408 | + |
| 1409 | + // Dispatch ESC keydown event |
| 1410 | + const escEvent = new testWindow.KeyboardEvent("keydown", { |
| 1411 | + key: "Escape", |
| 1412 | + keyCode: KeyEvent.DOM_VK_ESCAPE, |
| 1413 | + bubbles: true, |
| 1414 | + cancelable: true |
| 1415 | + }); |
| 1416 | + testWindow.document.dispatchEvent(escEvent); |
| 1417 | + |
| 1418 | + expect($hamburger.hasClass("hamburger-open")).toBe(false); |
| 1419 | + }); |
| 1420 | + }); |
1154 | 1421 | }); |
1155 | 1422 | }); |
0 commit comments