// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. // // /// module CorsicaTests { "use strict"; var _Constants = Helper.require("WinJS/Controls/_LegacyAppBar/_Constants"), _LightDismissService = Helper.require("WinJS/_LightDismissService"), Key = WinJS.Utilities.Key, MenuCommand = WinJS.UI.MenuCommand, Menu = WinJS.UI.Menu, Flyout = WinJS.UI.Flyout; interface IMarginBox { top: number; bottom: number; left: number; right: number; }; function verifyAllCommandsDeactivated(commands: Array, msg: string = "") { commands.forEach((command) => { OverlayHelpers.Assert.verifyMenuFlyoutCommandDeactivated(command, msg); }); } export class MenuTests { tearDown() { LiveUnit.LoggingCore.logComment("In tearDown"); } // Test Menu Instantiation testMenuInstantiation = function () { // Get the Menu element from the DOM LiveUnit.LoggingCore.logComment("Attempt to Instantiate the Menu element"); var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); LiveUnit.LoggingCore.logComment("Menu has been instantiated."); LiveUnit.Assert.isNotNull(menu, "Menu element should not be null when instantiated."); function verifyFunction(functionName) { LiveUnit.LoggingCore.logComment("Verifying that function " + functionName + " exists"); if (menu[functionName] === undefined) { LiveUnit.Assert.fail(functionName + " missing from Menu"); } LiveUnit.Assert.isNotNull(menu[functionName]); LiveUnit.Assert.isTrue(typeof (menu[functionName]) === "function", functionName + " exists on Menu, but it isn't a function"); } verifyFunction("show"); verifyFunction("hide"); verifyFunction("addEventListener"); verifyFunction("removeEventListener"); OverlayHelpers.disposeAndRemove(menuElement); } // Test Menu Instantiation with null element testMenuNullInstantiation = function () { LiveUnit.LoggingCore.logComment("Attempt to Instantiate the Menu with null element"); var menu = new Menu(null, { commands: { type: 'separator', id: 'sep' } }); LiveUnit.Assert.isNotNull(menu, "Menu instantiation was null when sent a null Menu element."); } // Test Menu Instantiation with no options testMenuEmptyInstantiation = function () { LiveUnit.LoggingCore.logComment("Attempt to Instantiate the Menu with empty constructor"); var menu = new Menu(); LiveUnit.Assert.isNotNull(menu, "Menu instantiation was null when sent a Empty Menu element."); } // Test multiple instantiation of the same Menu DOM element testMenuMultipleInstantiation() { MenuTests.prototype.testMenuMultipleInstantiation["LiveUnit.ExpectedException"] = { message: "Invalid argument: Controls may only be instantiated one time for each DOM element" }; // Get the Menu element from the DOM LiveUnit.LoggingCore.logComment("Attempt to Instantiate the Menu element"); var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); LiveUnit.LoggingCore.logComment("Menu has been instantiated."); LiveUnit.Assert.isNotNull(menu, "Menu element should not be null when instantiated."); try { new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); LiveUnit.Assert.fail("Expected WinJS.UI.Menu.DuplicateConstruction exception"); } finally { OverlayHelpers.disposeAndRemove(menuElement); } } // Test Menu parameters testMenuParams = function () { function testGoodInitOption(paramName, value) { LiveUnit.LoggingCore.logComment("Testing creating a Menu using good parameter " + paramName + "=" + value); var div = document.createElement("div"); var options = { commands: { type: 'separator', id: 'sep' } }; options[paramName] = value; document.body.appendChild(div); var menu = new Menu(div, options); LiveUnit.Assert.isNotNull(menu); OverlayHelpers.disposeAndRemove(div); } function testBadInitOption(paramName, value, expectedName, expectedMessage) { LiveUnit.LoggingCore.logComment("Testing creating a Menu using bad parameter " + paramName + "=" + value); var div = document.createElement("div"); document.body.appendChild(div); var options = { commands: { type: 'separator', id: 'sep' } }; options[paramName] = value; try { new Menu(div, options); LiveUnit.Assert.fail("Expected creating Menu with " + paramName + "=" + value + " to throw an exception"); } catch (e) { var exception = e; LiveUnit.LoggingCore.logComment(exception.message); LiveUnit.Assert.isTrue(exception !== null); LiveUnit.Assert.isTrue(exception.name === expectedName); LiveUnit.Assert.isTrue(exception.message === expectedMessage); } OverlayHelpers.disposeAndRemove(div); } LiveUnit.LoggingCore.logComment("Testing anchor"); testGoodInitOption("anchor", "ralph"); testGoodInitOption("anchor", "fred"); testGoodInitOption("anchor", -1); testGoodInitOption("anchor", 12); testGoodInitOption("anchor", {}); LiveUnit.LoggingCore.logComment("Testing alignment"); testGoodInitOption("alignment", "left"); testGoodInitOption("alignment", "right"); testGoodInitOption("alignment", "center"); var badAlignment = "Invalid argument: Flyout alignment should be 'center' (default), 'left', or 'right'."; testBadInitOption("alignment", "fred", "WinJS.UI.Flyout.BadAlignment", badAlignment); testBadInitOption("alignment", -1, "WinJS.UI.Flyout.BadAlignment", badAlignment); testBadInitOption("alignment", 12, "WinJS.UI.Flyout.BadAlignment", badAlignment); testBadInitOption("alignment", {}, "WinJS.UI.Flyout.BadAlignment", badAlignment); LiveUnit.LoggingCore.logComment("Testing placement"); testGoodInitOption("placement", "top"); testGoodInitOption("placement", "bottom"); testGoodInitOption("placement", "left"); testGoodInitOption("placement", "right"); testGoodInitOption("placement", "auto"); testGoodInitOption("placement", "autohorizontal"); testGoodInitOption("placement", "autovertical"); var badPlacement = "Invalid argument: Flyout placement should be 'top' (default), 'bottom', 'left', 'right', 'auto', 'autohorizontal', or 'autovertical'."; testBadInitOption("placement", "fred", "WinJS.UI.Flyout.BadPlacement", badPlacement); testBadInitOption("placement", -1, "WinJS.UI.Flyout.BadPlacement", badPlacement); testBadInitOption("placement", 12, "WinJS.UI.Flyout.BadPlacement", badPlacement); testBadInitOption("placement", {}, "WinJS.UI.Flyout.BadPlacement", badPlacement); } testDefaultMenuParameters = function () { // Get the Menu element from the DOM var menuElement = document.createElement("div"); document.body.appendChild(menuElement); LiveUnit.LoggingCore.logComment("Attempt to Instantiate the Menu element"); var menu = new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); LiveUnit.LoggingCore.logComment("Menu has been instantiated."); LiveUnit.Assert.isNotNull(menu, "Menu element should not be null when instantiated."); LiveUnit.Assert.areEqual(menuElement, menu.element, "Verifying that element is what we set it with"); LiveUnit.Assert.isTrue(menu.hidden, "Verifying that hidden is true"); OverlayHelpers.disposeAndRemove(menuElement); } // Simple Function Tests testSimpleMenuTestsFunctions = function () { // Get the MenuTests element from the DOM var menuElement = document.createElement("div"); document.body.appendChild(menuElement); LiveUnit.LoggingCore.logComment("Attempt to Instantiate the Menu element"); var menu = new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); LiveUnit.LoggingCore.logComment("Menu has been instantiated."); LiveUnit.Assert.isNotNull(menu, "Menu element should not be null when instantiated."); LiveUnit.LoggingCore.logComment("show"); menu.show(menuElement); LiveUnit.LoggingCore.logComment("hide"); menu.hide(); OverlayHelpers.disposeAndRemove(menuElement); } testHiddenProperty = function (complete) { var menuElement = document.createElement("div"); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); menu.anchor = document.body; menu.addEventListener("aftershow", function () { LiveUnit.Assert.isFalse(menu.hidden); menu.hidden = true; LiveUnit.Assert.isTrue(menu.hidden); menu.addEventListener("afterhide", function () { LiveUnit.Assert.isTrue(menu.hidden); OverlayHelpers.disposeAndRemove(menuElement); complete(); }); }); LiveUnit.Assert.isTrue(menu.hidden); menu.hidden = false; LiveUnit.Assert.isFalse(menu.hidden); } testMenuDispose = function () { var mc1 = new MenuCommand(document.createElement("button"), { label: "mc1" }); var mc2 = new MenuCommand(document.createElement("button"), { label: "mc2" }); var menu = new Menu(null, { commands: [mc1, mc2] }); LiveUnit.Assert.isTrue(menu.dispose); LiveUnit.Assert.isFalse(menu._disposed); menu.dispose(); LiveUnit.Assert.isTrue(menu._disposed); LiveUnit.Assert.isTrue(mc1._disposed); LiveUnit.Assert.isTrue(mc2._disposed); menu.dispose(); } testMenuShowThrows = function (complete) { // Get the menu element from the DOM var menuElement = document.createElement("div"); document.body.appendChild(menuElement); LiveUnit.LoggingCore.logComment("Attempt to Instantiate the menu element"); var menu: any = new Menu(menuElement); LiveUnit.LoggingCore.logComment("menu has been instantiated."); LiveUnit.Assert.isNotNull(menu, "menu element should not be null when instantiated."); LiveUnit.LoggingCore.logComment("Calling show() with no parameters should throw"); try { menu.show(); } catch (e) { LiveUnit.Assert.areEqual("Invalid argument: Flyout anchor element not found in DOM.", e.message); } LiveUnit.LoggingCore.logComment("Calling show() with null should throw"); try { menu.show(null); } catch (e) { LiveUnit.Assert.areEqual("Invalid argument: Flyout anchor element not found in DOM.", e.message); } OverlayHelpers.disposeAndRemove(menuElement); complete(); } testBackClickEventTriggersLightDismiss = function (complete) { // Verifies that a shown Menu will light dismiss due to backclick. // Simulate function simulateBackClick() { var handled = _LightDismissService._onBackClick(); LiveUnit.Assert.isTrue(handled, "Menu should have handled the 'backclick' event"); LiveUnit.Assert.isTrue(menu.hidden, "Menu should be hidden after light dismiss"); cleanup(); }; // Cleanup function cleanup() { OverlayHelpers.disposeAndRemove(menuElement); complete(); } // Setup var menuElement = document.createElement("div"); document.body.appendChild(menuElement); var menu = new Menu(menuElement); menu.addEventListener("aftershow", simulateBackClick, false); menu.show(document.body); }; testEscapeKeyClosesMenu = function (complete) { // Verifies that ESC key hides a Menu function afterHide() { menu.removeEventListener, ("afterhide", afterHide, false); OverlayHelpers.disposeAndRemove(menuElement); complete(); } // Get the menu element from the DOM var menuElement = document.createElement("div"); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { anchor: document.body }); menu.addEventListener("afterhide", afterHide, false); OverlayHelpers.show(menu).then(() => { var msg = "ESC key should hide the menu."; LiveUnit.LoggingCore.logComment("Test: " + msg); Helper.keydown(menu.element, Key.escape); }); }; testShowMovesFocusSyncAndHideMovesFocusAsync = function (complete) { // Verifies Menu.show moves focus at the beginning of the animation // and Menu.hide moves focus at the end of the animation. var button = document.createElement("button"); document.body.appendChild(button); var menuElement = document.createElement("div"); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { anchor: document.body }); var msg = "", test1Ran = false; button.focus(); LiveUnit.Assert.areEqual(document.activeElement, button, "TEST ERROR: button should have focus"); function beforeShow() { menu.removeEventListener("beforeshow", beforeShow, false); WinJS.Promise.timeout(0).then(() => { LiveUnit.Assert.areEqual(document.activeElement, menuElement, msg); test1Ran = true; }); }; menu.addEventListener("beforeshow", beforeShow, false); function afterHide() { menu.removeEventListener("afterhide", afterHide, false); LiveUnit.Assert.areEqual(document.activeElement, button, msg); complete(); } menu.addEventListener("afterhide", afterHide, false); msg = "Menu.show should take focus synchronously after the 'beforeshow' event"; LiveUnit.LoggingCore.logComment("Test: " + msg); OverlayHelpers.show(menu).then(() => { LiveUnit.Assert.isTrue(test1Ran, "TEST ERROR: Test 1 did not run."); msg = "Menu.hide should move focus before the 'afterhide' event"; LiveUnit.LoggingCore.logComment("Test: " + msg); return OverlayHelpers.hide(menu); }); } testMenuLaysOutCommandsCorrectly = function (complete) { // Verifies that layout is adjusted for all visible commands in a menu depending on what other types of commands are also visible in the menu. // Command layouts should be updated during the following function calls: // menu.show() // menu.showCommands() // menu.hideCommands() // menu.showOnlyCommands() function verifyCommandLayouts() { // Helper function verifies that the visible commands found in the Menu DOM have the proper layouts var hasToggleCommand = false, hasFlyoutCommand = false; var commandsInMenu = Array.prototype.map.call(menu.element.querySelectorAll(".win-command"), function getCommands(element) { var command = element.winControl; if (!command.hidden) { if (command.type === _Constants.typeToggle) { hasToggleCommand = true; } else if (command.type === _Constants.typeFlyout) { hasFlyoutCommand = true; } } return command; }); commandsInMenu.forEach(function verifyLayouts(menuCommand) { if (menuCommand.type !== _Constants.typeSeparator) { var toggleSpanStyle = getComputedStyle(menuCommand._toggleSpan); var flyoutSpanStyle = getComputedStyle(menuCommand._flyoutSpan); if (hasToggleCommand) { LiveUnit.Assert.areNotEqual(toggleSpanStyle.display, "none", "When a menu contains a visible toggle command, EVERY command should reserve extra width for the toggle span"); if (menuCommand.type === _Constants.typeToggle && menuCommand.selected) { LiveUnit.Assert.areEqual(toggleSpanStyle.visibility, "visible"); } else { LiveUnit.Assert.areEqual(toggleSpanStyle.visibility, "hidden"); } } else { LiveUnit.Assert.isTrue(toggleSpanStyle.display === "none", "When a menu does not contain visible toggle commands, NO command should reserve space for the toggle span"); } if (hasFlyoutCommand) { LiveUnit.Assert.areNotEqual(flyoutSpanStyle.display, "none", "When a menu contains a visible flyout command, EVERY command should reserve extra width for the flyout span"); if (menuCommand.type === _Constants.typeFlyout) { LiveUnit.Assert.areEqual(flyoutSpanStyle.visibility, "visible"); } else { LiveUnit.Assert.areEqual(flyoutSpanStyle.visibility, "hidden"); } } else { LiveUnit.Assert.isTrue(flyoutSpanStyle.display === "none", "When a menu does not contain visible flyout commands, NO command should reserve space for the flyout span"); } } }); } function testCommandUpdates(commands: Array) { menu.showOnlyCommands(commands, false); verifyCommandLayouts(); menu.hideCommands(commands); verifyCommandLayouts(); menu.showCommands(commands); verifyCommandLayouts(); }; // commands var b1 = new MenuCommand(null, { type: 'button' }), t1 = new MenuCommand(null, { type: 'toggle', selected: true }), t2 = new MenuCommand(null, { type: 'toggle', selected: false }), f1 = new MenuCommand(null, { type: 'flyout' }), s1 = new MenuCommand(null, { type: 'separator' }), commands = [b1, t1, t2, f1, s1]; var menuElement = document.createElement("div"); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { commands: commands }); function menu_onaftershow() { menu.removeEventListener("aftershow", menu_onaftershow, false); verifyCommandLayouts(); // MenuCommands should have correct layout when the Menu is shown. testCommandUpdates([]); testCommandUpdates([b1, t1, t2, f1, s1]); testCommandUpdates([b1, s1]); testCommandUpdates([b1, t1]); testCommandUpdates([t2, f1]); testCommandUpdates([b1, t2, f1, s1]); testCommandUpdates([b1, f1]); testCommandUpdates([t1, f1]); OverlayHelpers.disposeAndRemove(menuElement); complete(); } menu.addEventListener("aftershow", menu_onaftershow, false); menu.show(menu.element); }; testMenuHidesOnActionCommitted = function (complete) { // Whenever any 'button' or 'toggle' typed MenuCommand is invoked, // an action is considered to have been committed and the containing Menu should hide. var commandTypes = { button: "button", toggle: "toggle", separator: "separator", flyout: "flyout" }; var asyncTest = (type: string) => { return new WinJS.Promise((c) => { var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { anchor: document.body }); var command = new MenuCommand(null, { type: type }); menu.commands = [command]; function cleanUp() { menu.onbeforehide = () => { }; OverlayHelpers.disposeAndRemove(menuElement); c(); } OverlayHelpers.show(menu).then(() => { switch (command.type) { case commandTypes.button: case commandTypes.toggle: menu.onbeforehide = () => { cleanUp(); } command._invoke(); break; case commandTypes.separator: case commandTypes.flyout: menu.onbeforehide = () => { LiveUnit.Assert.fail("Menu should not hide when command of type '" + command.type + "' is invoked"); } command._invoke(); WinJS.Promise.timeout(0).then(cleanUp); break; } }); }); }; // Run a test for each commandType. Helper.Promise.forEach(Object.keys(commandTypes), asyncTest).done(complete); }; testFocusChangeBetweenCommandDeactivatesFlyoutCommands = function (complete) { // Moving focus between commands in a Menu will deactivate any Flyout typed commands in the menu each time. // Menus will apply focus to MenuCommands on "mouseover" so this should verify the scenario where an // activated flyout command will deactivate when mousing over other commands in the Menu. var msg = ""; var menuElement = document.createElement('div'); menuElement.id = "menu"; document.body.appendChild(menuElement); var menu = new Menu(menuElement, { anchor: document.body }); var subMenuElement = document.createElement('div'); subMenuElement.id = "subMenuElement"; document.body.appendChild(subMenuElement); var subMenu = new Menu(subMenuElement); var subFlyoutElement = document.createElement('div'); subFlyoutElement.id = "subFlyoutElement"; document.body.appendChild(subFlyoutElement); var subFlyout = new Flyout(subFlyoutElement); var b1 = new MenuCommand(null, { id: 'buttonCmd', type: 'button' }), f1 = new MenuCommand(null, { id: 'subMenuCmd', type: 'flyout', flyout: subMenu }), f2 = new MenuCommand(null, { id: 'subFlyoutCmd', type: 'flyout', flyout: subFlyout }); var commands = [b1, f1, f2]; menu.commands = commands; OverlayHelpers.show(menu).then(() => { msg = "All commands should start out deactivated"; LiveUnit.LoggingCore.logComment("Sanity Check: " + msg); verifyAllCommandsDeactivated(commands, msg); return MenuCommand._activateFlyoutCommand(f1); }).then(() => { OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(f1, "TEST ERROR: command needs to be activated before continuing"); msg = "Focusing an activated 'flyout' typed command in a menu should leave it activated"; LiveUnit.LoggingCore.logComment("Test: " + msg); f1.element.focus(); OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(f1, msg); msg = "Changing focus from an activated 'flyout' typed command in a Menu, to any other command in that Menu, should deactivate all commands."; LiveUnit.LoggingCore.logComment("Test : " + msg); f2.element.focus(); verifyAllCommandsDeactivated(commands, msg); return MenuCommand._activateFlyoutCommand(f2); }).then(() => { OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(f2, "TEST ERROR: command needs to be activated before continuing"); msg = "Changing focus from an activated 'flyout' typed command in a Menu, to any other command in that Menu, should deactivate all commands."; LiveUnit.LoggingCore.logComment("Test: " + msg); b1.element.focus(); verifyAllCommandsDeactivated(commands, msg); OverlayHelpers.disposeAndRemove(menuElement); OverlayHelpers.disposeAndRemove(subMenuElement); OverlayHelpers.disposeAndRemove(subFlyoutElement); complete(); }); }; testCommandsDeactivateWhenContainingMenuHides = function (complete) { // Verifies that hiding a Menu will deactivate any 'flyout' typed commands that it contains. var msg = ""; var menu1Element = document.createElement('div'); menu1Element.id = "menu1"; document.body.appendChild(menu1Element); var menu1 = new Menu(menu1Element, { anchor: document.body }); var menu2Element = document.createElement('div'); menu2Element.id = "menu2"; document.body.appendChild(menu2Element); var menu2 = new Menu(menu2Element); var menu3Element = document.createElement('div'); menu3Element.id = "menu3"; document.body.appendChild(menu3Element); var menu3 = new Menu(menu3Element); var c1 = new MenuCommand(null, { id: 'menu1Cmd', type: 'flyout', flyout: menu2 }), c2 = new MenuCommand(null, { id: 'menu2Cmd', type: 'flyout', flyout: menu3 }), c3 = new MenuCommand(null, { id: 'menu3Cmd', type: 'button' }); menu1.commands = [c1]; menu2.commands = [c2]; menu3.commands = [c3]; OverlayHelpers.show(menu1).then(() => { return MenuCommand._activateFlyoutCommand(c1); }).then(() => { OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(c1, "TEST ERROR: command needs to be activated before continuing"); return MenuCommand._activateFlyoutCommand(c2); }).then(() => { OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(c2, "TEST ERROR: command needs to be activated before continuing"); msg = "When a Menu is hidden, all of its 'flyout' typed MenuCommands should be deactivated"; LiveUnit.LoggingCore.logComment("Test: " + msg); return OverlayHelpers.hide(menu2) }).then(() => { OverlayHelpers.Assert.verifyMenuFlyoutCommandDeactivated(c2); OverlayHelpers.disposeAndRemove(menu1Element); OverlayHelpers.disposeAndRemove(menu2Element); OverlayHelpers.disposeAndRemove(menu3Element); complete(); }); }; testParentMenuMovesFocusToSubMenuWhenActivatedMenuCommandIsFocused = function (complete) { // Verifies that when a Menu contains a 'flyout' typed MenuCommand that is already activated, and that MenuCommand recieves focus, // then the Menu should move focus onto the element of the MenuCommand's subMenu and all of the subMenu's 'flyout' typed commands // should be deactivated. // A real world scenario is a user mousing back into a parent or grandparent Menu across the activated MenuCommand in that Menu. // Mouseover on MenuCommands in a Menu, focuses that command, but when the MenuCommand is already activated we want to put focus // into that command's subMenu and let the cascade Manager close the subSubMenu + descendants. var msg = ""; var parentMenuElement = document.createElement('div'); parentMenuElement.id = "parentMenu"; document.body.appendChild(parentMenuElement); var parentMenu = new Menu(parentMenuElement, { anchor: document.body }); var subMenuElement = document.createElement('div'); subMenuElement.id = "subMenu"; document.body.appendChild(subMenuElement); var subMenu = new Menu(subMenuElement); var subSubMenuElement = document.createElement('div'); subSubMenuElement.id = "subSubMenu"; document.body.appendChild(subSubMenuElement); var subSubMenu = new Menu(subSubMenuElement); function afterSubSubMenuHide() { subSubMenu.removeEventListener("afterhide", afterSubSubMenuHide, false); OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(c1) OverlayHelpers.Assert.verifyMenuFlyoutCommandDeactivated(c2); LiveUnit.Assert.areEqual(document.activeElement, subMenu.element); OverlayHelpers.disposeAndRemove(parentMenuElement); OverlayHelpers.disposeAndRemove(subMenuElement); OverlayHelpers.disposeAndRemove(subSubMenuElement); complete(); } subSubMenu.addEventListener("afterhide", afterSubSubMenuHide, false); var c1 = new MenuCommand(null, { id: 'c1', type: 'flyout', flyout: subMenu }), c2 = new MenuCommand(null, { id: 'c2', type: 'flyout', flyout: subSubMenu }), c3 = new MenuCommand(null, { id: 'c3', type: 'button' }); parentMenu.commands = [c1]; subMenu.commands = [c2]; subSubMenu.commands = [c3]; OverlayHelpers.show(parentMenu).then(() => { return MenuCommand._activateFlyoutCommand(c1); }).then(() => { OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(c1, "TEST ERROR: command needs to be activated before continuing"); return MenuCommand._activateFlyoutCommand(c2); }).then(() => { OverlayHelpers.Assert.verifyMenuFlyoutCommandActivated(c2, "TEST ERROR: command needs to be activated before continuing"); msg = "Focusing an activated command in the Parent Menu should move focus to that commands subMenu and deactivate all 'flyout' commands in the subMenu"; LiveUnit.LoggingCore.logComment("Test: " + msg); c1.element.focus(); }); }; testAdaptiveSpacing = function (complete) { var InputTypes = WinJS.UI._InputTypes; function test(inputType, expectedClass) { var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { anchor: document.body, commands: [ new MenuCommand(null, { id: 'c1', type: 'button' }), new MenuCommand(null, { id: 'c2', type: 'button' }), new MenuCommand(null, { id: 'c3', type: 'button' }) ] }); WinJS.UI._lastInputType = inputType; return OverlayHelpers.show(menu).then(() => { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(menuElement, expectedClass), "Menu should use class '" + expectedClass + "' when last input type was '" + inputType + "'"); OverlayHelpers.disposeAndRemove(menuElement); }); } WinJS.Promise.as().then(() => { return test(InputTypes.mouse, _Constants.menuMouseSpacingClass); }).then(() => { return test(InputTypes.keyboard, _Constants.menuMouseSpacingClass); }).then(() => { return test(InputTypes.touch, _Constants.menuTouchSpacingClass); }).then(() => { return test(InputTypes.pen, _Constants.menuTouchSpacingClass); }).then(complete); } testAdaptiveSpacingDoesntChangeWhileShown = function (complete) { var InputTypes = WinJS.UI._InputTypes; var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { anchor: document.body, commands: [ new MenuCommand(null, { id: 'c1', type: 'button' }), new MenuCommand(null, { id: 'c2', type: 'button' }), new MenuCommand(null, { id: 'c3', type: 'button' }) ] }); WinJS.UI._lastInputType = InputTypes.mouse; return OverlayHelpers.show(menu).then(() => { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(menuElement, _Constants.menuMouseSpacingClass), "Menu should use mouse spacing when last input type was mouse"); WinJS.UI._lastInputType = InputTypes.touch menu.show(document.body); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(menuElement, _Constants.menuMouseSpacingClass), "Menu shouldn't have switched spacing while it was shown"); OverlayHelpers.disposeAndRemove(menuElement); complete(); }); } testAdaptiveSpacingRecomputedWhenShowing = function (complete) { var InputTypes = WinJS.UI._InputTypes; var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { anchor: document.body, commands: [ new MenuCommand(null, { id: 'c1', type: 'button' }), new MenuCommand(null, { id: 'c2', type: 'button' }), new MenuCommand(null, { id: 'c3', type: 'button' }) ] }); WinJS.UI._lastInputType = InputTypes.mouse; return OverlayHelpers.show(menu).then(() => { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(menuElement, _Constants.menuMouseSpacingClass), "Menu should use mouse spacing when last input type was mouse"); return OverlayHelpers.hide(menu); }).then(() => { WinJS.UI._lastInputType = InputTypes.touch; return OverlayHelpers.show(menu); }).then(() => { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(menuElement, _Constants.menuTouchSpacingClass), "Menu should use touch spacing when reshowing and last input type was touch"); OverlayHelpers.disposeAndRemove(menuElement); complete(); }); } testAdaptiveSpacingConsistentInCascade = function (complete) { var InputTypes = WinJS.UI._InputTypes; var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var commands = [ new MenuCommand(null, { id: 'c1', type: 'button' }), new MenuCommand(null, { id: 'c2', type: 'button' }), new MenuCommand(null, { id: 'c3', type: 'button' }) ]; var menu = new Menu(menuElement, { anchor: document.body, commands: commands }); var subMenuElement = document.createElement('div'); document.body.appendChild(subMenuElement); var subMenu = new Menu(subMenuElement, { anchor: commands[0], commands: [ new MenuCommand(null, { id: 'c1', type: 'button' }), new MenuCommand(null, { id: 'c2', type: 'button' }), new MenuCommand(null, { id: 'c3', type: 'button' }) ] }); WinJS.UI._lastInputType = InputTypes.mouse; return OverlayHelpers.show(menu).then(() => { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(menuElement, _Constants.menuMouseSpacingClass), "Menu should use mouse spacing when last input type was mouse"); WinJS.UI._lastInputType = InputTypes.touch; OverlayHelpers.show(subMenu); }).then(() => { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(menuElement, _Constants.menuMouseSpacingClass), "Menu shouldn't have switched spacing while it was shown"); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(subMenuElement, _Constants.menuMouseSpacingClass), "Sub menu should use mouse spacing because that is what the rest of the cascade is using"); OverlayHelpers.disposeAndRemove(menuElement); OverlayHelpers.disposeAndRemove(subMenuElement); complete(); }); } testShowAt(complete) { // Verifies that calling Menu.showAt(point) with an "in bounds point" will align the menu borderbox with the point specified. // An "in bounds point" is defined as a point where the borderbox of the menu can be positioned such that no edge of the menu's // marginbox overruns any edge of the visual viewport. var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); var requiredWindowDimension = 100; // For this test to be valid, the Menu's MarginBox must fit within the confines of the visual. // viewport after we've aligned the top / left of the Menu's borderbox to the specified point. // Otherwise its considered an out of bounds point and is handled in a later test case. LiveUnit.Assert.isTrue(window.innerWidth >= requiredWindowDimension, "TEST ERROR: test expects visual viewport width >= " + requiredWindowDimension + "px"); LiveUnit.Assert.isTrue(window.innerHeight >= requiredWindowDimension, "TEST ERROR: test expects visual viewport height >= " + requiredWindowDimension + "px"); // Find a valid "in bounds point" within the window to pass to Menu.showAt() var style = menu.element.style; var contentSize = 50; var margins = WinJS.Utilities._getPreciseMargins(menu.element); style.width = contentSize + "px"; style.minWidth = contentSize + "px"; style.maxWidth = contentSize + "px"; style.height = contentSize + "px"; style.maxHeight = contentSize + "px"; style.minHeight = contentSize + "px"; // Make sure the point we choose for the top/left of the borderbox also leaves the marginbox clear of the viewport top/left edge. var testX = 2 + margins.left; var testY = 2 + margins.top; function testShowAt_WithCoordinates(): WinJS.Promise { var coordinates = { x: testX, y: testY }; return verifyPositionOnScreen(coordinates, "Coordinates"); } function testShowAt_WithMouseEvent(): WinJS.Promise { // API requires clientX and clientY properties. var pointerEventObjectShim = { clientX: testX, clientY: testY }; return verifyPositionOnScreen(pointerEventObjectShim, "MouseEventObj"); } function verifyPositionOnScreen(testParameter, testParameterType): WinJS.Promise { // Verify that the menu is positioned with the top left corner of its border box located at // the location specified by the testParameter. return new WinJS.Promise(function (completePromise) { menu.onaftershow = () => { menu.onaftershow = null; var menuRect = menu.element.getBoundingClientRect(); LiveUnit.Assert.areEqual(testY, menuRect.top, testParameterType + ": Menu should be top aligned with the y coordinate"); LiveUnit.Assert.areEqual(testX, menuRect.left, testParameterType + ": Menu should be left aligned with the x coordinate"); menu.onafterhide = function () { menu.onafterhide = null; completePromise(); } menu.hide(); }; menu.showAt(testParameter); }); } testShowAt_WithCoordinates() .then(testShowAt_WithMouseEvent) .done(() => { OverlayHelpers.disposeAndRemove(menuElement); complete(); }); } testShowAt_Boundaries(complete) { // Verify that when showAt is called: // if any edge of the menu would clip through the corresponding edge of the visual viewport, // then: the menu is repositioned such that the clipping edge is instead pinned to the // corresponding viewport edge. function getLocation(menu: WinJS.UI.PrivateMenu): IMarginBox { // Returns locaton of the Menu's margin box. var margins = WinJS.Utilities._getPreciseMargins(menu.element); var borderBox = menu.element.getBoundingClientRect(); return { top: borderBox.top - margins.top, right: borderBox.right + margins.right, bottom: borderBox.bottom + margins.bottom, left: borderBox.left - margins.left, } } function asyncShowAt(menu: WinJS.UI.PrivateMenu, options: { x: number; y: number; }) { return new WinJS.Promise((completePromise) => { menu.addEventListener("aftershow", function afterShow() { menu.removeEventListener("aftershow", afterShow, false); completePromise(); }, false); if (menu.hidden) { menu.showAt(options); } else { menu.addEventListener("afterhide", function afterHide() { menu.removeEventListener("afterhide", afterHide, false); menu.showAt(options); }, false); menu.hide(); } }); } var menuElement = document.createElement('div'); document.body.appendChild(menuElement); var menu = new Menu(menuElement, { commands: { type: 'separator', id: 'sep' } }); var marginBox: IMarginBox; // Test Cases: var overrunTopLeft = { x: -2, y: -2 }; var overrunTopRight = { x: window.innerWidth, y: -2 }; var overrunBottomLeft = { x: -2, y: window.innerHeight }; var overrunBottomRight = { x: window.innerWidth, y: window.innerHeight }; var msg = "Top left boundary: "; asyncShowAt(menu, overrunTopLeft) .then(() => { marginBox = getLocation(menu); Helper.Assert.areFloatsEqual(0, marginBox.left, msg + "menu should not overrun left edge", 1); Helper.Assert.areFloatsEqual(0, marginBox.top, msg + "menu should not overrun top edge", 1); msg = "Top right boundary: "; return asyncShowAt(menu, overrunTopRight); }) .then(() => { marginBox = getLocation(menu); Helper.Assert.areFloatsEqual(window.innerWidth, marginBox.right, msg + "menu should not overrun right edge", 1); Helper.Assert.areFloatsEqual(0, marginBox.top, msg + "menu should not overrun top edge", 1); msg = "Bottom left boundary: "; return asyncShowAt(menu, overrunBottomLeft) }) .then(() => { marginBox = getLocation(menu); Helper.Assert.areFloatsEqual(0, marginBox.left, msg + "menu should not overrun left edge", 1); Helper.Assert.areFloatsEqual(window.innerHeight, marginBox.bottom, msg + "menu should not overrun bottom edge", 1); msg = "Bottom right boundary: "; return asyncShowAt(menu, overrunBottomRight) }) .done(() => { marginBox = getLocation(menu); Helper.Assert.areFloatsEqual(window.innerWidth, marginBox.right, msg + "menu should not overrun right edge", 1); Helper.Assert.areFloatsEqual(window.innerHeight, marginBox.bottom, msg + "menu should not overrun bottom edge", 1); OverlayHelpers.disposeAndRemove(menuElement); complete(); }); } } } // register the object as a test class by passing in the name LiveUnit.registerTestClass("CorsicaTests.MenuTests");