head.js (9013B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Import the rule view's head.js first (which itself imports inspector's head.js and shared-head.js). 7 Services.scriptloader.loadSubScript( 8 "chrome://mochitests/content/browser/devtools/client/inspector/rules/test/head.js", 9 this 10 ); 11 12 const { 13 COMPATIBILITY_UPDATE_SELECTED_NODE_COMPLETE, 14 COMPATIBILITY_UPDATE_TOP_LEVEL_TARGET_COMPLETE, 15 } = require("resource://devtools/client/inspector/compatibility/actions/index.js"); 16 17 const { 18 toCamelCase, 19 } = require("resource://devtools/client/inspector/compatibility/utils/cases.js"); 20 21 async function openCompatibilityView() { 22 info("Open the compatibility view"); 23 const { inspector } = await openInspectorSidebarTab("compatibilityview"); 24 await Promise.all([ 25 waitForUpdateSelectedNodeAction(inspector.store), 26 waitForUpdateTopLevelTargetAction(inspector.store), 27 ]); 28 const panel = inspector.panelDoc.querySelector( 29 "#compatibilityview-panel .inspector-tabpanel" 30 ); 31 32 const selectedElementPane = panel.querySelector( 33 "#compatibility-app--selected-element-pane" 34 ); 35 36 const allElementsPane = panel.querySelector( 37 "#compatibility-app--all-elements-pane" 38 ); 39 40 return { allElementsPane, inspector, panel, selectedElementPane }; 41 } 42 43 /** 44 * Check whether the content of issue item element is matched with the expected values. 45 * 46 * @param {Element} panel 47 * @param {Array} expectedIssues 48 * Array of the issue expected. 49 * For the structure of issue items, see types.js. 50 */ 51 async function assertIssueList(panel, expectedIssues) { 52 await waitFor( 53 () => 54 panel.querySelectorAll("[data-qa-property]").length === 55 expectedIssues.length, 56 "The number of issues is correct" 57 ); 58 59 if (expectedIssues.length === 0) { 60 // No issue. 61 return; 62 } 63 64 const getFluentString = await getFluentStringHelper([ 65 "devtools/client/compatibility.ftl", 66 ]); 67 68 for (const expectedIssue of expectedIssues) { 69 const property = expectedIssue.property; 70 info(`Check an element for ${property}`); 71 const issueEl = getIssueItem(property, panel); 72 ok(issueEl, `Issue element for the ${property} is in the panel`); 73 74 if (expectedIssue.unsupportedBrowsers) { 75 // We only display a single icon per unsupported browser, so we need to 76 // group the expected unsupported browsers (versions) by their browser id. 77 const expectedUnsupportedBrowsersById = new Map(); 78 for (const unsupportedBrowser of expectedIssue.unsupportedBrowsers) { 79 if (!expectedUnsupportedBrowsersById.has(unsupportedBrowser.id)) { 80 expectedUnsupportedBrowsersById.set(unsupportedBrowser.id, []); 81 } 82 expectedUnsupportedBrowsersById 83 .get(unsupportedBrowser.id) 84 .push(unsupportedBrowser); 85 } 86 87 const unsupportedBrowserListEl = issueEl.querySelector( 88 ".compatibility-unsupported-browser-list" 89 ); 90 const unsupportedBrowsersEl = 91 unsupportedBrowserListEl.querySelectorAll("li"); 92 93 is( 94 unsupportedBrowsersEl.length, 95 expectedUnsupportedBrowsersById.size, 96 "The expected number of browser icons are displayed" 97 ); 98 99 for (const unsupportedBrowserEl of unsupportedBrowsersEl) { 100 const expectedUnsupportedBrowsers = expectedUnsupportedBrowsersById.get( 101 unsupportedBrowserEl.getAttribute("data-browser-id") 102 ); 103 104 ok(expectedUnsupportedBrowsers, "The expected browser is displayed"); 105 // debugger; 106 is( 107 unsupportedBrowserEl.querySelector(".compatibility-browser-version") 108 .innerText, 109 // If esr is not supported, but a newest version isn't as well, we don't display 110 // the esr version number 111 ( 112 expectedUnsupportedBrowsers.find( 113 ({ status }) => status !== "esr" 114 ) || expectedUnsupportedBrowsers[0] 115 ).version, 116 "The expected browser version is displayed" 117 ); 118 119 is( 120 unsupportedBrowserEl.getAttribute("title"), 121 getFluentString("compatibility-issue-browsers-list", "title", { 122 browsers: expectedUnsupportedBrowsers 123 .map( 124 ({ name, status, version }) => 125 `${name} ${version}${status ? ` (${status})` : ""}` 126 ) 127 .join("\n"), 128 }), 129 "The brower item has the expected title attribute" 130 ); 131 } 132 } 133 134 for (const [key, value] of Object.entries(expectedIssue)) { 135 const datasetKey = toCamelCase(`qa-${key}`); 136 is( 137 issueEl.dataset[datasetKey], 138 JSON.stringify(value), 139 `The value of ${datasetKey} is correct` 140 ); 141 } 142 143 const propertyEl = issueEl.querySelector( 144 ".compatibility-issue-item__property" 145 ); 146 const MDN_CLASSNAME = "compatibility-issue-item__mdn-link"; 147 const SPEC_CLASSNAME = "compatibility-issue-item__spec-link"; 148 149 is( 150 propertyEl.textContent, 151 property, 152 "property name is displayed as expected" 153 ); 154 155 is( 156 propertyEl.classList.contains(MDN_CLASSNAME), 157 !!expectedIssue.url, 158 `${property} element ${ 159 expectedIssue.url ? "has" : "does not have" 160 } mdn link class` 161 ); 162 is( 163 propertyEl.classList.contains(SPEC_CLASSNAME), 164 !!expectedIssue.specUrl, 165 `${property} element ${ 166 expectedIssue.specUrl ? "has" : "does not have" 167 } spec link class` 168 ); 169 170 if (expectedIssue.url || expectedIssue.specUrl) { 171 is( 172 propertyEl.nodeName.toLowerCase(), 173 "a", 174 `Link rendered for ${property}` 175 ); 176 177 const expectedUrl = expectedIssue.url 178 ? expectedIssue.url + 179 "?utm_source=devtools&utm_medium=inspector-compatibility&utm_campaign=default" 180 : expectedIssue.specUrl; 181 const { link } = await simulateLinkClick(propertyEl); 182 is( 183 link, 184 expectedUrl, 185 `Click on ${property} link navigates user to expected url` 186 ); 187 } else { 188 is( 189 propertyEl.nodeName.toLowerCase(), 190 "span", 191 `No link rendered for ${property}` 192 ); 193 194 const { link } = await simulateLinkClick(propertyEl); 195 is(link, null, `Click on ${property} does not navigate`); 196 } 197 } 198 } 199 200 /** 201 * Check whether the content of node item element is matched with the expected values. 202 * 203 * @param {Element} panel 204 * @param {Array} expectedNodes 205 * e.g. 206 * [{ property: "margin-inline-end", nodes: ["body", "div.classname"] },...] 207 */ 208 async function assertNodeList(panel, expectedNodes) { 209 for (const { property, nodes } of expectedNodes) { 210 info(`Check nodes for ${property}`); 211 const issueEl = getIssueItem(property, panel); 212 213 await waitUntil( 214 () => 215 issueEl.querySelectorAll(".compatibility-node-item").length === 216 nodes.length 217 ); 218 ok(true, "The number of nodes is correct"); 219 220 const nodeEls = [...issueEl.querySelectorAll(".compatibility-node-item")]; 221 for (const node of nodes) { 222 const nodeEl = nodeEls.find(el => el.textContent === node); 223 ok(nodeEl, "The text content of the node element is correct"); 224 } 225 } 226 } 227 228 /** 229 * Get IssueItem of given property from given element. 230 * 231 * @param {string} property 232 * @param {Element} element 233 * @return {Element} 234 */ 235 function getIssueItem(property, element) { 236 return element.querySelector(`[data-qa-property=\"\\"${property}\\"\"]`); 237 } 238 239 /** 240 * Toggle enable/disable checkbox of a specific property on rule view. 241 * 242 * @param {Inspector} inspector 243 * @param {number} ruleIndex 244 * @param {number} propIndex 245 */ 246 async function togglePropStatusOnRuleView(inspector, ruleIndex, propIndex) { 247 const ruleView = inspector.getPanel("ruleview").view; 248 const rule = getRuleViewRuleEditor(ruleView, ruleIndex).rule; 249 // In case of inline style changes, we track the mutations via the 250 // inspector's markupmutation event to react to dynamic style changes 251 // which Resource Watcher doesn't cover yet. 252 // If an inline style is applied to the element, we need to wait on the 253 // markupmutation event 254 const onMutation = 255 ruleIndex === 0 ? inspector.once("markupmutation") : Promise.resolve(); 256 const textProp = rule.textProps[propIndex]; 257 const onRuleviewChanged = ruleView.once("ruleview-changed"); 258 textProp.editor.enable.click(); 259 await Promise.all([onRuleviewChanged, onMutation]); 260 } 261 262 /** 263 * Return a promise which waits for COMPATIBILITY_UPDATE_SELECTED_NODE_COMPLETE action. 264 * 265 * @param {object} store 266 * @return {Promise} 267 */ 268 function waitForUpdateSelectedNodeAction(store) { 269 return waitForDispatch(store, COMPATIBILITY_UPDATE_SELECTED_NODE_COMPLETE); 270 } 271 272 /** 273 * Return a promise which waits for COMPATIBILITY_UPDATE_TOP_LEVEL_TARGET_COMPLETE action. 274 * 275 * @param {object} store 276 * @return {Promise} 277 */ 278 function waitForUpdateTopLevelTargetAction(store) { 279 return waitForDispatch(store, COMPATIBILITY_UPDATE_TOP_LEVEL_TARGET_COMPLETE); 280 }