Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(717)

Side by Side Diff: chrome/test/data/webui/cr_elements/cr_shared_menu_tests.js

Issue 2356643003: Fix cr-shared-menu focus issues. (Closed)
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 });
OLDNEW
« no previous file with comments | « no previous file | ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698