browser_inspector_breadcrumbs_mutations.js (8265B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 "use strict"; 4 5 // Test that the breadcrumbs widget refreshes correctly when there are markup 6 // mutations (and that it doesn't refresh when those mutations don't change its 7 // output). 8 9 const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html"; 10 11 // Each item in the TEST_DATA array is a test case that should contain the 12 // following properties: 13 // - desc {String} A description of this test case (will be logged). 14 // - setup {Function*} A generator function (can yield promises) that sets up 15 // the test case. Useful for selecting a node before starting the test. 16 // - run {Function*} A generator function (can yield promises) that runs the 17 // actual test case, i.e, mutates the content DOM to cause the breadcrumbs 18 // to refresh, or not. 19 // - shouldRefresh {Boolean} Once the `run` function has completed, and the test 20 // has detected that the page has changed, this boolean instructs the test to 21 // verify if the breadcrumbs has refreshed or not. 22 // - output {Array} A list of strings for the text that should be found in each 23 // button after the test has run. 24 const TEST_DATA = [ 25 { 26 desc: "Adding a child at the end of the chain shouldn't change anything", 27 async setup(inspector) { 28 await selectNode("#i1111", inspector); 29 }, 30 async run({ walker, selection }) { 31 await walker.setInnerHTML(selection.nodeFront, "<b>test</b>"); 32 }, 33 shouldRefresh: false, 34 output: ["html", "body", "article#i1", "div#i11", "div#i111", "div#i1111"], 35 }, 36 { 37 desc: "Updating an ID to an displayed element should refresh", 38 setup() {}, 39 async run({ walker }) { 40 const node = await walker.querySelector(walker.rootNode, "#i1"); 41 await node.modifyAttributes([ 42 { 43 attributeName: "id", 44 newValue: "i1-changed", 45 }, 46 ]); 47 }, 48 shouldRefresh: true, 49 output: [ 50 "html", 51 "body", 52 "article#i1-changed", 53 "div#i11", 54 "div#i111", 55 "div#i1111", 56 ], 57 }, 58 { 59 desc: "Updating an class to a displayed element should refresh", 60 setup() {}, 61 async run({ walker }) { 62 const node = await walker.querySelector(walker.rootNode, "body"); 63 await node.modifyAttributes([ 64 { 65 attributeName: "class", 66 newValue: "test-class", 67 }, 68 ]); 69 }, 70 shouldRefresh: true, 71 output: [ 72 "html", 73 "body.test-class", 74 "article#i1-changed", 75 "div#i11", 76 "div#i111", 77 "div#i1111", 78 ], 79 }, 80 { 81 desc: 82 "Updating a non id/class attribute to a displayed element should not " + 83 "refresh", 84 setup() {}, 85 async run({ walker }) { 86 const node = await walker.querySelector(walker.rootNode, "#i11"); 87 await node.modifyAttributes([ 88 { 89 attributeName: "name", 90 newValue: "value", 91 }, 92 ]); 93 }, 94 shouldRefresh: false, 95 output: [ 96 "html", 97 "body.test-class", 98 "article#i1-changed", 99 "div#i11", 100 "div#i111", 101 "div#i1111", 102 ], 103 }, 104 { 105 desc: "Moving a child in an element that's not displayed should not refresh", 106 setup() {}, 107 async run({ walker }) { 108 // Re-append #i1211 as a last child of #i2. 109 const parent = await walker.querySelector(walker.rootNode, "#i2"); 110 const child = await walker.querySelector(walker.rootNode, "#i211"); 111 await walker.insertBefore(child, parent); 112 }, 113 shouldRefresh: false, 114 output: [ 115 "html", 116 "body.test-class", 117 "article#i1-changed", 118 "div#i11", 119 "div#i111", 120 "div#i1111", 121 ], 122 }, 123 { 124 desc: "Moving an undisplayed child in a displayed element should not refresh", 125 setup() {}, 126 async run({ walker }) { 127 // Re-append #i2 in body (move it to the end). 128 const parent = await walker.querySelector(walker.rootNode, "body"); 129 const child = await walker.querySelector(walker.rootNode, "#i2"); 130 await walker.insertBefore(child, parent); 131 }, 132 shouldRefresh: false, 133 output: [ 134 "html", 135 "body.test-class", 136 "article#i1-changed", 137 "div#i11", 138 "div#i111", 139 "div#i1111", 140 ], 141 }, 142 { 143 desc: 144 "Updating attributes on an element that's not displayed should not " + 145 "refresh", 146 setup() {}, 147 async run({ walker }) { 148 const node = await walker.querySelector(walker.rootNode, "#i2"); 149 await node.modifyAttributes([ 150 { 151 attributeName: "id", 152 newValue: "i2-changed", 153 }, 154 { 155 attributeName: "class", 156 newValue: "test-class", 157 }, 158 ]); 159 }, 160 shouldRefresh: false, 161 output: [ 162 "html", 163 "body.test-class", 164 "article#i1-changed", 165 "div#i11", 166 "div#i111", 167 "div#i1111", 168 ], 169 }, 170 { 171 desc: "Removing the currently selected node should refresh", 172 async setup(inspector) { 173 await selectNode("#i2-changed", inspector); 174 }, 175 async run({ walker, selection }) { 176 await walker.removeNode(selection.nodeFront); 177 }, 178 shouldRefresh: true, 179 output: ["html", "body.test-class"], 180 }, 181 { 182 desc: "Changing the class of the currently selected node should refresh", 183 setup() {}, 184 async run({ selection }) { 185 await selection.nodeFront.modifyAttributes([ 186 { 187 attributeName: "class", 188 newValue: "test-class-changed", 189 }, 190 ]); 191 }, 192 shouldRefresh: true, 193 output: ["html", "body.test-class-changed"], 194 }, 195 { 196 desc: "Changing the id of the currently selected node should refresh", 197 setup() {}, 198 async run({ selection }) { 199 await selection.nodeFront.modifyAttributes([ 200 { 201 attributeName: "id", 202 newValue: "new-id", 203 }, 204 ]); 205 }, 206 shouldRefresh: true, 207 output: ["html", "body#new-id.test-class-changed"], 208 }, 209 ]; 210 211 add_task(async function () { 212 const { inspector } = await openInspectorForURL(TEST_URI); 213 const breadcrumbs = inspector.panelDoc.getElementById( 214 "inspector-breadcrumbs" 215 ); 216 const container = breadcrumbs.querySelector(".html-arrowscrollbox-inner"); 217 const win = container.ownerDocument.defaultView; 218 219 for (const { desc, setup, run, shouldRefresh, output } of TEST_DATA) { 220 info("Running test case: " + desc); 221 222 info( 223 "Listen to markupmutation events from the inspector to know when a " + 224 "test case has completed" 225 ); 226 const onContentMutation = inspector.once("markupmutation"); 227 228 info("Running setup"); 229 await setup(inspector); 230 231 info("Listen to mutations on the breadcrumbs container"); 232 let hasBreadcrumbsMutated = false; 233 const observer = new win.MutationObserver(mutations => { 234 // Only consider childList changes or tooltiptext/checked attributes 235 // changes. The rest may be mutations caused by the overflowing arrowbox. 236 for (const { type, attributeName } of mutations) { 237 const isChildList = type === "childList"; 238 const isAttributes = 239 type === "attributes" && 240 (attributeName === "checked" || attributeName === "tooltiptext"); 241 if (isChildList || isAttributes) { 242 hasBreadcrumbsMutated = true; 243 break; 244 } 245 } 246 }); 247 observer.observe(container, { 248 attributes: true, 249 childList: true, 250 subtree: true, 251 }); 252 253 info("Running the test case"); 254 await run(inspector); 255 256 info("Wait until the page has mutated"); 257 await onContentMutation; 258 259 if (shouldRefresh) { 260 info("The breadcrumbs is expected to refresh, so wait for it"); 261 await inspector.once("inspector-updated"); 262 } else { 263 ok( 264 !inspector._updateProgress, 265 "The breadcrumbs widget is not currently updating" 266 ); 267 } 268 269 is(shouldRefresh, hasBreadcrumbsMutated, "Has the breadcrumbs refreshed?"); 270 observer.disconnect(); 271 272 info("Check the output of the breadcrumbs widget"); 273 is(container.childNodes.length, output.length, "Correct number of buttons"); 274 for (let i = 0; i < container.childNodes.length; i++) { 275 is( 276 output[i], 277 container.childNodes[i].textContent, 278 "Text content for button " + i + " is correct" 279 ); 280 } 281 } 282 });