| 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 /** | 5 /** |
| 6 * @fileoverview Tests for cr-action-menu element. Runs as an interactive UI | 6 * @fileoverview Tests for cr-action-menu element. Runs as an interactive UI |
| 7 * test, since many of these tests check focus behavior. | 7 * test, since many of these tests check focus behavior. |
| 8 */ | 8 */ |
| 9 suite('CrActionMenu', function() { | 9 suite('CrActionMenu', function() { |
| 10 /** @type {?CrActionMenuElement} */ | 10 /** @type {?CrActionMenuElement} */ |
| 11 var menu = null; | 11 var menu = null; |
| 12 | 12 |
| 13 /** @type {?NodeList<HTMLElement>} */ | 13 /** @type {?NodeList<HTMLElement>} */ |
| 14 var items = null; | 14 var items = null; |
| 15 | 15 |
| 16 /** @type {HTMLElement} */ |
| 17 var dots = null; |
| 18 |
| 16 setup(function() { | 19 setup(function() { |
| 17 PolymerTest.clearBody(); | 20 PolymerTest.clearBody(); |
| 18 | 21 |
| 19 document.body.innerHTML = ` | 22 document.body.innerHTML = ` |
| 20 <button id="dots">...</button> | 23 <button id="dots">...</button> |
| 21 <dialog is="cr-action-menu"> | 24 <dialog is="cr-action-menu"> |
| 22 <button class="dropdown-item">Un</button> | 25 <button class="dropdown-item">Un</button> |
| 23 <hr> | 26 <hr> |
| 24 <button class="dropdown-item">Dos</button> | 27 <button class="dropdown-item">Dos</button> |
| 25 <button class="dropdown-item">Tres</button> | 28 <button class="dropdown-item">Tres</button> |
| 26 </dialog> | 29 </dialog> |
| 27 `; | 30 `; |
| 28 | 31 |
| 29 menu = document.querySelector('dialog[is=cr-action-menu]'); | 32 menu = document.querySelector('dialog[is=cr-action-menu]'); |
| 30 items = menu.querySelectorAll('.dropdown-item'); | 33 items = menu.querySelectorAll('.dropdown-item'); |
| 34 dots = document.querySelector('#dots'); |
| 31 assertEquals(3, items.length); | 35 assertEquals(3, items.length); |
| 32 }); | 36 }); |
| 33 | 37 |
| 34 teardown(function() { | 38 teardown(function() { |
| 39 document.body.style.direction = 'ltr'; |
| 40 |
| 35 if (menu.open) | 41 if (menu.open) |
| 36 menu.close(); | 42 menu.close(); |
| 37 }); | 43 }); |
| 38 | 44 |
| 39 function down() { | 45 function down() { |
| 40 MockInteractions.keyDownOn(menu, 'ArrowDown', [], 'ArrowDown'); | 46 MockInteractions.keyDownOn(menu, 'ArrowDown', [], 'ArrowDown'); |
| 41 } | 47 } |
| 42 | 48 |
| 43 function up() { | 49 function up() { |
| 44 MockInteractions.keyDownOn(menu, 'ArrowUp', [], 'ArrowUp'); | 50 MockInteractions.keyDownOn(menu, 'ArrowUp', [], 'ArrowUp'); |
| 45 } | 51 } |
| 46 | 52 |
| 47 test('hidden or disabled items', function() { | 53 test('hidden or disabled items', function() { |
| 48 menu.showAt(document.querySelector('#dots')); | 54 menu.showAt(dots); |
| 49 down(); | 55 down(); |
| 50 assertEquals(menu.root.activeElement, items[0]); | 56 assertEquals(menu.root.activeElement, items[0]); |
| 51 | 57 |
| 52 menu.close(); | 58 menu.close(); |
| 53 items[0].hidden = true; | 59 items[0].hidden = true; |
| 54 menu.showAt(document.querySelector('#dots')); | 60 menu.showAt(dots); |
| 55 down(); | 61 down(); |
| 56 assertEquals(menu.root.activeElement, items[1]); | 62 assertEquals(menu.root.activeElement, items[1]); |
| 57 | 63 |
| 58 menu.close(); | 64 menu.close(); |
| 59 items[1].disabled = true; | 65 items[1].disabled = true; |
| 60 menu.showAt(document.querySelector('#dots')); | 66 menu.showAt(dots); |
| 61 down(); | 67 down(); |
| 62 assertEquals(menu.root.activeElement, items[2]); | 68 assertEquals(menu.root.activeElement, items[2]); |
| 63 }); | 69 }); |
| 64 | 70 |
| 65 test('focus after down/up arrow', function() { | 71 test('focus after down/up arrow', function() { |
| 66 menu.showAt(document.querySelector('#dots')); | 72 menu.showAt(dots); |
| 67 | 73 |
| 68 // The menu should be focused when shown, but not on any of the items. | 74 // The menu should be focused when shown, but not on any of the items. |
| 69 assertEquals(menu, document.activeElement); | 75 assertEquals(menu, document.activeElement); |
| 70 assertNotEquals(items[0], menu.root.activeElement); | 76 assertNotEquals(items[0], menu.root.activeElement); |
| 71 assertNotEquals(items[1], menu.root.activeElement); | 77 assertNotEquals(items[1], menu.root.activeElement); |
| 72 assertNotEquals(items[2], menu.root.activeElement); | 78 assertNotEquals(items[2], menu.root.activeElement); |
| 73 | 79 |
| 74 down(); | 80 down(); |
| 75 assertEquals(items[0], menu.root.activeElement); | 81 assertEquals(items[0], menu.root.activeElement); |
| 76 down(); | 82 down(); |
| 77 assertEquals(items[1], menu.root.activeElement); | 83 assertEquals(items[1], menu.root.activeElement); |
| 78 down(); | 84 down(); |
| 79 assertEquals(items[2], menu.root.activeElement); | 85 assertEquals(items[2], menu.root.activeElement); |
| 80 down(); | 86 down(); |
| 81 assertEquals(items[0], menu.root.activeElement); | 87 assertEquals(items[0], menu.root.activeElement); |
| 82 up(); | 88 up(); |
| 83 assertEquals(items[2], menu.root.activeElement); | 89 assertEquals(items[2], menu.root.activeElement); |
| 84 up(); | 90 up(); |
| 85 assertEquals(items[1], menu.root.activeElement); | 91 assertEquals(items[1], menu.root.activeElement); |
| 86 up(); | 92 up(); |
| 87 assertEquals(items[0], menu.root.activeElement); | 93 assertEquals(items[0], menu.root.activeElement); |
| 88 up(); | 94 up(); |
| 89 assertEquals(items[2], menu.root.activeElement); | 95 assertEquals(items[2], menu.root.activeElement); |
| 90 | 96 |
| 91 items[1].disabled = true; | 97 items[1].disabled = true; |
| 92 up(); | 98 up(); |
| 93 assertEquals(items[0], menu.root.activeElement); | 99 assertEquals(items[0], menu.root.activeElement); |
| 94 }); | 100 }); |
| 95 | 101 |
| 96 test('pressing up arrow when no focus will focus last item', function(){ | 102 test('pressing up arrow when no focus will focus last item', function() { |
| 97 menu.showAt(document.querySelector('#dots')); | 103 menu.showAt(dots); |
| 98 assertEquals(menu, document.activeElement); | 104 assertEquals(menu, document.activeElement); |
| 99 | 105 |
| 100 up(); | 106 up(); |
| 101 assertEquals(items[items.length - 1], menu.root.activeElement); | 107 assertEquals(items[items.length - 1], menu.root.activeElement); |
| 102 }); | 108 }); |
| 103 | 109 |
| 104 test('can navigate to dynamically added items', function() { | 110 test('can navigate to dynamically added items', function() { |
| 105 // Can modify children after attached() and before showAt(). | 111 // Can modify children after attached() and before showAt(). |
| 106 var item = document.createElement('button'); | 112 var item = document.createElement('button'); |
| 107 item.classList.add('dropdown-item'); | 113 item.classList.add('dropdown-item'); |
| 108 menu.insertBefore(item, items[0]); | 114 menu.insertBefore(item, items[0]); |
| 109 menu.showAt(document.querySelector('#dots')); | 115 menu.showAt(dots); |
| 110 | 116 |
| 111 down(); | 117 down(); |
| 112 assertEquals(item, menu.root.activeElement); | 118 assertEquals(item, menu.root.activeElement); |
| 113 down(); | 119 down(); |
| 114 assertEquals(items[0], menu.root.activeElement); | 120 assertEquals(items[0], menu.root.activeElement); |
| 115 | 121 |
| 116 // Can modify children while menu is open. | 122 // Can modify children while menu is open. |
| 117 menu.removeChild(item); | 123 menu.removeChild(item); |
| 118 | 124 |
| 119 up(); | 125 up(); |
| 120 // Focus should have wrapped around to final item. | 126 // Focus should have wrapped around to final item. |
| 121 assertEquals(items[2], menu.root.activeElement); | 127 assertEquals(items[2], menu.root.activeElement); |
| 122 }); | 128 }); |
| 123 | 129 |
| 124 test('close on resize', function() { | 130 test('close on resize', function() { |
| 125 menu.showAt(document.querySelector('#dots')); | 131 menu.showAt(dots); |
| 126 assertTrue(menu.open); | 132 assertTrue(menu.open); |
| 127 | 133 |
| 128 window.dispatchEvent(new CustomEvent('resize')); | 134 window.dispatchEvent(new CustomEvent('resize')); |
| 129 assertFalse(menu.open); | 135 assertFalse(menu.open); |
| 130 }); | 136 }); |
| 131 | 137 |
| 132 test('close on popstate', function() { | 138 test('close on popstate', function() { |
| 133 menu.showAt(document.querySelector('#dots')); | 139 menu.showAt(dots); |
| 134 assertTrue(menu.open); | 140 assertTrue(menu.open); |
| 135 | 141 |
| 136 window.dispatchEvent(new CustomEvent('popstate')); | 142 window.dispatchEvent(new CustomEvent('popstate')); |
| 137 assertFalse(menu.open); | 143 assertFalse(menu.open); |
| 138 }); | 144 }); |
| 139 | 145 |
| 140 /** @param {string} key The key to use for closing. */ | 146 /** @param {string} key The key to use for closing. */ |
| 141 function testFocusAfterClosing(key) { | 147 function testFocusAfterClosing(key) { |
| 142 return new Promise(function(resolve) { | 148 return new Promise(function(resolve) { |
| 143 var dots = document.querySelector('#dots'); | |
| 144 menu.showAt(dots); | 149 menu.showAt(dots); |
| 145 assertTrue(menu.open); | 150 assertTrue(menu.open); |
| 146 | 151 |
| 147 // Check that focus returns to the anchor element. | 152 // Check that focus returns to the anchor element. |
| 148 dots.addEventListener('focus', resolve); | 153 dots.addEventListener('focus', resolve); |
| 149 MockInteractions.keyDownOn(menu, key, [], key); | 154 MockInteractions.keyDownOn(menu, key, [], key); |
| 150 assertFalse(menu.open); | 155 assertFalse(menu.open); |
| 151 }); | 156 }); |
| 152 } | 157 } |
| 153 | 158 |
| 154 test('close on Tab', function() { return testFocusAfterClosing('Tab'); }); | 159 test('close on Tab', function() { |
| 160 return testFocusAfterClosing('Tab'); |
| 161 }); |
| 155 test('close on Escape', function() { | 162 test('close on Escape', function() { |
| 156 return testFocusAfterClosing('Escape'); | 163 return testFocusAfterClosing('Escape'); |
| 157 }); | 164 }); |
| 158 | 165 |
| 159 test('mouse movement focus options', function() { | 166 test('mouse movement focus options', function() { |
| 160 function makeMouseoverEvent(node) { | 167 function makeMouseoverEvent(node) { |
| 161 var e = new MouseEvent('mouseover', {bubbles: true}); | 168 var e = new MouseEvent('mouseover', {bubbles: true}); |
| 162 node.dispatchEvent(e); | 169 node.dispatchEvent(e); |
| 163 } | 170 } |
| 164 | 171 |
| 165 menu.showAt(document.querySelector('#dots')); | 172 menu.showAt(dots); |
| 166 | 173 |
| 167 // Moving mouse on option 1 should focus it. | 174 // Moving mouse on option 1 should focus it. |
| 168 assertNotEquals(items[0], menu.root.activeElement); | 175 assertNotEquals(items[0], menu.root.activeElement); |
| 169 makeMouseoverEvent(items[0]); | 176 makeMouseoverEvent(items[0]); |
| 170 assertEquals(items[0], menu.root.activeElement); | 177 assertEquals(items[0], menu.root.activeElement); |
| 171 | 178 |
| 172 // Moving mouse on the menu (not on option) should focus the menu. | 179 // Moving mouse on the menu (not on option) should focus the menu. |
| 173 makeMouseoverEvent(menu); | 180 makeMouseoverEvent(menu); |
| 174 assertNotEquals(items[0], menu.root.activeElement); | 181 assertNotEquals(items[0], menu.root.activeElement); |
| 175 assertEquals(menu, document.activeElement); | 182 assertEquals(menu, document.activeElement); |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 252 menu.close(); | 259 menu.close(); |
| 253 | 260 |
| 254 // If the viewport can't fit the menu, align the menu to the viewport. | 261 // If the viewport can't fit the menu, align the menu to the viewport. |
| 255 menu.showAtPosition({ | 262 menu.showAtPosition({ |
| 256 left: menuWidth - 5, | 263 left: menuWidth - 5, |
| 257 top: 0, | 264 top: 0, |
| 258 width: 0, | 265 width: 0, |
| 259 height: 0, | 266 height: 0, |
| 260 maxX: menuWidth * 2 - 10, | 267 maxX: menuWidth * 2 - 10, |
| 261 }); | 268 }); |
| 262 assertEquals(`${menuWidth - 10}px`, menu.style.left); | 269 assertEquals(`${menuWidth - 10}px`, menu.style.left); |
| 263 assertEquals(`0px`, menu.style.top); | 270 assertEquals(`0px`, menu.style.top); |
| 264 menu.close(); | 271 menu.close(); |
| 265 | 272 |
| 266 // Alignment is reversed in RTL. | 273 // Alignment is reversed in RTL. |
| 267 document.body.style.direction = 'rtl'; | 274 document.body.style.direction = 'rtl'; |
| 268 menu.showAtPosition(config); | 275 menu.showAtPosition(config); |
| 269 assertTrue(menu.open); | 276 assertTrue(menu.open); |
| 270 assertEquals(140 - menuWidth, menu.offsetLeft); | 277 assertEquals(140 - menuWidth, menu.offsetLeft); |
| 271 assertEquals('250px', menu.style.top); | 278 assertEquals('250px', menu.style.top); |
| 272 menu.close(); | 279 menu.close(); |
| 273 document.body.style.direction = 'ltr'; | |
| 274 }); | 280 }); |
| 275 | 281 |
| 276 test('offscreen scroll positioning', function() { | 282 suite('offscreen scroll positioning', function() { |
| 277 var bodyHeight = 10000; | 283 var bodyHeight = 10000; |
| 278 var bodyWidth = 20000; | 284 var bodyWidth = 20000; |
| 279 var containerLeft = 5000; | 285 var containerLeft = 5000; |
| 280 var containerTop = 10000; | 286 var containerTop = 10000; |
| 281 var containerWidth = 500; | 287 var containerWidth = 500; |
| 282 var containerHeight = 500; | 288 var containerHeight = 500; |
| 283 document.body.innerHTML = ` | 289 var menuWidth = 100; |
| 284 <style> | 290 var menuHeight = 200; |
| 285 body { | |
| 286 height: ${bodyHeight}px; | |
| 287 width: ${bodyWidth}px; | |
| 288 } | |
| 289 | 291 |
| 290 #container { | 292 setup(function() { |
| 291 overflow: auto; | 293 document.body.scrollTop = 0; |
| 292 position: absolute; | 294 document.body.scrollLeft = 0; |
| 293 top: ${containerTop}px; | 295 document.body.innerHTML = ` |
| 294 left: ${containerLeft}px; | 296 <style> |
| 295 height: ${containerHeight}px; | 297 body { |
| 296 width: ${containerWidth}px; | 298 height: ${bodyHeight}px; |
| 297 } | 299 width: ${bodyWidth}px; |
| 300 } |
| 298 | 301 |
| 299 #inner-container { | 302 #container { |
| 300 height: 1000px; | 303 overflow: auto; |
| 301 width: 1000px; | 304 position: absolute; |
| 302 } | 305 top: ${containerTop}px; |
| 303 </style> | 306 left: ${containerLeft}px; |
| 304 <div id="container"> | 307 right: ${containerLeft}px; |
| 305 <div id="inner-container"> | 308 height: ${containerHeight}px; |
| 306 <button id="dots">...</button> | 309 width: ${containerWidth}px; |
| 307 <dialog is="cr-action-menu"> | 310 } |
| 308 <button class="dropdown-item">Un</button> | 311 |
| 309 <hr> | 312 #inner-container { |
| 310 <button class="dropdown-item">Dos</button> | 313 height: 1000px; |
| 311 <button class="dropdown-item">Tres</button> | 314 width: 1000px; |
| 312 </dialog> | 315 } |
| 316 |
| 317 dialog { |
| 318 height: ${menuHeight}; |
| 319 width: ${menuWidth}; |
| 320 padding: 0; |
| 321 } |
| 322 </style> |
| 323 <div id="container"> |
| 324 <div id="inner-container"> |
| 325 <button id="dots">...</button> |
| 326 <dialog is="cr-action-menu"> |
| 327 <button class="dropdown-item">Un</button> |
| 328 <hr> |
| 329 <button class="dropdown-item">Dos</button> |
| 330 <button class="dropdown-item">Tres</button> |
| 331 </dialog> |
| 332 </div> |
| 313 </div> | 333 </div> |
| 314 </div> | 334 `; |
| 315 `; | 335 menu = document.querySelector('dialog[is=cr-action-menu]'); |
| 316 menu = document.querySelector('dialog[is=cr-action-menu]'); | 336 dots = document.querySelector('#dots'); |
| 317 var dots = document.querySelector('#dots'); | 337 }) |
| 318 | 338 |
| 319 // Show the menu, scrolling the body to the button. | 339 // Show the menu, scrolling the body to the button. |
| 320 menu.showAt( | 340 test('simple offscreen', function() { |
| 321 dots, | 341 menu.showAt(dots, {anchorAlignmentX: AnchorAlignment.AFTER_START}); |
| 322 {anchorAlignmentX: AnchorAlignment.AFTER_START}); | 342 assertEquals(`${containerLeft}px`, menu.style.left); |
| 323 assertEquals(`${containerLeft}px`, menu.style.left); | 343 assertEquals(`${containerTop}px`, menu.style.top); |
| 324 assertEquals(`${containerTop}px`, menu.style.top); | 344 menu.close(); |
| 325 menu.close(); | 345 }); |
| 326 | 346 |
| 327 // Show the menu, scrolling the container to the button, and the body to the | 347 // Show the menu, scrolling the container to the button, and the body to the |
| 328 // button. | 348 // button. |
| 329 document.body.scrollLeft = bodyWidth; | 349 test('offscreen and out of scroll container viewport', function() { |
| 330 document.body.scrollTop = bodyHeight; | 350 document.body.scrollLeft = bodyWidth; |
| 351 document.body.scrollTop = bodyHeight; |
| 352 var container = document.querySelector('#container'); |
| 331 | 353 |
| 332 document.querySelector('#container').scrollLeft = containerLeft; | 354 container.scrollLeft = containerLeft; |
| 333 document.querySelector('#container').scrollTop = containerTop; | 355 container.scrollTop = containerTop; |
| 334 | 356 |
| 335 menu.showAt(dots, {anchorAlignmentX: AnchorAlignment.AFTER_START}); | 357 menu.showAt(dots, {anchorAlignmentX: AnchorAlignment.AFTER_START}); |
| 336 assertEquals(`${containerLeft}px`, menu.style.left); | 358 assertEquals(`${containerLeft}px`, menu.style.left); |
| 337 assertEquals(`${containerTop}px`, menu.style.top); | 359 assertEquals(`${containerTop}px`, menu.style.top); |
| 338 menu.close(); | 360 menu.close(); |
| 361 }); |
| 339 | 362 |
| 340 // Show the menu for an already onscreen button. The anchor should be | 363 // Show the menu for an already onscreen button. The anchor should be |
| 341 // overridden so that no scrolling happens. | 364 // overridden so that no scrolling happens. |
| 342 document.body.scrollLeft = 0; | 365 test('onscreen forces anchor change', function() { |
| 343 document.body.scrollTop = 0; | 366 var rect = dots.getBoundingClientRect(); |
| 367 document.body.scrollLeft = rect.right - document.body.clientWidth + 10; |
| 368 document.body.scrollTop = rect.bottom - document.body.clientHeight + 10; |
| 344 | 369 |
| 345 var rect = dots.getBoundingClientRect(); | 370 menu.showAt(dots, {anchorAlignmentX: AnchorAlignment.AFTER_START}); |
| 346 document.body.scrollLeft = rect.right - document.body.clientWidth; | 371 var buttonWidth = dots.offsetWidth; |
| 347 document.body.scrollTop = rect.bottom - document.body.clientHeight; | 372 var buttonHeight = dots.offsetHeight; |
| 373 assertEquals(containerLeft - menuWidth + buttonWidth, menu.offsetLeft); |
| 374 assertEquals(containerTop - menuHeight + buttonHeight, menu.offsetTop); |
| 375 menu.close(); |
| 376 }); |
| 348 | 377 |
| 349 menu.showAt(dots, {anchorAlignmentX: AnchorAlignment.AFTER_START}); | 378 test('scroll position maintained for showAtPosition', function() { |
| 350 var menuWidth = menu.offsetWidth; | 379 document.body.scrollLeft = 500; |
| 351 var menuHeight = menu.offsetHeight; | 380 document.body.scrollTop = 1000; |
| 352 var buttonWidth = dots.offsetWidth; | 381 menu.showAtPosition({top: 50, left: 50}); |
| 353 var buttonHeight = dots.offsetHeight; | 382 assertEquals(550, menu.offsetLeft); |
| 354 assertEquals(containerLeft - menuWidth + buttonWidth, menu.offsetLeft); | 383 assertEquals(1050, menu.offsetTop); |
| 355 assertEquals(containerTop - menuHeight + buttonHeight, menu.offsetTop); | 384 menu.close(); |
| 385 }); |
| 386 |
| 387 test('rtl', function() { |
| 388 // Anchor to an item in RTL. |
| 389 document.body.style.direction = 'rtl'; |
| 390 menu.showAt(dots, {anchorAlignmentX: AnchorAlignment.AFTER_START}); |
| 391 assertEquals( |
| 392 container.offsetLeft + containerWidth - menuWidth, |
| 393 menu.offsetLeft); |
| 394 assertEquals(containerTop, menu.offsetTop); |
| 395 menu.close(); |
| 396 }); |
| 356 }); | 397 }); |
| 357 }); | 398 }); |
| OLD | NEW |