| <!doctype html> |
| <!-- |
| @license |
| Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
| Code distributed by Google as part of the polymer project is also |
| subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
| --> |
| <html> |
| <head> |
| |
| <title>iron-menu-behavior tests</title> |
| |
| <meta charset="utf-8"> |
| <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
| <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes"> |
| |
| |
| <script src="../../webcomponentsjs/webcomponents-lite.js"></script> |
| <script src="../../web-component-tester/browser.js"></script> |
| <script src="../../iron-test-helpers/mock-interactions.js"></script> |
| <link rel="import" href="test-menu.html"> |
| <link rel="import" href="test-nested-menu.html"> |
| |
| <style> |
| .ghost, [hidden] { |
| display: none; |
| } |
| .invisible { |
| visibility: hidden; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <test-fixture id="basic"> |
| <template> |
| <test-menu> |
| <div>item 1</div> |
| <div>item 2</div> |
| <div>item 3</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="single-item"> |
| <template> |
| <test-menu> |
| <div>item 1</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="disabled"> |
| <template> |
| <test-menu> |
| <div>a item 1</div> |
| <div disabled>b item 2</div> |
| <div>b item 3</div> |
| <div disabled>c item 4</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="invisible"> |
| <template> |
| <test-menu> |
| <div>item 1</div> |
| <div hidden>item 2</div> |
| <div class="ghost">item 3</div> |
| <div class="invisible">item 3.1</div> |
| <div>item 4</div> |
| <div hidden>item 5</div> |
| <div class="ghost">item 6</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="nested-invisible"> |
| <template> |
| <test-nested-menu></test-nested-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="only-disabled"> |
| <template> |
| <test-menu> |
| <div disabled>disabled item</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="multi"> |
| <template> |
| <test-menu multi> |
| <div>item 1</div> |
| <div>item 2</div> |
| <div>item 3</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="nested"> |
| <template> |
| <test-menu> |
| <test-menu> |
| <div>item 1</div> |
| <div>item 2</div> |
| <div>item 3</div> |
| </test-menu> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="empty"> |
| <template> |
| <test-menu></test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="disabled-group-and-options"> |
| <template> |
| <test-menu disabled> |
| <div disabled>one</div> |
| <div disabled>two</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="nonzero-tabindex"> |
| <template> |
| <test-menu tabindex="32"> |
| <div>One</div> |
| <div>Two</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="countries"> |
| <template> |
| <test-menu> |
| <div>Ghana</div> |
| <div>Uganda</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <test-fixture id="bogus-attr-for-item-title"> |
| <template> |
| <test-menu attr-for-item-title="totally-doesnt-exist"> |
| <div>Focused by default</div> |
| <div>I'm not entitled!</div> |
| </test-menu> |
| </template> |
| </test-fixture> |
| |
| <script> |
| suite('menu a11y tests', function() { |
| test('menu has role="menu"', function() { |
| var menu = fixture('basic'); |
| assert.equal(menu.getAttribute('role'), 'menu', 'has role="menu"'); |
| }); |
| |
| test('first item gets focus when menu is focused', function(done) { |
| var menu = fixture('basic'); |
| MockInteractions.focus(menu); |
| Polymer.Base.async(function() { |
| var ownerRoot = Polymer.dom(menu.firstElementChild).getOwnerRoot() || document; |
| var activeElement = Polymer.dom(ownerRoot).activeElement; |
| assert.equal(activeElement, menu.firstElementChild, 'menu.firstElementChild is focused'); |
| done(); |
| }); |
| }); |
| |
| test('first item gets focus when menu is focused in a single item menu', function(done) { |
| var menu = fixture('single-item'); |
| MockInteractions.focus(menu); |
| Polymer.Base.async(function() { |
| var ownerRoot = Polymer.dom(menu.firstElementChild).getOwnerRoot() || document; |
| var activeElement = Polymer.dom(ownerRoot).activeElement; |
| assert.equal(activeElement, menu.firstElementChild, 'menu.firstElementChild is focused'); |
| done(); |
| }); |
| }); |
| |
| test('selected item gets focus when menu is focused', function(done) { |
| var menu = fixture('basic'); |
| menu.selected = 1; |
| MockInteractions.focus(menu); |
| Polymer.Base.async(function() { |
| var ownerRoot = Polymer.dom(menu.selectedItem).getOwnerRoot() || document; |
| var activeElement = Polymer.dom(ownerRoot).activeElement; |
| assert.equal(activeElement, menu.selectedItem, 'menu.selectedItem is focused'); |
| done(); |
| }); |
| }); |
| |
| test('focusing on next item skips disabled items', function(done) { |
| var menu = fixture('disabled'); |
| MockInteractions.focus(menu); |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press down |
| MockInteractions.keyDownOn(menu, 40); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[2], 'menu.items[2] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on next item skips invisible items', function(done) { |
| var menu = fixture('invisible'); |
| |
| MockInteractions.focus(menu); |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press down |
| MockInteractions.keyDownOn(menu, 40); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[4], 'menu.items[4] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on next item skips nested invisible items', function(done) { |
| var nestedMenu = fixture('nested-invisible'); |
| var menu = nestedMenu.$.actualMenu; |
| |
| MockInteractions.focus(menu); |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press down |
| MockInteractions.keyDownOn(menu, 40); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[4], 'menu.items[4] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on next item in empty menu', function(done) { |
| var menu = fixture('empty'); |
| MockInteractions.focus(menu); |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press down |
| MockInteractions.keyDownOn(menu, 40); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, undefined, 'no focused item'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on next item in all disabled menu', function(done) { |
| var menu = fixture('only-disabled'); |
| MockInteractions.focus(menu); |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press down |
| MockInteractions.keyDownOn(menu, 40); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, undefined, 'no focused item'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on previous item skips disabled items', function(done) { |
| var menu = fixture('disabled'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press up |
| MockInteractions.keyDownOn(menu, 38); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[2], 'menu.items[2] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on previous item skips invisible items', function(done) { |
| var menu = fixture('invisible'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press up |
| MockInteractions.keyDownOn(menu, 38); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[4], 'menu.items[4] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on previous item skips nested invisible items', function(done) { |
| var nestedMenu = fixture('nested-invisible'); |
| var menu = nestedMenu.$.actualMenu; |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press up |
| MockInteractions.keyDownOn(menu, 38); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[4], 'menu.items[4] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on previous item in empty menu', function(done) { |
| var menu = fixture('empty'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press up |
| MockInteractions.keyDownOn(menu, 38); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, undefined, 'no focused item'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on previous item in all disabled menu', function(done) { |
| var menu = fixture('only-disabled'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press up |
| MockInteractions.keyDownOn(menu, 38); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, undefined, 'no focused item'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on item using key press skips disabled items', function(done) { |
| var menu = fixture('disabled'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press 'b' |
| MockInteractions.keyDownOn(menu, 66); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[2], 'menu.items[2] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on item using key press ignores disabled items', function(done) { |
| var menu = fixture('disabled'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press 'c' |
| MockInteractions.keyDownOn(menu, 67); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[0], 'menu.items[0] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on item using key press in all disabled items', function(done) { |
| var menu = fixture('only-disabled'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press 'c' |
| MockInteractions.keyDownOn(menu, 67); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, undefined, 'no focused item'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on item with multi-char, quick input', function(done) { |
| var menu = fixture('countries'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press 'u' |
| MockInteractions.keyDownOn(menu, 85); |
| |
| // Key press 'g' |
| MockInteractions.keyDownOn(menu, 71); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[1], 'menu.items[1] is focused'); |
| done(); |
| }); |
| }); |
| }); |
| |
| test('focusing on item with bogus attr-for-item-title', function(done) { |
| var menu = fixture('bogus-attr-for-item-title'); |
| MockInteractions.focus(menu); |
| |
| // Wait for async focus |
| Polymer.Base.async(function() { |
| // Key press 'i' |
| MockInteractions.keyDownOn(menu, 73); |
| |
| Polymer.Base.async(function() { |
| var focusedItem = Polymer.dom(menu).node.focusedItem; |
| assert.equal(focusedItem, menu.items[0], 'menu.items[0] is still focused'); |
| done(); |
| }); |
| }); |
| |
| }); |
| |
| test('focusing non-item content does not auto-focus an item', function(done) { |
| var menu = fixture('basic'); |
| menu.extraContent.focus(); |
| Polymer.Base.async(function() { |
| var menuOwnerRoot = Polymer.dom(menu.extraContent).getOwnerRoot() || document; |
| var menuActiveElement = Polymer.dom(menuOwnerRoot).activeElement; |
| assert.equal(menuActiveElement, menu.extraContent, 'menu.extraContent is focused'); |
| assert.equal(Polymer.dom(document).activeElement, menu, 'menu is document.activeElement'); |
| done(); |
| }); |
| }); |
| |
| test('last activated item in a multi select menu is focused', function(done) { |
| var menu = fixture('multi'); |
| menu.selected = 0; |
| menu.items[1].click(); |
| Polymer.Base.async(function() { |
| var ownerRoot = Polymer.dom(menu.items[1]).getOwnerRoot() || document; |
| var activeElement = Polymer.dom(ownerRoot).activeElement; |
| assert.equal(activeElement, menu.items[1], 'menu.items[1] is focused'); |
| done(); |
| }); |
| }); |
| |
| test('deselection in a multi select menu focuses deselected item', function(done) { |
| var menu = fixture('multi'); |
| menu.selected = 0; |
| menu.items[0].click(); |
| Polymer.Base.async(function() { |
| var ownerRoot = Polymer.dom(menu.items[0]).getOwnerRoot() || document; |
| var activeElement = Polymer.dom(ownerRoot).activeElement; |
| assert.equal(activeElement, menu.items[0], 'menu.items[0] is focused'); |
| done(); |
| }); |
| }); |
| |
| test('keyboard events should not bubble', function(done) { |
| var menu = fixture('nested'); |
| var keyCounter = 0; |
| |
| menu.addEventListener('keydown', function(event) { |
| if (menu.keyboardEventMatchesKeys(event, 'esc')) { |
| keyCounter++; |
| } |
| if (menu.keyboardEventMatchesKeys(event, 'up')) { |
| keyCounter++; |
| } |
| if (menu.keyboardEventMatchesKeys(event, 'down')) { |
| keyCounter++; |
| } |
| }); |
| |
| // up |
| MockInteractions.keyDownOn(menu.firstElementChild, 38); |
| // down |
| MockInteractions.keyDownOn(menu.firstElementChild, 40); |
| // esc |
| MockInteractions.keyDownOn(menu.firstElementChild, 27); |
| |
| Polymer.Base.async(function() { |
| assert.equal(menu.firstElementChild.tagName, 'TEST-MENU'); |
| assert.equal(keyCounter, 0); |
| done(); |
| }); |
| }); |
| |
| test('empty menus don\'t unfocus themselves', function(done) { |
| var menu = fixture('empty'); |
| |
| menu.focus(); |
| Polymer.Base.async(function() { |
| assert.equal(Polymer.dom(document).activeElement, menu); |
| done(); |
| }); |
| }); |
| |
| test('A disabled menu should not be focusable', function(done) { |
| var menu = fixture('disabled-group-and-options'); |
| menu.focus(); |
| Polymer.Base.async(function() { |
| assert.notEqual(Polymer.dom(document).activeElement, menu); |
| assert.notEqual(Polymer.dom(document).activeElement, menu.items[0]); |
| assert.notEqual(Polymer.dom(document).activeElement, menu.items[1]); |
| done(); |
| }); |
| }); |
| |
| test('A disabled menu will not have a tab index.', function() { |
| var menu = fixture('countries'); |
| assert.equal(menu.getAttribute('tabindex'), '0'); |
| menu.disabled = true; |
| assert.equal(menu.getAttribute('tabindex'), null); |
| menu.disabled = false; |
| assert.equal(menu.getAttribute('tabindex'), '0'); |
| }); |
| |
| test('Updated tab index of disabled element should remain.', function() { |
| var menu = fixture('countries'); |
| assert.equal(menu.getAttribute('tabindex'), '0'); |
| menu.disabled = true; |
| assert.equal(menu.getAttribute('tabindex'), null); |
| menu.setAttribute('tabindex', 15); |
| assert.equal(menu.getAttribute('tabindex'), '15'); |
| menu.disabled = false; |
| assert.equal(menu.getAttribute('tabindex'), '15'); |
| }); |
| |
| test('A disabled menu will regain its non-zero tab index when re-enabled.', function() { |
| var menu = fixture('nonzero-tabindex'); |
| assert.equal(menu.getAttribute('tabindex'), '32'); |
| menu.disabled = true; |
| assert.equal(menu.getAttribute('tabindex'), null); |
| menu.disabled = false; |
| assert.equal(menu.getAttribute('tabindex'), '32'); |
| }); |
| |
| test('`tabIndex` properties of all items are updated when items change', function(done) { |
| var menu = fixture('basic'); |
| |
| function assertTabIndexCounts(nodes, expected) { |
| var tabIndexCounts = {}; |
| for (var i = 0; i < nodes.length; i++) { |
| var tabIndex = nodes[i].tabIndex; |
| if (tabIndexCounts[tabIndex]) { |
| tabIndexCounts[tabIndex]++; |
| } else { |
| tabIndexCounts[tabIndex] = 1; |
| } |
| } |
| |
| assert.equal(Object.keys(tabIndexCounts).length, Object.keys(expected).length); |
| Object.keys(expected).forEach(function(key) { |
| assert.equal(tabIndexCounts[key], expected[key]); |
| }); |
| } |
| |
| function divWithTabIndex(tabIndex) { |
| var div = document.createElement('div'); |
| div.tabIndex = tabIndex; |
| return div; |
| } |
| |
| // Only the selected item will have tabIndex 0. |
| menu.select(0); |
| assertTabIndexCounts(menu.items, {"-1": 2, "0": 1}); |
| |
| Polymer.dom(menu).appendChild(divWithTabIndex(1)); |
| Polymer.dom(menu).appendChild(divWithTabIndex(2)); |
| Polymer.dom(menu).appendChild(divWithTabIndex(3)); |
| |
| // Async wait for `observeNodes`. |
| Polymer.Base.async(function() { |
| assertTabIndexCounts(menu.items, {"-1": 5, "0": 1}); |
| done(); |
| }); |
| }); |
| }); |
| </script> |
| </body> |
| </html> |