844 lines
29 KiB
JavaScript
844 lines
29 KiB
JavaScript
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
goog.provide('goog.ui.MenuButtonTest');
|
|
goog.setTestOnly('goog.ui.MenuButtonTest');
|
|
|
|
goog.require('goog.Timer');
|
|
goog.require('goog.a11y.aria');
|
|
goog.require('goog.a11y.aria.State');
|
|
goog.require('goog.dom');
|
|
goog.require('goog.events');
|
|
goog.require('goog.events.Event');
|
|
goog.require('goog.events.EventType');
|
|
goog.require('goog.events.KeyCodes');
|
|
goog.require('goog.events.KeyHandler');
|
|
goog.require('goog.positioning');
|
|
goog.require('goog.positioning.Corner');
|
|
goog.require('goog.positioning.MenuAnchoredPosition');
|
|
goog.require('goog.positioning.Overflow');
|
|
goog.require('goog.style');
|
|
goog.require('goog.testing.ExpectedFailures');
|
|
goog.require('goog.testing.PropertyReplacer');
|
|
goog.require('goog.testing.events');
|
|
goog.require('goog.testing.jsunit');
|
|
goog.require('goog.testing.recordFunction');
|
|
goog.require('goog.ui.Component');
|
|
goog.require('goog.ui.Menu');
|
|
goog.require('goog.ui.MenuButton');
|
|
goog.require('goog.ui.MenuItem');
|
|
goog.require('goog.ui.SubMenu');
|
|
goog.require('goog.userAgent');
|
|
goog.require('goog.userAgent.product');
|
|
goog.require('goog.userAgent.product.isVersion');
|
|
|
|
var menuButton;
|
|
var clonedMenuButtonDom;
|
|
var expectedFailures;
|
|
|
|
function setUpPage() {
|
|
expectedFailures = new goog.testing.ExpectedFailures();
|
|
}
|
|
|
|
// Mock out goog.positioning.positionAtCoordinate to always ignore failure when
|
|
// the window is too small, since we don't care about the viewport size on
|
|
// the selenium farm.
|
|
// TODO(nicksantos): Move this into a common location if we ever have enough
|
|
// code for a general goog.testing.ui library.
|
|
var originalPositionAtCoordinate = goog.positioning.positionAtCoordinate;
|
|
goog.positioning.positionAtCoordinate = function(absolutePos, movableElement,
|
|
movableElementCorner, opt_margin, opt_viewport, opt_overflow,
|
|
opt_preferredSize) {
|
|
return originalPositionAtCoordinate.call(this, absolutePos, movableElement,
|
|
movableElementCorner, opt_margin, opt_viewport,
|
|
goog.positioning.Overflow.IGNORE, opt_preferredSize);
|
|
};
|
|
|
|
function MyFakeEvent(keyCode, opt_eventType) {
|
|
this.type = opt_eventType || goog.events.KeyHandler.EventType.KEY;
|
|
this.keyCode = keyCode;
|
|
this.propagationStopped = false;
|
|
this.preventDefault = goog.nullFunction;
|
|
this.stopPropagation = function() {
|
|
this.propagationStopped = true;
|
|
};
|
|
}
|
|
|
|
function setUp() {
|
|
window.scrollTo(0, 0);
|
|
|
|
var viewportSize = goog.dom.getViewportSize();
|
|
// Some tests need enough size viewport.
|
|
if (viewportSize.width < 600 || viewportSize.height < 600) {
|
|
window.moveTo(0, 0);
|
|
window.resizeTo(640, 640);
|
|
}
|
|
|
|
clonedMenuButtonDom = goog.dom.getElement('demoMenuButton').cloneNode(true);
|
|
|
|
menuButton = new goog.ui.MenuButton();
|
|
}
|
|
|
|
function tearDown() {
|
|
expectedFailures.handleTearDown();
|
|
menuButton.dispose();
|
|
|
|
var element = goog.dom.getElement('demoMenuButton');
|
|
element.parentNode.replaceChild(clonedMenuButtonDom, element);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if the aria-haspopup property is set correctly.
|
|
*/
|
|
function checkHasPopUp() {
|
|
menuButton.enterDocument();
|
|
assertFalse('Menu button must have aria-haspopup attribute set to false',
|
|
goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.HASPOPUP));
|
|
var menu = new goog.ui.Menu();
|
|
menu.createDom();
|
|
menuButton.setMenu(menu);
|
|
assertTrue('Menu button must have aria-haspopup attribute set to true',
|
|
goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.HASPOPUP));
|
|
menuButton.setMenu(null);
|
|
assertFale('Menu button must have aria-haspopup attribute set to false',
|
|
goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.HASPOPUP));
|
|
}
|
|
|
|
|
|
/**
|
|
* Open the menu and click on the menu item inside.
|
|
* Check if the aria-haspopup property is set correctly.
|
|
*/
|
|
function testBasicButtonBehavior() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
|
|
assertEquals('Menu button must have aria-haspopup attribute set to true',
|
|
'true', goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.HASPOPUP));
|
|
|
|
goog.testing.events.fireClickSequence(node);
|
|
|
|
assertTrue('Menu must open after click', menuButton.isOpen());
|
|
|
|
var menuItemClicked = 0;
|
|
var lastMenuItemClicked = null;
|
|
goog.events.listen(menuButton.getMenu(),
|
|
goog.ui.Component.EventType.ACTION,
|
|
function(e) {
|
|
menuItemClicked++;
|
|
lastMenuItemClicked = e.target;
|
|
});
|
|
|
|
var menuItem2 = goog.dom.getElement('menuItem2');
|
|
goog.testing.events.fireClickSequence(menuItem2);
|
|
assertFalse('Menu must close on clicking when open', menuButton.isOpen());
|
|
assertEquals('Number of menu items clicked should be 1', 1, menuItemClicked);
|
|
assertEquals('menuItem2 should be the last menuitem clicked', menuItem2,
|
|
lastMenuItemClicked.getElement());
|
|
}
|
|
|
|
|
|
/**
|
|
* Open the menu, highlight first menuitem and then the second.
|
|
* Check if the aria-activedescendant property is set correctly.
|
|
*/
|
|
function testHighlightItemBehavior() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
goog.testing.events.fireClickSequence(node);
|
|
|
|
assertTrue('Menu must open after click', menuButton.isOpen());
|
|
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.DOWN));
|
|
assertNotNull(menuButton.getElement());
|
|
assertEquals('First menuitem must be the aria-activedescendant',
|
|
'menuItem1', goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.ACTIVEDESCENDANT));
|
|
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.DOWN));
|
|
assertEquals('Second menuitem must be the aria-activedescendant',
|
|
'menuItem2', goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.ACTIVEDESCENDANT));
|
|
}
|
|
|
|
|
|
/**
|
|
* Check that the appropriate items are selected when menus are opened with the
|
|
* keyboard and setSelectFirstOnEnterOrSpace is not set.
|
|
*/
|
|
function testHighlightFirstOnOpen() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.ENTER));
|
|
assertEquals(
|
|
'By default no items should be highlighted when opened with enter.',
|
|
null, menuButton.getMenu().getHighlighted());
|
|
|
|
menuButton.setOpen(false);
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.DOWN));
|
|
assertTrue('Menu must open after down key', menuButton.isOpen());
|
|
assertEquals('First menuitem must be highlighted',
|
|
'menuItem1', menuButton.getMenu().getHighlighted().getElement().id);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check that the appropriate items are selected when menus are opened with the
|
|
* keyboard, setSelectFirstOnEnterOrSpace is not set, and the first menu item is
|
|
* disabled.
|
|
*/
|
|
function testHighlightFirstOnOpen_withFirstDisabled() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
var menu = menuButton.getMenu();
|
|
menu.getItemAt(0).setEnabled(false);
|
|
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.ENTER));
|
|
assertEquals(
|
|
'By default no items should be highlighted when opened with enter.',
|
|
null, menuButton.getMenu().getHighlighted());
|
|
|
|
menuButton.setOpen(false);
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.DOWN));
|
|
assertTrue('Menu must open after down key', menuButton.isOpen());
|
|
assertEquals('First enabled menuitem must be highlighted',
|
|
'menuItem2', menuButton.getMenu().getHighlighted().getElement().id);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check that the appropriate items are selected when menus are opened with the
|
|
* keyboard and setSelectFirstOnEnterOrSpace is set.
|
|
*/
|
|
function testHighlightFirstOnOpen_withEnterOrSpaceSet() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
menuButton.setSelectFirstOnEnterOrSpace(true);
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.ENTER));
|
|
assertEquals('The first item should be highlighted when opened with enter ' +
|
|
'after setting selectFirstOnEnterOrSpace',
|
|
'menuItem1', menuButton.getMenu().getHighlighted().getElement().id);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check that the appropriate item is selected when a menu is opened with the
|
|
* keyboard, setSelectFirstOnEnterOrSpace is true, and the first menu item is
|
|
* disabled.
|
|
*/
|
|
function testHighlightFirstOnOpen_withEnterOrSpaceSetAndFirstDisabled() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
menuButton.setSelectFirstOnEnterOrSpace(true);
|
|
var menu = menuButton.getMenu();
|
|
menu.getItemAt(0).setEnabled(false);
|
|
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.ENTER));
|
|
assertEquals('The first enabled item should be highlighted when opened ' +
|
|
'with enter after setting selectFirstOnEnterOrSpace',
|
|
'menuItem2', menuButton.getMenu().getHighlighted().getElement().id);
|
|
}
|
|
|
|
|
|
/**
|
|
* Open the menu, enter a submenu and then back out of it.
|
|
* Check if the aria-activedescendant property is set correctly.
|
|
*/
|
|
function testCloseSubMenuBehavior() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
var menu = menuButton.getMenu();
|
|
var subMenu = new goog.ui.SubMenu('Submenu');
|
|
menu.addItem(subMenu);
|
|
subMenu.getElement().id = 'subMenu';
|
|
var subMenuMenu = new goog.ui.Menu();
|
|
subMenu.setMenu(subMenuMenu);
|
|
var subMenuItem = new goog.ui.MenuItem('Submenu item 1');
|
|
subMenuMenu.addItem(subMenuItem);
|
|
subMenuItem.getElement().id = 'subMenuItem1';
|
|
menuButton.setOpen(true);
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.DOWN));
|
|
}
|
|
assertEquals('Submenu must be the aria-activedescendant',
|
|
'subMenu', goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.ACTIVEDESCENDANT));
|
|
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.RIGHT));
|
|
assertEquals('Submenu item 1 must be the aria-activedescendant',
|
|
'subMenuItem1', goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.ACTIVEDESCENDANT));
|
|
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.LEFT));
|
|
assertEquals('Submenu must be the aria-activedescendant',
|
|
'subMenu', goog.a11y.aria.getState(menuButton.getElement(),
|
|
goog.a11y.aria.State.ACTIVEDESCENDANT));
|
|
}
|
|
|
|
|
|
/**
|
|
* Make sure the menu opens when enter is pressed.
|
|
*/
|
|
function testEnterOpensMenu() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.ENTER));
|
|
assertTrue('Menu must open after enter', menuButton.isOpen());
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests the behavior of the enter and space keys when the menu is open.
|
|
*/
|
|
function testSpaceOrEnterClosesMenu() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
|
|
menuButton.setOpen(true);
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.ENTER));
|
|
assertFalse('Menu should close after pressing Enter', menuButton.isOpen());
|
|
|
|
menuButton.setOpen(true);
|
|
menuButton.handleKeyEvent(new MyFakeEvent(goog.events.KeyCodes.SPACE,
|
|
goog.events.EventType.KEYUP));
|
|
assertFalse('Menu should close after pressing Space', menuButton.isOpen());
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that a keydown event of the escape key propagates normally when the
|
|
* menu is closed.
|
|
*/
|
|
function testStopEscapePropagationMenuClosed() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
var fakeEvent = new MyFakeEvent(
|
|
goog.events.KeyCodes.ESCAPE, goog.events.EventType.KEYDOWN);
|
|
menuButton.decorate(node);
|
|
menuButton.setOpen(false);
|
|
|
|
menuButton.handleKeyDownEvent_(fakeEvent);
|
|
assertFalse('Event propagation was erroneously stopped.',
|
|
fakeEvent.propagationStopped);
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that a keydown event of the escape key is prevented from propagating
|
|
* when the menu is open.
|
|
*/
|
|
function testStopEscapePropagationMenuOpen() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
var fakeEvent = new MyFakeEvent(
|
|
goog.events.KeyCodes.ESCAPE, goog.events.EventType.KEYDOWN);
|
|
menuButton.decorate(node);
|
|
menuButton.setOpen(true);
|
|
|
|
menuButton.handleKeyDownEvent_(fakeEvent);
|
|
assertTrue(
|
|
'Event propagation was not stopped.', fakeEvent.propagationStopped);
|
|
}
|
|
|
|
|
|
/**
|
|
* Open the menu and click on the menu item inside after exiting and entering
|
|
* the document once, to test proper setup/teardown behavior of MenuButton.
|
|
*/
|
|
function testButtonAfterEnterDocument() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
|
|
menuButton.exitDocument();
|
|
menuButton.enterDocument();
|
|
|
|
goog.testing.events.fireClickSequence(node);
|
|
assertTrue('Menu must open after click', menuButton.isOpen());
|
|
|
|
var menuItem2 = goog.dom.getElement('menuItem2');
|
|
goog.testing.events.fireClickSequence(menuItem2);
|
|
assertFalse('Menu must close on clicking when open', menuButton.isOpen());
|
|
}
|
|
|
|
|
|
/**
|
|
* Renders the menu button, moves its menu and then repositions to make sure the
|
|
* position is more or less ok.
|
|
*/
|
|
function testPositionMenu() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
var menu = menuButton.getMenu();
|
|
menu.setVisible(true, true);
|
|
|
|
// Move to 500, 500
|
|
menu.setPosition(500, 500);
|
|
|
|
// Now reposition and make sure position is more or less ok.
|
|
menuButton.positionMenu();
|
|
var menuNode = goog.dom.getElement('demoMenu');
|
|
assertRoughlyEquals(menuNode.offsetTop, node.offsetTop + node.offsetHeight,
|
|
20);
|
|
assertRoughlyEquals(menuNode.offsetLeft, node.offsetLeft, 20);
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that calling positionMenu when the menu is not in the document does not
|
|
* throw an exception.
|
|
*/
|
|
function testPositionMenuNotInDocument() {
|
|
var menu = new goog.ui.Menu();
|
|
menu.createDom();
|
|
menuButton.setMenu(menu);
|
|
menuButton.positionMenu();
|
|
}
|
|
|
|
|
|
/**
|
|
* Shows the menu and moves the menu button, a timer correct the menu position.
|
|
*/
|
|
function testOpenedMenuPositionCorrection() {
|
|
var iframe = goog.dom.getElement('iframe1');
|
|
var iframeDoc = goog.dom.getFrameContentDocument(iframe);
|
|
var iframeDom = goog.dom.getDomHelper(iframeDoc);
|
|
var iframeWindow = goog.dom.getWindow(iframeDoc);
|
|
|
|
var button = new goog.ui.MenuButton();
|
|
iframeWindow.scrollTo(0, 0);
|
|
var node = iframeDom.getElement('demoMenuButton');
|
|
button.decorate(node);
|
|
var mockTimer = new goog.Timer();
|
|
// Don't start the timer. We manually dispatch the Tick event.
|
|
mockTimer.start = goog.nullFunction;
|
|
button.timer_ = mockTimer;
|
|
|
|
var replacer = new goog.testing.PropertyReplacer();
|
|
var positionMenuCalled;
|
|
var origPositionMenu = goog.bind(button.positionMenu, button);
|
|
replacer.set(button, 'positionMenu', function() {
|
|
positionMenuCalled = true;
|
|
origPositionMenu();
|
|
});
|
|
|
|
// Show the menu.
|
|
button.setOpen(true);
|
|
|
|
// Confirm the menu position
|
|
var menuNode = iframeDom.getElement('demoMenu');
|
|
assertRoughlyEquals(menuNode.offsetTop, node.offsetTop + node.offsetHeight,
|
|
20);
|
|
assertRoughlyEquals(menuNode.offsetLeft, node.offsetLeft, 20);
|
|
|
|
positionMenuCalled = false;
|
|
// A Tick event is dispatched.
|
|
mockTimer.dispatchEvent(goog.Timer.TICK);
|
|
assertFalse('positionMenu() shouldn\'t be called.', positionMenuCalled);
|
|
|
|
// Move the menu button by DOM structure change
|
|
var p1 = iframeDom.createDom('p', null, iframeDom.createTextNode('foo'));
|
|
var p2 = iframeDom.createDom('p', null, iframeDom.createTextNode('foo'));
|
|
var p3 = iframeDom.createDom('p', null, iframeDom.createTextNode('foo'));
|
|
iframeDom.insertSiblingBefore(p1, node);
|
|
iframeDom.insertSiblingBefore(p2, node);
|
|
iframeDom.insertSiblingBefore(p3, node);
|
|
|
|
// Confirm the menu is detached from the button.
|
|
assertTrue(Math.abs(node.offsetTop + node.offsetHeight -
|
|
menuNode.offsetTop) > 20);
|
|
|
|
positionMenuCalled = false;
|
|
// A Tick event is dispatched.
|
|
mockTimer.dispatchEvent(goog.Timer.TICK);
|
|
assertTrue('positionMenu() should be called.', positionMenuCalled);
|
|
|
|
// The menu is moved to appropriate position again.
|
|
assertRoughlyEquals(menuNode.offsetTop, node.offsetTop + node.offsetHeight,
|
|
20);
|
|
|
|
// Make the frame page scrollable.
|
|
var viewportHeight = iframeDom.getViewportSize().height;
|
|
var footer = iframeDom.getElement('footer');
|
|
goog.style.setSize(footer, 1, viewportHeight * 2);
|
|
// Change the viewport offset.
|
|
iframeWindow.scrollTo(0, viewportHeight);
|
|
// A Tick event is dispatched and positionMenu() should be called.
|
|
positionMenuCalled = false;
|
|
mockTimer.dispatchEvent(goog.Timer.TICK);
|
|
assertTrue('positionMenu() should be called.', positionMenuCalled);
|
|
goog.style.setSize(footer, 1, 1);
|
|
|
|
// Tear down.
|
|
iframeDom.removeNode(p1);
|
|
iframeDom.removeNode(p2);
|
|
iframeDom.removeNode(p3);
|
|
replacer.reset();
|
|
button.dispose();
|
|
}
|
|
|
|
|
|
/**
|
|
* Use a different button to position the menu and make sure it does so
|
|
* correctly.
|
|
*/
|
|
function testAlternatePositioningElement() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
|
|
var posElement = goog.dom.getElement('positionElement');
|
|
menuButton.setPositionElement(posElement);
|
|
|
|
// Show the menu.
|
|
menuButton.setOpen(true);
|
|
|
|
// Confirm the menu position
|
|
var menuNode = menuButton.getMenu().getElement();
|
|
assertRoughlyEquals(menuNode.offsetTop, posElement.offsetTop +
|
|
posElement.offsetHeight, 20);
|
|
assertRoughlyEquals(menuNode.offsetLeft, posElement.offsetLeft, 20);
|
|
}
|
|
|
|
|
|
/**
|
|
* Test forced positioning above the button.
|
|
*/
|
|
function testPositioningAboveAnchor() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
|
|
// Show the menu.
|
|
menuButton.setAlignMenuToStart(true); // Should get overridden below
|
|
menuButton.setScrollOnOverflow(true); // Should get overridden below
|
|
|
|
var position = new goog.positioning.MenuAnchoredPosition(
|
|
menuButton.getElement(),
|
|
goog.positioning.Corner.TOP_START,
|
|
/* opt_adjust */ false, /* opt_resize */ false);
|
|
menuButton.setMenuPosition(position);
|
|
menuButton.setOpen(true);
|
|
|
|
// Confirm the menu position
|
|
var buttonBounds = goog.style.getBounds(node);
|
|
var menuNode = menuButton.getMenu().getElement();
|
|
var menuBounds = goog.style.getBounds(menuNode);
|
|
|
|
assertRoughlyEquals(menuBounds.top + menuBounds.height,
|
|
buttonBounds.top, 3);
|
|
assertRoughlyEquals(menuBounds.left, buttonBounds.left, 3);
|
|
// For this test to be valid, the node must have non-trival height.
|
|
assertRoughlyEquals(node.offsetHeight, 19, 3);
|
|
}
|
|
|
|
|
|
/**
|
|
* Test forced positioning below the button.
|
|
*/
|
|
function testPositioningBelowAnchor() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
|
|
// Show the menu.
|
|
menuButton.setAlignMenuToStart(true); // Should get overridden below
|
|
menuButton.setScrollOnOverflow(true); // Should get overridden below
|
|
|
|
var position = new goog.positioning.MenuAnchoredPosition(
|
|
menuButton.getElement(),
|
|
goog.positioning.Corner.BOTTOM_START,
|
|
/* opt_adjust */ false, /* opt_resize */ false);
|
|
menuButton.setMenuPosition(position);
|
|
menuButton.setOpen(true);
|
|
|
|
// Confirm the menu position
|
|
var buttonBounds = goog.style.getBounds(node);
|
|
var menuNode = menuButton.getMenu().getElement();
|
|
var menuBounds = goog.style.getBounds(menuNode);
|
|
|
|
expectedFailures.expectFailureFor(isWinSafariBefore5());
|
|
try {
|
|
assertRoughlyEquals(menuBounds.top,
|
|
buttonBounds.top + buttonBounds.height, 3);
|
|
assertRoughlyEquals(menuBounds.left, buttonBounds.left, 3);
|
|
} catch (e) {
|
|
expectedFailures.handleException(e);
|
|
}
|
|
// For this test to be valid, the node must have non-trival height.
|
|
assertRoughlyEquals(node.offsetHeight, 19, 3);
|
|
}
|
|
|
|
function isWinSafariBefore5() {
|
|
return goog.userAgent.WINDOWS && goog.userAgent.product.SAFARI &&
|
|
goog.userAgent.product.isVersion(4) &&
|
|
!goog.userAgent.product.isVersion(5);
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that space, and only space, fire on key up.
|
|
*/
|
|
function testSpaceFireOnKeyUp() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
|
|
e = new goog.events.Event(goog.events.KeyHandler.EventType.KEY, menuButton);
|
|
e.preventDefault = goog.testing.recordFunction();
|
|
e.keyCode = goog.events.KeyCodes.SPACE;
|
|
menuButton.handleKeyEvent(e);
|
|
assertFalse('Menu must not have been triggered by Space keypress',
|
|
menuButton.isOpen());
|
|
assertNotNull('Page scrolling is prevented', e.preventDefault.getLastCall());
|
|
|
|
e = new goog.events.Event(goog.events.EventType.KEYUP, menuButton);
|
|
e.keyCode = goog.events.KeyCodes.SPACE;
|
|
menuButton.handleKeyEvent(e);
|
|
assertTrue('Menu must have been triggered by Space keyup',
|
|
menuButton.isOpen());
|
|
menuButton.getMenu().setHighlightedIndex(0);
|
|
e = new goog.events.Event(goog.events.KeyHandler.EventType.KEY, menuButton);
|
|
e.keyCode = goog.events.KeyCodes.DOWN;
|
|
menuButton.handleKeyEvent(e);
|
|
assertEquals('Highlighted menu item must have hanged by Down keypress',
|
|
1,
|
|
menuButton.getMenu().getHighlightedIndex());
|
|
|
|
menuButton.getMenu().setHighlightedIndex(0);
|
|
e = new goog.events.Event(goog.events.EventType.KEYUP, menuButton);
|
|
e.keyCode = goog.events.KeyCodes.DOWN;
|
|
menuButton.handleKeyEvent(e);
|
|
assertEquals('Highlighted menu item must not have changed by Down keyup',
|
|
0,
|
|
menuButton.getMenu().getHighlightedIndex());
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that preventing the button from closing also prevents the menu from
|
|
* being hidden.
|
|
*/
|
|
function testPreventHide() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
menuButton.setDispatchTransitionEvents(goog.ui.Component.State.OPENED, true);
|
|
|
|
// Show the menu.
|
|
menuButton.setOpen(true);
|
|
assertTrue('Menu button should be open.', menuButton.isOpen());
|
|
assertTrue('Menu should be visible.', menuButton.getMenu().isVisible());
|
|
|
|
var key = goog.events.listen(menuButton,
|
|
goog.ui.Component.EventType.CLOSE,
|
|
function(event) { event.preventDefault(); });
|
|
|
|
// Try to hide the menu.
|
|
menuButton.setOpen(false);
|
|
assertTrue('Menu button should still be open.', menuButton.isOpen());
|
|
assertTrue('Menu should still be visible.', menuButton.getMenu().isVisible());
|
|
|
|
// Remove listener and try again.
|
|
goog.events.unlistenByKey(key);
|
|
menuButton.setOpen(false);
|
|
assertFalse('Menu button should not be open.', menuButton.isOpen());
|
|
assertFalse('Menu should not be visible.', menuButton.getMenu().isVisible());
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that opening and closing the menu does not affect how adding or
|
|
* removing menu items changes the size of the menu.
|
|
*/
|
|
function testResizeOnItemAddOrRemove() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
var menu = menuButton.getMenu();
|
|
|
|
// Show the menu.
|
|
menuButton.setOpen(true);
|
|
var originalSize = goog.style.getSize(menu.getElement());
|
|
|
|
// Check that removing an item while the menu is left open correctly changes
|
|
// the size of the menu.
|
|
// Remove an item using a method on Menu.
|
|
var item = menu.removeChildAt(0, true);
|
|
// Confirm size of menu changed.
|
|
var afterRemoveSize = goog.style.getSize(menu.getElement());
|
|
assertTrue('Height of menu must decrease after removing a menu item.',
|
|
afterRemoveSize.height < originalSize.height);
|
|
|
|
// Check that removing an item while the menu is closed, then opened
|
|
// (so that reposition is called) correctly changes the size of the menu.
|
|
// Hide menu.
|
|
menuButton.setOpen(false);
|
|
var item2 = menu.removeChildAt(0, true);
|
|
menuButton.setOpen(true);
|
|
// Confirm size of menu changed.
|
|
var afterRemoveAgainSize = goog.style.getSize(menu.getElement());
|
|
assertTrue('Height of menu must decrease after removing a second menu item.',
|
|
afterRemoveAgainSize.height < afterRemoveSize.height);
|
|
|
|
// Check that adding an item while the menu is opened, then closed, then
|
|
// opened, correctly changes the size of the menu.
|
|
// Add an item, this time using a MenuButton method.
|
|
menuButton.setOpen(true);
|
|
menuButton.addItem(item2);
|
|
menuButton.setOpen(false);
|
|
menuButton.setOpen(true);
|
|
// Confirm size of menu changed.
|
|
var afterAddSize = goog.style.getSize(menu.getElement());
|
|
assertTrue('Height of menu must increase after adding a menu item.',
|
|
afterRemoveAgainSize.height < afterAddSize.height);
|
|
assertEquals(
|
|
'Removing and adding back items must not change the height of a menu.',
|
|
afterRemoveSize.height, afterAddSize.height);
|
|
|
|
// Add back the last item to keep state consistent.
|
|
menuButton.addItem(item);
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that adding and removing items from a menu with scrollOnOverflow is on
|
|
* correctly resizes the menu.
|
|
*/
|
|
function testResizeOnItemAddOrRemoveWithScrollOnOverflow() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
var menu = menuButton.getMenu();
|
|
|
|
// Show the menu.
|
|
menuButton.setScrollOnOverflow(true);
|
|
menuButton.setOpen(true);
|
|
var originalSize = goog.style.getSize(menu.getElement());
|
|
|
|
// Check that removing an item while the menu is left open correctly changes
|
|
// the size of the menu.
|
|
// Remove an item using a method on Menu.
|
|
var item = menu.removeChildAt(0, true);
|
|
menuButton.invalidateMenuSize();
|
|
menuButton.positionMenu();
|
|
|
|
// Confirm size of menu changed.
|
|
var afterRemoveSize = goog.style.getSize(menu.getElement());
|
|
assertTrue('Height of menu must decrease after removing a menu item.',
|
|
afterRemoveSize.height < originalSize.height);
|
|
|
|
var item2 = menu.removeChildAt(0, true);
|
|
menuButton.invalidateMenuSize();
|
|
menuButton.positionMenu();
|
|
|
|
// Confirm size of menu changed.
|
|
var afterRemoveAgainSize = goog.style.getSize(menu.getElement());
|
|
assertTrue('Height of menu must decrease after removing a second menu item.',
|
|
afterRemoveAgainSize.height < afterRemoveSize.height);
|
|
|
|
// Check that adding an item while the menu is opened correctly changes the
|
|
// size of the menu.
|
|
menuButton.addItem(item2);
|
|
menuButton.invalidateMenuSize();
|
|
menuButton.positionMenu();
|
|
|
|
// Confirm size of menu changed.
|
|
var afterAddSize = goog.style.getSize(menu.getElement());
|
|
assertTrue('Height of menu must increase after adding a menu item.',
|
|
afterRemoveAgainSize.height < afterAddSize.height);
|
|
assertEquals(
|
|
'Removing and adding back items must not change the height of a menu.',
|
|
afterRemoveSize.height, afterAddSize.height);
|
|
}
|
|
|
|
|
|
/**
|
|
* Try rendering the menu as a sibling rather than as a child of the dom. This
|
|
* tests the case when the button is rendered, rather than decorated.
|
|
*/
|
|
function testRenderMenuAsSibling() {
|
|
menuButton.setRenderMenuAsSibling(true);
|
|
menuButton.addItem(new goog.ui.MenuItem('Menu item 1'));
|
|
menuButton.addItem(new goog.ui.MenuItem('Menu item 2'));
|
|
// By default the menu is rendered into the top level dom and the button
|
|
// is rendered into whatever parent we provide. If we don't provide a
|
|
// parent then we aren't really testing anything, since both would be, by
|
|
// default, rendered into the top level dom, and therefore siblings.
|
|
menuButton.render(goog.dom.getElement('siblingTest'));
|
|
menuButton.setOpen(true);
|
|
assertEquals(
|
|
menuButton.getElement().parentNode,
|
|
menuButton.getMenu().getElement().parentNode);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check that we render the menu as a sibling of the menu button, immediately
|
|
* after the menu button.
|
|
*/
|
|
function testRenderMenuAsSiblingForDecoratedButton() {
|
|
var menu = new goog.ui.Menu();
|
|
menu.addChild(new goog.ui.MenuItem('Menu item 1'), true /* render */);
|
|
menu.addChild(new goog.ui.MenuItem('Menu item 2'), true /* render */);
|
|
menu.addChild(new goog.ui.MenuItem('Menu item 3'), true /* render */);
|
|
|
|
var menuButton = new goog.ui.MenuButton();
|
|
menuButton.setMenu(menu);
|
|
menuButton.setRenderMenuAsSibling(true);
|
|
var node = goog.dom.getElement('button1');
|
|
menuButton.decorate(node);
|
|
|
|
menuButton.setOpen(true);
|
|
|
|
assertEquals('The menu should be rendered immediately after the menu button',
|
|
goog.dom.getNextElementSibling(menuButton.getElement()),
|
|
menu.getElement());
|
|
|
|
assertEquals('The menu should be rendered immediately before the next button',
|
|
goog.dom.getNextElementSibling(menu.getElement()),
|
|
goog.dom.getElement('button2'));
|
|
}
|
|
|
|
function testAlignToStartSetter() {
|
|
assertTrue(menuButton.isAlignMenuToStart());
|
|
|
|
menuButton.setAlignMenuToStart(false);
|
|
assertFalse(menuButton.isAlignMenuToStart());
|
|
|
|
menuButton.setAlignMenuToStart(true);
|
|
assertTrue(menuButton.isAlignMenuToStart());
|
|
}
|
|
|
|
function testScrollOnOverflowSetter() {
|
|
assertFalse(menuButton.isScrollOnOverflow());
|
|
|
|
menuButton.setScrollOnOverflow(true);
|
|
assertTrue(menuButton.isScrollOnOverflow());
|
|
|
|
menuButton.setScrollOnOverflow(false);
|
|
assertFalse(menuButton.isScrollOnOverflow());
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests that the attached menu has been set to aria-hidden=false explicitly
|
|
* when the menu is opened.
|
|
*/
|
|
function testSetOpenUnsetsAriaHidden() {
|
|
var node = goog.dom.getElement('demoMenuButton');
|
|
menuButton.decorate(node);
|
|
var menuElem = menuButton.getMenu().getElementStrict();
|
|
goog.a11y.aria.setState(menuElem, goog.a11y.aria.State.HIDDEN, true);
|
|
menuButton.setOpen(true);
|
|
assertEquals(
|
|
'', goog.a11y.aria.getState(menuElem, goog.a11y.aria.State.HIDDEN));
|
|
}
|