browser_treeWidget_keyboard_interaction.js (8532B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Tests that keyboard interaction works fine with the tree widget 7 8 const TEST_URI = 9 "data:text/html;charset=utf-8,<head>" + 10 "<link rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" + 11 "ets.css'></head><body><div></div><span></span></body>"; 12 const { 13 TreeWidget, 14 } = require("resource://devtools/client/shared/widgets/TreeWidget.js"); 15 16 add_task(async function () { 17 await SpecialPowers.pushPrefEnv({ 18 set: [["security.allow_unsafe_parent_loads", true]], 19 }); 20 21 await addTab("about:blank"); 22 const { host, win, doc } = await createHost("bottom", TEST_URI); 23 24 // Creating a host is not correctly waiting when DevTools run in content frame 25 // See Bug 1571421. 26 await wait(1000); 27 28 const tree = new TreeWidget(doc.querySelector("div"), { 29 defaultType: "store", 30 }); 31 32 populateTree(tree, doc); 33 34 await testKeyboardInteraction(tree, win); 35 36 tree.destroy(); 37 host.destroy(); 38 gBrowser.removeCurrentTab(); 39 }); 40 41 function populateTree(tree, doc) { 42 tree.add([ 43 { 44 id: "level1", 45 label: "Level 1", 46 }, 47 { 48 id: "level2-1", 49 label: "Level 2", 50 }, 51 { 52 id: "level3-1", 53 label: "Level 3 - Child 1", 54 type: "dir", 55 }, 56 ]); 57 tree.add([ 58 "level1", 59 "level2-1", 60 { id: "level3-2", label: "Level 3 - Child 2" }, 61 ]); 62 tree.add([ 63 "level1", 64 "level2-1", 65 { id: "level3-3", label: "Level 3 - Child 3" }, 66 ]); 67 tree.add([ 68 "level1", 69 { 70 id: "level2-2", 71 label: "Level 2.1", 72 }, 73 { 74 id: "level3-1", 75 label: "Level 3.1", 76 }, 77 ]); 78 tree.add([ 79 { 80 id: "level1", 81 label: "Level 1", 82 }, 83 { 84 id: "level2", 85 label: "Level 2", 86 }, 87 { 88 id: "level3", 89 label: "Level 3", 90 type: "js", 91 }, 92 ]); 93 tree.add(["level1.1", "level2", { id: "level3", type: "url" }]); 94 95 // Adding a new non text item in the tree. 96 const node = doc.createElement("div"); 97 node.textContent = "Foo Bar"; 98 node.className = "foo bar"; 99 tree.add([ 100 { 101 id: "level1.2", 102 node, 103 attachment: { 104 foo: "bar", 105 }, 106 }, 107 ]); 108 } 109 110 // Sends a click event on the passed DOM node in an async manner 111 function click(node) { 112 const win = node.ownerDocument.defaultView; 113 executeSoon(() => EventUtils.synthesizeMouseAtCenter(node, {}, win)); 114 } 115 116 /** 117 * Tests if pressing navigation keys on the tree items does the expected behavior 118 */ 119 async function testKeyboardInteraction(tree, win) { 120 info("Testing keyboard interaction with the tree"); 121 const waitForSelect = () => 122 new Promise(resolve => { 123 tree.once("select", (d, a) => resolve({ data: d, attachment: a })); 124 }); 125 126 info("clicking on first top level item"); 127 let node = tree.root.children.firstChild.firstChild; 128 129 // The select event handler will be called before the click event hasn't 130 // fully finished, so wait for both of them. 131 const clicked = once(node, "click"); 132 let onTreeSelect = waitForSelect(); 133 click(node); 134 135 info("Wait for the click event"); 136 await clicked; 137 138 info("Wait for the select event on tree"); 139 await onTreeSelect; 140 141 node = tree.root.children.firstChild.nextSibling.firstChild; 142 // node should not have selected class 143 ok( 144 !node.classList.contains("theme-selected"), 145 "Node should not have selected class" 146 ); 147 ok(!node.hasAttribute("expanded"), "Node is not expanded"); 148 149 info("Pressing down key to select next item"); 150 onTreeSelect = waitForSelect(); 151 EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); 152 153 let { data, attachment } = await onTreeSelect; 154 is(data.length, 1, "Correct level item was selected after keydown"); 155 is(data[0], "level1", "Correct item was selected after pressing down"); 156 ok(!attachment, "null attachment was emitted"); 157 ok(node.classList.contains("theme-selected"), "Node has selected class"); 158 ok(node.hasAttribute("expanded"), "Node is expanded now"); 159 160 info("Pressing down key again to select next item"); 161 onTreeSelect = waitForSelect(); 162 EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); 163 ({ data, attachment } = await onTreeSelect); 164 is( 165 data.length, 166 2, 167 "Correct level item was selected after second down keypress" 168 ); 169 is(data[0], "level1", "Correct parent level"); 170 is(data[1], "level2", "Correct second level"); 171 172 info("Pressing down key again to select next item"); 173 onTreeSelect = waitForSelect(); 174 EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); 175 ({ data, attachment } = await onTreeSelect); 176 is( 177 data.length, 178 3, 179 "Correct level item was selected after third down keypress" 180 ); 181 is(data[0], "level1", "Correct parent level"); 182 is(data[1], "level2", "Correct second level"); 183 is(data[2], "level3", "Correct third level"); 184 185 info("Pressing down key again to select next item"); 186 onTreeSelect = waitForSelect(); 187 EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); 188 ({ data, attachment } = await onTreeSelect); 189 is( 190 data.length, 191 2, 192 "Correct level item was selected after fourth down keypress" 193 ); 194 is(data[0], "level1", "Correct parent level"); 195 is(data[1], "level2-1", "Correct second level"); 196 197 const waitForKeydown = () => 198 new Promise(resolve => { 199 tree.root.children.addEventListener( 200 "keydown", 201 () => { 202 // executeSoon so that other listeners on the same method are executed first 203 executeSoon(() => resolve()); 204 }, 205 { once: true } 206 ); 207 }); 208 209 // pressing left to check expand collapse feature. 210 // This does not emit any event, so listening for keypress 211 let onTreeKeydown = waitForKeydown(); 212 info("Pressing left key to collapse the item"); 213 node = tree._selectedLabel; 214 ok(node.hasAttribute("expanded"), "Item is expanded before left keypress"); 215 EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win); 216 await onTreeKeydown; 217 218 ok( 219 !node.hasAttribute("expanded"), 220 "Item is not expanded after left keypress" 221 ); 222 223 // pressing left on collapsed item should select the previous item 224 225 info("Pressing left key on collapsed item to select previous"); 226 onTreeSelect = waitForSelect(); 227 // parent node should have no effect of this keypress 228 node = tree.root.children.firstChild.nextSibling.firstChild; 229 ok(node.hasAttribute("expanded"), "Parent is expanded"); 230 EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win); 231 ({ data } = await onTreeSelect); 232 is( 233 data.length, 234 3, 235 "Correct level item was selected after second left keypress" 236 ); 237 is(data[0], "level1", "Correct parent level"); 238 is(data[1], "level2", "Correct second level"); 239 is(data[2], "level3", "Correct third level"); 240 ok( 241 node.hasAttribute("expanded"), 242 "Parent is still expanded after left keypress" 243 ); 244 245 // pressing down again 246 247 info("Pressing down key to select next item"); 248 onTreeSelect = waitForSelect(); 249 EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); 250 ({ data, attachment } = await onTreeSelect); 251 is( 252 data.length, 253 2, 254 "Correct level item was selected after fifth down keypress" 255 ); 256 is(data[0], "level1", "Correct parent level"); 257 is(data[1], "level2-1", "Correct second level"); 258 259 // collapsing the item to check expand feature. 260 onTreeKeydown = waitForKeydown(); 261 info("Pressing left key to collapse the item"); 262 node = tree._selectedLabel; 263 ok(node.hasAttribute("expanded"), "Item is expanded before left keypress"); 264 EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win); 265 await onTreeKeydown; 266 ok(!node.hasAttribute("expanded"), "Item is collapsed after left keypress"); 267 268 // pressing right should expand this now. 269 onTreeKeydown = waitForKeydown(); 270 info("Pressing right key to expend the collapsed item"); 271 node = tree._selectedLabel; 272 ok(!node.hasAttribute("expanded"), "Item is collapsed before right keypress"); 273 EventUtils.synthesizeKey("KEY_ArrowRight", {}, win); 274 await onTreeKeydown; 275 ok(node.hasAttribute("expanded"), "Item is expanded after right keypress"); 276 277 // selecting last item node to test edge navigation case 278 279 tree.selectedItem = ["level1.1", "level2", "level3"]; 280 node = tree._selectedLabel; 281 // pressing down again should not change selection 282 onTreeKeydown = waitForKeydown(); 283 info("Pressing down key on last item of the tree"); 284 EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); 285 await onTreeKeydown; 286 287 ok( 288 tree.isSelected(["level1.1", "level2", "level3"]), 289 "Last item is still selected after pressing down on last item of the tree" 290 ); 291 }