| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** @fileoverview Suite of tests for cr-shared-menu. */ | 5 /** @fileoverview Suite of tests for cr-shared-menu. */ |
| 6 suite('cr-shared-menu', function() { | 6 suite('cr-shared-menu', function() { |
| 7 var menu; | 7 var menu; |
| 8 | 8 |
| 9 var button; | 9 var button; |
| 10 var button2; | 10 var button2; |
| 11 | 11 |
| 12 var item1; | 12 var items = []; |
| 13 var item2; | |
| 14 var item3; | |
| 15 | 13 |
| 16 function afterOpen(callback) { | 14 function afterOpen(callback) { |
| 17 menu.addEventListener('iron-overlay-opened', function f() { | 15 menu.addEventListener('iron-overlay-opened', function f() { |
| 18 menu.removeEventListener('iron-overlay-opened', f); | 16 menu.removeEventListener('iron-overlay-opened', f); |
| 19 callback(); | 17 callback(); |
| 20 }); | 18 }); |
| 21 } | 19 } |
| 22 | 20 |
| 23 function afterClose(callback) { | 21 function afterClose(callback) { |
| 24 menu.addEventListener('iron-overlay-closed', function f() { | 22 menu.addEventListener('iron-overlay-closed', function f() { |
| 25 menu.removeEventListener('iron-overlay-closed', f); | 23 menu.removeEventListener('iron-overlay-closed', f); |
| 26 callback(); | 24 callback(); |
| 27 }); | 25 }); |
| 28 } | 26 } |
| 29 | 27 |
| 30 suiteSetup(function() { | 28 suiteSetup(function() { |
| 31 return PolymerTest.importHtml( | 29 return PolymerTest.importHtml( |
| 32 'chrome://resources/polymer/v1_0/paper-item/paper-item.html'); | 30 'chrome://resources/polymer/v1_0/paper-item/paper-item.html'); |
| 33 }); | 31 }); |
| 34 | 32 |
| 35 setup(function() { | 33 setup(function() { |
| 36 PolymerTest.clearBody(); | 34 PolymerTest.clearBody(); |
| 37 // Basic wiring to set up a menu which opens when a button is pressed. | 35 // Basic wiring to set up an empty menu that opens when a button is pressed. |
| 38 menu = document.createElement('cr-shared-menu'); | 36 menu = document.createElement('cr-shared-menu'); |
| 39 menu.$$('#dropdown').noAnimations = true; | 37 menu.$$('#dropdown').noAnimations = true; |
| 40 | 38 |
| 41 item1 = document.createElement('paper-item'); | |
| 42 menu.appendChild(item1); | |
| 43 item2 = document.createElement('paper-item'); | |
| 44 menu.appendChild(item2); | |
| 45 item3 = document.createElement('paper-item'); | |
| 46 menu.appendChild(item3); | |
| 47 | |
| 48 button = document.createElement('button'); | 39 button = document.createElement('button'); |
| 49 button.addEventListener('tap', function(e) { | 40 button.addEventListener('tap', function(e) { |
| 50 menu.toggleMenu(button, {}); | 41 menu.toggleMenu(button, {}); |
| 51 e.stopPropagation(); // Prevent 'tap' from closing the dialog. | 42 e.stopPropagation(); // Prevent 'tap' from closing the dialog. |
| 52 }); | 43 }); |
| 53 | 44 |
| 54 button2 = document.createElement('button'); | 45 button2 = document.createElement('button'); |
| 55 button2.addEventListener('tap', function(e) { | 46 button2.addEventListener('tap', function(e) { |
| 56 menu.toggleMenu(button2, {}); | 47 menu.toggleMenu(button2, {}); |
| 57 e.stopPropagation(); // Prevent 'tap' from closing the dialog. | 48 e.stopPropagation(); // Prevent 'tap' from closing the dialog. |
| 58 }); | 49 }); |
| 59 | 50 |
| 60 document.body.appendChild(menu); | 51 document.body.appendChild(menu); |
| 61 document.body.appendChild(button); | 52 document.body.appendChild(button); |
| 62 document.body.appendChild(button2); | 53 document.body.appendChild(button2); |
| 63 }); | 54 }); |
| 64 | 55 |
| 65 test('opening and closing menu', function(done) { | 56 suite('basic', function() { |
| 66 MockInteractions.tap(button); | 57 setup(function() { |
| 67 assertTrue(menu.menuOpen); | 58 // Populate the menu with paper-items. |
| 59 for (var i = 0; i < 3; i++) { |
| 60 items[i] = document.createElement('paper-item'); |
| 61 menu.appendChild(items[i]); |
| 62 } |
| 63 }); |
| 68 | 64 |
| 69 afterOpen(function() { | 65 test('opening and closing menu', function(done) { |
| 70 // Using tap to close the menu requires that the iron-overlay-behavior | 66 MockInteractions.tap(button); |
| 71 // has finished initializing, which happens asynchronously between | 67 assertTrue(menu.menuOpen); |
| 72 // tapping the button and firing iron-overlay-opened. | |
| 73 MockInteractions.tap(document.body); | |
| 74 assertFalse(menu.menuOpen); | |
| 75 | 68 |
| 76 done(); | 69 afterOpen(function() { |
| 77 }); | 70 // Using tap to close the menu requires that the iron-overlay-behavior |
| 78 }); | 71 // has finished initializing, which happens asynchronously between |
| 72 // tapping the button and firing iron-overlay-opened. |
| 73 MockInteractions.tap(document.body); |
| 74 assertFalse(menu.menuOpen); |
| 79 | 75 |
| 80 test('open and close menu with escape', function(done) { | |
| 81 MockInteractions.tap(button); | |
| 82 assertTrue(menu.menuOpen); | |
| 83 afterOpen(function() { | |
| 84 // Pressing escape should close the menu. | |
| 85 MockInteractions.pressAndReleaseKeyOn(menu, 27); | |
| 86 assertFalse(menu.menuOpen); | |
| 87 done(); | |
| 88 }); | |
| 89 }); | |
| 90 | |
| 91 test('refocus button on close', function(done) { | |
| 92 button.focus(); | |
| 93 MockInteractions.tap(button); | |
| 94 | |
| 95 afterOpen(function() { | |
| 96 assertTrue(menu.menuOpen); | |
| 97 // Focus is applied asynchronously after the menu is opened. | |
| 98 assertEquals(item1, menu.shadowRoot.activeElement); | |
| 99 | |
| 100 menu.closeMenu(); | |
| 101 | |
| 102 afterClose(function() { | |
| 103 assertFalse(menu.menuOpen); | |
| 104 // Button should regain focus after closing the menu. | |
| 105 assertEquals(button, document.activeElement); | |
| 106 done(); | 76 done(); |
| 107 }); | 77 }); |
| 78 }); |
| 108 | 79 |
| 80 test('open and close menu with escape', function(done) { |
| 81 MockInteractions.tap(button); |
| 82 assertTrue(menu.menuOpen); |
| 83 afterOpen(function() { |
| 84 // Pressing escape should close the menu. |
| 85 MockInteractions.pressAndReleaseKeyOn(menu, 27); |
| 86 assertFalse(menu.menuOpen); |
| 87 done(); |
| 88 }); |
| 109 }); | 89 }); |
| 110 }); | |
| 111 | 90 |
| 112 test('refocus latest button on close', function(done) { | 91 test('refocus button on close', function(done) { |
| 113 // Regression test for crbug.com/628080. | 92 button.focus(); |
| 114 button.focus(); | 93 MockInteractions.tap(button); |
| 115 MockInteractions.tap(button); | |
| 116 | |
| 117 afterOpen(function() { | |
| 118 button2.focus(); | |
| 119 MockInteractions.tap(button2); | |
| 120 | 94 |
| 121 afterOpen(function() { | 95 afterOpen(function() { |
| 96 assertTrue(menu.menuOpen); |
| 97 // Focus is applied asynchronously after the menu is opened. |
| 98 assertEquals(items[0], menu.shadowRoot.activeElement); |
| 99 |
| 122 menu.closeMenu(); | 100 menu.closeMenu(); |
| 101 |
| 123 afterClose(function() { | 102 afterClose(function() { |
| 124 assertEquals(button2, document.activeElement); | 103 assertFalse(menu.menuOpen); |
| 104 // Button should regain focus after closing the menu. |
| 105 assertEquals(button, document.activeElement); |
| 106 done(); |
| 107 }); |
| 108 |
| 109 }); |
| 110 }); |
| 111 |
| 112 test('refocus latest button on close', function(done) { |
| 113 // Regression test for crbug.com/628080. |
| 114 button.focus(); |
| 115 MockInteractions.tap(button); |
| 116 |
| 117 afterOpen(function() { |
| 118 button2.focus(); |
| 119 MockInteractions.tap(button2); |
| 120 |
| 121 afterOpen(function() { |
| 122 menu.closeMenu(); |
| 123 afterClose(function() { |
| 124 assertEquals(button2, document.activeElement); |
| 125 done(); |
| 126 }); |
| 127 }); |
| 128 }); |
| 129 }); |
| 130 |
| 131 test('closeMenu does not refocus button when focus moves', function(done) { |
| 132 var input = document.createElement('input'); |
| 133 document.body.appendChild(input); |
| 134 |
| 135 button.focus(); |
| 136 MockInteractions.tap(button); |
| 137 |
| 138 afterOpen(function() { |
| 139 input.focus(); |
| 140 menu.closeMenu(); |
| 141 |
| 142 afterClose(function() { |
| 143 assertEquals(input, document.activeElement); |
| 125 done(); | 144 done(); |
| 126 }); | 145 }); |
| 127 }); | 146 }); |
| 128 }); | 147 }); |
| 129 }); | |
| 130 | 148 |
| 131 test('closeMenu does not refocus button when focus moves', function(done) { | 149 test('focus is trapped inside the menu', function(done) { |
| 132 var input = document.createElement('input'); | 150 button.focus(); |
| 133 document.body.appendChild(input); | 151 MockInteractions.tap(button); |
| 134 | 152 |
| 135 button.focus(); | 153 afterOpen(function() { |
| 136 MockInteractions.tap(button); | 154 // Simulate shift-tab on first element. |
| 155 assertEquals(items[0], menu.shadowRoot.activeElement); |
| 156 MockInteractions.pressAndReleaseKeyOn(items[0], 9, ['shift']); |
| 157 assertEquals(items[2], menu.shadowRoot.activeElement); |
| 137 | 158 |
| 138 afterOpen(function() { | 159 // Simulate tab on last element. |
| 139 input.focus(); | 160 MockInteractions.pressAndReleaseKeyOn(items[2], 9); |
| 140 menu.closeMenu(); | 161 assertEquals(items[0], menu.shadowRoot.activeElement); |
| 141 | 162 |
| 142 afterClose(function() { | 163 // Simulate shift-tab on first element. |
| 143 assertEquals(input, document.activeElement); | 164 assertEquals(items[0], menu.shadowRoot.activeElement); |
| 165 MockInteractions.pressAndReleaseKeyOn(items[0], 9, ['shift']); |
| 166 assertEquals(items[2], menu.shadowRoot.activeElement); |
| 167 |
| 168 // Simulate shift-tab on last element. This should simply cause the |
| 169 // browser to focus the previous item in the tab order, since |
| 170 // cr-shared-menu should not wrap in this case. However, we can't mimic |
| 171 // native events from JS, so the focus won't actually move unless |
| 172 // cr-shared--menu misbehaves. |
| 173 MockInteractions.pressAndReleaseKeyOn(items[2], 9, ['shift']); |
| 174 assertEquals(items[2], menu.shadowRoot.activeElement); |
| 175 |
| 144 done(); | 176 done(); |
| 145 }); | 177 }); |
| 146 }); | 178 }); |
| 147 }); | 179 }); |
| 148 | 180 |
| 149 test('focus is trapped inside the menu', function(done) { | 181 suite('different item types', function() { |
| 150 button.focus(); | 182 setup(function() { |
| 151 MockInteractions.tap(button); | 183 // Populate the menu with tabbable and untabbable items. |
| 184 items[0] = document.createElement('paper-item'); |
| 185 items[0].disabled = true; |
| 186 menu.appendChild(items[0]); |
| 152 | 187 |
| 153 afterOpen(function() { | 188 items[1] = document.createElement('paper-item'); |
| 154 // Simulate shift-tab on first element. | 189 menu.appendChild(items[1]); |
| 155 assertEquals(item1, menu.shadowRoot.activeElement); | |
| 156 MockInteractions.pressAndReleaseKeyOn(item1, 9, ['shift']); | |
| 157 assertEquals(item3, menu.shadowRoot.activeElement); | |
| 158 | 190 |
| 159 // Simulate tab on last element. | 191 items[2] = document.createElement('button'); |
| 160 MockInteractions.pressAndReleaseKeyOn(item3, 9); | 192 menu.appendChild(items[2]); |
| 161 assertEquals(item1, menu.shadowRoot.activeElement); | |
| 162 | 193 |
| 163 done(); | 194 items[3] = document.createElement('button'); |
| 195 items[3].disabled = true; |
| 196 |
| 197 items[4] = document.createElement('paper-item'); |
| 198 items[4].hidden = true; |
| 199 menu.appendChild(items[4]); |
| 200 }); |
| 201 |
| 202 test('focus does not start/end with untabbable elements', function(done) { |
| 203 button.focus(); |
| 204 MockInteractions.tap(button); |
| 205 |
| 206 afterOpen(function() { |
| 207 // The first item is disabled, so the second item is the first tabbable |
| 208 // item and should be focusced. |
| 209 assertEquals(items[1], menu.shadowRoot.activeElement); |
| 210 |
| 211 // The last two items are disabled or hidden, so they should be skipped |
| 212 // too. |
| 213 MockInteractions.pressAndReleaseKeyOn(items[1], 9, ['shift']); |
| 214 assertEquals(items[2], menu.shadowRoot.activeElement); |
| 215 |
| 216 // Simulate tab on last tabbable element to wrap to the first tabbable |
| 217 // element again. |
| 218 MockInteractions.pressAndReleaseKeyOn(items[2], 9); |
| 219 assertEquals(items[1], menu.shadowRoot.activeElement); |
| 220 |
| 221 done(); |
| 222 }); |
| 164 }); | 223 }); |
| 165 }); | 224 }); |
| 166 }); | 225 }); |
| OLD | NEW |