test_tree-view_01.html (12342B)
1 <!-- This Source Code Form is subject to the terms of the Mozilla Public 2 - License, v. 2.0. If a copy of the MPL was not distributed with this 3 - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> 4 <!DOCTYPE HTML> 5 <html> 6 <!-- 7 Test that TreeView component has working keyboard interactions. 8 --> 9 <head> 10 <meta charset="utf-8"> 11 <title>TreeView component keyboard test</title> 12 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 13 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> 14 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> 15 <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> 16 </head> 17 <body> 18 <pre id="test"> 19 <script src="head.js" type="application/javascript"></script> 20 <script type="application/javascript"> 21 22 "use strict"; 23 24 window.onload = function() { 25 try { 26 const { a, button, div } = 27 require("devtools/client/shared/vendor/react-dom-factories"); 28 const React = browserRequire("devtools/client/shared/vendor/react"); 29 const { 30 Simulate, 31 findRenderedDOMComponentWithClass, 32 findRenderedDOMComponentWithTag, 33 scryRenderedDOMComponentsWithClass, 34 } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); 35 const TreeView = ChromeUtils.importESModule( 36 "resource://devtools/client/shared/components/tree/TreeView.mjs", 37 { global: "current" } 38 ).default; 39 40 const _props = { 41 ...TEST_TREE_VIEW_INTERFACE, 42 renderValue: props => { 43 return (props.value === "C" ? 44 div({}, 45 props.value + " ", 46 a({ href: "#" }, "Focusable 1"), 47 button({ }, "Focusable 2")) : 48 props.value + "" 49 ); 50 }, 51 }; 52 const treeView = React.createElement(TreeView, _props); 53 const tree = ReactDOM.render(treeView, document.body); 54 const treeViewEl = findRenderedDOMComponentWithClass(tree, "treeTable"); 55 const rows = scryRenderedDOMComponentsWithClass(tree, "treeRow"); 56 const defaultFocus = treeViewEl.ownerDocument.body; 57 58 function blurEl(el) { 59 // Simulate.blur does not seem to update the activeElement. 60 el.blur(); 61 } 62 63 function focusEl(el) { 64 // Simulate.focus does not seem to update the activeElement. 65 el.focus(); 66 } 67 68 function getExpectedActiveElementForFinalShiftTab() { 69 // When tab focus mode is applied, the "Run Chrome Tests" button is not 70 // focusable, so this Shift+Tab moves the focus to a Chrome UI element 71 // instead of the "Run Chrome Tests" button, which makes the focus to 72 // move to a browsing context that has a different top level browsing context 73 // than the current browsing context. Since top level browsing contexts are 74 // different, the activeElement in the old document is not cleared. Also 75 // this is only the case when e10s is enabled. 76 if (SpecialPowers.getBoolPref("accessibility.tabfocus_applies_to_xul") && 77 SpecialPowers.Services.appinfo.browserTabsRemoteAutostart) { 78 return treeViewEl; 79 } 80 81 return treeViewEl.ownerDocument.body; 82 } 83 84 const tests = [{ 85 name: "Test default TreeView state. Keyboard focus is set to document " + 86 "body by default.", 87 state: { selected: null, active: null }, 88 activeElement: defaultFocus, 89 }, { 90 name: "Selected row must be set to the first row on initial focus. " + 91 "Keyboard focus should be set on TreeView's conatiner.", 92 action: () => { 93 focusEl(treeViewEl); 94 Simulate.click(rows[0]); 95 }, 96 activeElement: treeViewEl, 97 state: { selected: "/B" }, 98 }, { 99 name: "Selected row should remain set even when the treeView is " + 100 "blured. Keyboard focus should be set back to document body.", 101 action: () => blurEl(treeViewEl), 102 state: { selected: "/B" }, 103 activeElement: defaultFocus, 104 }, { 105 name: "Selected row must be re-set again to the first row on initial " + 106 "focus. Keyboard focus should be set on treeView's conatiner.", 107 action: () => focusEl(treeViewEl), 108 activeElement: treeViewEl, 109 state: { selected: "/B" }, 110 }, { 111 name: "Selected row should be updated to next on ArrowDown.", 112 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, 113 state: { selected: "/C" }, 114 }, { 115 name: "Selected row should be updated to last on ArrowDown.", 116 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, 117 state: { selected: "/D" }, 118 }, { 119 name: "Selected row should remain on last on ArrowDown.", 120 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, 121 state: { selected: "/D" }, 122 }, { 123 name: "Selected row should be updated to previous on ArrowUp.", 124 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, 125 state: { selected: "/C" }, 126 }, { 127 name: "Selected row should be updated to first on ArrowUp.", 128 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, 129 state: { selected: "/B" }, 130 }, { 131 name: "Selected row should remain on first on ArrowUp.", 132 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, 133 state: { selected: "/B" }, 134 }, { 135 name: "Selected row should move to the next matching row with first letter navigation.", 136 event: { type: "keyDown", el: treeViewEl, options: { key: "C" }}, 137 state: { selected: "/C" }, 138 }, { 139 name: "Selected row should not change when there are no more visible nodes matching first letter navigation.", 140 event: { type: "keyDown", el: treeViewEl, options: { key: "C" }}, 141 state: { selected: "/C" }, 142 }, { 143 name: "Selected row should be updated to last on End.", 144 event: { type: "keyDown", el: treeViewEl, options: { key: "End" }}, 145 state: { selected: "/D" }, 146 }, { 147 name: "Selected row should be updated to first on Home.", 148 event: { type: "keyDown", el: treeViewEl, options: { key: "Home" }}, 149 state: { selected: "/B" }, 150 }, { 151 name: "Selected row should be set as active on Enter.", 152 event: { type: "keyDown", el: treeViewEl, options: { key: "Enter" }}, 153 state: { selected: "/B", active: "/B" }, 154 activeElement: treeViewEl, 155 }, { 156 name: "Active row should be unset on Escape.", 157 event: { type: "keyDown", el: treeViewEl, options: { key: "Escape" }}, 158 state: { selected: "/B", active: null }, 159 }, { 160 name: "Selected row should be set as active on Space.", 161 event: { type: "keyDown", el: treeViewEl, options: { key: " " }}, 162 state: { selected: "/B", active: "/B" }, 163 activeElement: treeViewEl, 164 }, { 165 name: "Selected row should unset when focus leaves the treeView.", 166 action: () => blurEl(treeViewEl), 167 state: { selected: "/B", active: null }, 168 activeElement: defaultFocus, 169 }, { 170 name: "Keyboard focus should be set on treeView's conatiner on focus.", 171 action: () => focusEl(treeViewEl), 172 activeElement: treeViewEl, 173 }, { 174 name: "Selected row should be updated to next on ArrowDown.", 175 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, 176 state: { selected: "/C", active: null }, 177 }, { 178 name: "Selected row should be set as active on Enter. Keyboard focus " + 179 "should be set on the first focusable element inside the row, if " + 180 "available.", 181 event: { type: "keyDown", el: treeViewEl, options: { key: "Enter" }}, 182 state: { selected: "/C", active: "/C" }, 183 get activeElement() { 184 // When row becomes active/inactive, it is replaced with a newly 185 // rendered one. 186 return findRenderedDOMComponentWithTag(tree, "a"); 187 }, 188 }, { 189 name: "Keyboard focus should be set to next tabbable element inside " + 190 "the active row on Tab.", 191 action() { 192 synthesizeKey("KEY_Tab"); 193 }, 194 state: { selected: "/C", active: "/C" }, 195 get activeElement() { 196 // When row becomes active/inactive, it is replaced with a newly 197 // rendered one. 198 return findRenderedDOMComponentWithTag(tree, "button"); 199 }, 200 }, { 201 name: "Keyboard focus should wrap inside the row when focused on last " + 202 "tabbable element.", 203 action() { 204 synthesizeKey("KEY_Tab"); 205 }, 206 state: { selected: "/C", active: "/C" }, 207 get activeElement() { 208 return findRenderedDOMComponentWithTag(tree, "a"); 209 }, 210 }, { 211 name: "Keyboard focus should wrap inside the row when focused on first " + 212 "tabbable element.", 213 action() { 214 synthesizeKey("KEY_Tab", { shiftKey: true }); 215 }, 216 state: { selected: "/C", active: "/C" }, 217 get activeElement() { 218 return findRenderedDOMComponentWithTag(tree, "button"); 219 }, 220 }, { 221 name: "Active row should be unset on Escape. Focus should move back to " + 222 "the treeView container.", 223 event: { type: "keyDown", el: treeViewEl, options: { key: "Escape" }}, 224 state: { selected: "/C", active: null }, 225 activeElement: treeViewEl, 226 }, { 227 name: "Selected row should be set as active on Space. Keyboard focus " + 228 "should be set on the first focusable element inside the row, if " + 229 "available.", 230 event: { type: "keyDown", el: treeViewEl, options: { key: " " }}, 231 state: { selected: "/C", active: "/C" }, 232 get activeElement() { 233 // When row becomes active/inactive, it is replaced with a newly 234 // rendered one. 235 return findRenderedDOMComponentWithTag(tree, "a"); 236 }, 237 }, { 238 name: "Selected row should remain set even when the treeView is " + 239 "blured. Keyboard focus should be set back to document body.", 240 action: () => treeViewEl.ownerDocument.activeElement.blur(), 241 state: { selected: "/C", active: null }, 242 activeElement: defaultFocus, 243 }, { 244 name: "Keyboard focus should be set on treeView's conatiner on focus.", 245 action: () => focusEl(treeViewEl), 246 state: { selected: "/C", active: null }, 247 activeElement: treeViewEl, 248 }, { 249 name: "Selected row should be set as active on Space. Keyboard focus " + 250 "should be set on the first focusable element inside the row, if " + 251 "available.", 252 event: { type: "keyDown", el: treeViewEl, options: { key: " " }}, 253 state: { selected: "/C", active: "/C" }, 254 get activeElement() { 255 // When row becomes active/inactive, it is replaced with a newly 256 // rendered one. 257 return findRenderedDOMComponentWithTag(tree, "a"); 258 }, 259 }, { 260 name: "Selected row should be updated to previous on ArrowUp.", 261 event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, 262 state: { selected: "/B", active: null }, 263 activeElement: treeViewEl, 264 }, { 265 name: "Selected row should be set as active on Enter.", 266 event: { type: "keyDown", el: treeViewEl, options: { key: "Enter" }}, 267 state: { selected: "/B", active: "/B" }, 268 activeElement: treeViewEl, 269 }, { 270 name: "Keyboard focus should move to another focusable element outside " + 271 "of the treeView when there's nothing to focus on inside the row.", 272 action() { 273 synthesizeKey("KEY_Tab", { shiftKey: true }); 274 }, 275 state: { selected: "/B", active: null }, 276 activeElement: getExpectedActiveElementForFinalShiftTab(), 277 } 278 ]; 279 280 for (const test of tests) { 281 const { action, event, state, name } = test; 282 283 info(name); 284 if (event) { 285 const { type, options, el } = event; 286 Simulate[type](el, options); 287 } else if (action) { 288 action(); 289 } 290 291 if (test.activeElement) { 292 is(treeViewEl.ownerDocument.activeElement, test.activeElement, 293 "Focus is set correctly."); 294 } 295 296 for (const key in state) { 297 is(tree.state[key], state[key], `${key} state is correct.`); 298 } 299 } 300 } catch (e) { 301 ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); 302 } finally { 303 SimpleTest.finish(); 304 } 305 }; 306 </script> 307 </pre> 308 </body> 309 </html>