summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/compatibility/test/browser/head.js
blob: 54e897093b1df5ea9f79d917e3bd152e22d96932 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

// Import the rule view's head.js first (which itself imports inspector's head.js and shared-head.js).
Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/devtools/client/inspector/rules/test/head.js",
  this
);

const {
  COMPATIBILITY_UPDATE_SELECTED_NODE_COMPLETE,
  COMPATIBILITY_UPDATE_TOP_LEVEL_TARGET_COMPLETE,
} = require("resource://devtools/client/inspector/compatibility/actions/index.js");

const {
  toCamelCase,
} = require("resource://devtools/client/inspector/compatibility/utils/cases.js");

async function openCompatibilityView() {
  info("Open the compatibility view");
  await pushPref("devtools.inspector.compatibility.enabled", true);

  const { inspector } = await openInspectorSidebarTab("compatibilityview");
  await Promise.all([
    waitForUpdateSelectedNodeAction(inspector.store),
    waitForUpdateTopLevelTargetAction(inspector.store),
  ]);
  const panel = inspector.panelDoc.querySelector(
    "#compatibilityview-panel .inspector-tabpanel"
  );

  const selectedElementPane = panel.querySelector(
    "#compatibility-app--selected-element-pane"
  );

  const allElementsPane = panel.querySelector(
    "#compatibility-app--all-elements-pane"
  );

  return { allElementsPane, inspector, panel, selectedElementPane };
}

/**
 * Check whether the content of issue item element is matched with the expected values.
 *
 * @param {Element} panel
 * @param {Array} expectedIssues
 *        Array of the issue expected.
 *        For the structure of issue items, see types.js.
 */
async function assertIssueList(panel, expectedIssues) {
  info("Check the number of issues");
  await waitUntil(
    () =>
      panel.querySelectorAll("[data-qa-property]").length ===
      expectedIssues.length
  );
  ok(true, "The number of issues is correct");

  if (expectedIssues.length === 0) {
    // No issue.
    return;
  }

  const getFluentString = await getFluentStringHelper([
    "devtools/client/compatibility.ftl",
  ]);

  for (const expectedIssue of expectedIssues) {
    const property = expectedIssue.property;
    info(`Check an element for ${property}`);
    const issueEl = getIssueItem(property, panel);
    ok(issueEl, `Issue element for the ${property} is in the panel`);

    if (expectedIssue.unsupportedBrowsers) {
      // We only display a single icon per unsupported browser, so we need to
      // group the expected unsupported browsers (versions) by their browser id.
      const expectedUnsupportedBrowsersById = new Map();
      for (const unsupportedBrowser of expectedIssue.unsupportedBrowsers) {
        if (!expectedUnsupportedBrowsersById.has(unsupportedBrowser.id)) {
          expectedUnsupportedBrowsersById.set(unsupportedBrowser.id, []);
        }
        expectedUnsupportedBrowsersById
          .get(unsupportedBrowser.id)
          .push(unsupportedBrowser);
      }

      const unsupportedBrowserListEl = issueEl.querySelector(
        ".compatibility-unsupported-browser-list"
      );
      const unsupportedBrowsersEl =
        unsupportedBrowserListEl.querySelectorAll("li");

      is(
        unsupportedBrowsersEl.length,
        expectedUnsupportedBrowsersById.size,
        "The expected number of browser icons are displayed"
      );

      for (const unsupportedBrowserEl of unsupportedBrowsersEl) {
        const expectedUnsupportedBrowsers = expectedUnsupportedBrowsersById.get(
          unsupportedBrowserEl.getAttribute("data-browser-id")
        );

        ok(expectedUnsupportedBrowsers, "The expected browser is displayed");
        // debugger;
        is(
          unsupportedBrowserEl.querySelector(".compatibility-browser-version")
            .innerText,
          // If esr is not supported, but a newest version isn't as well, we don't display
          // the esr version number
          (
            expectedUnsupportedBrowsers.find(
              ({ status }) => status !== "esr"
            ) || expectedUnsupportedBrowsers[0]
          ).version,
          "The expected browser version is displayed"
        );

        is(
          unsupportedBrowserEl.getAttribute("title"),
          getFluentString("compatibility-issue-browsers-list", "title", {
            browsers: expectedUnsupportedBrowsers
              .map(
                ({ name, status, version }) =>
                  `${name} ${version}${status ? ` (${status})` : ""}`
              )
              .join("\n"),
          }),
          "The brower item has the expected title attribute"
        );
      }
    }

    for (const [key, value] of Object.entries(expectedIssue)) {
      const datasetKey = toCamelCase(`qa-${key}`);
      is(
        issueEl.dataset[datasetKey],
        JSON.stringify(value),
        `The value of ${datasetKey} is correct`
      );
    }

    const propertyEl = issueEl.querySelector(
      ".compatibility-issue-item__property"
    );
    const MDN_CLASSNAME = "compatibility-issue-item__mdn-link";
    const SPEC_CLASSNAME = "compatibility-issue-item__spec-link";

    is(
      propertyEl.textContent,
      property,
      "property name is displayed as expected"
    );

    is(
      propertyEl.classList.contains(MDN_CLASSNAME),
      !!expectedIssue.url,
      `${property} element ${
        expectedIssue.url ? "has" : "does not have"
      } mdn link class`
    );
    is(
      propertyEl.classList.contains(SPEC_CLASSNAME),
      !!expectedIssue.specUrl,
      `${property} element ${
        expectedIssue.specUrl ? "has" : "does not have"
      } spec link class`
    );

    if (expectedIssue.url || expectedIssue.specUrl) {
      is(
        propertyEl.nodeName.toLowerCase(),
        "a",
        `Link rendered for ${property}`
      );

      const expectedUrl = expectedIssue.url
        ? expectedIssue.url +
          "?utm_source=devtools&utm_medium=inspector-compatibility&utm_campaign=default"
        : expectedIssue.specUrl;
      const { link } = await simulateLinkClick(propertyEl);
      is(
        link,
        expectedUrl,
        `Click on ${property} link navigates user to expected url`
      );
    } else {
      is(
        propertyEl.nodeName.toLowerCase(),
        "span",
        `No link rendered for ${property}`
      );

      const { link } = await simulateLinkClick(propertyEl);
      is(link, null, `Click on ${property} does not navigate`);
    }
  }
}

/**
 * Check whether the content of node item element is matched with the expected values.
 *
 * @param {Element} panel
 * @param {Array} expectedNodes
 *        e.g.
 *        [{ property: "margin-inline-end", nodes: ["body", "div.classname"] },...]
 */
async function assertNodeList(panel, expectedNodes) {
  for (const { property, nodes } of expectedNodes) {
    info(`Check nodes for ${property}`);
    const issueEl = getIssueItem(property, panel);

    await waitUntil(
      () =>
        issueEl.querySelectorAll(".compatibility-node-item").length ===
        nodes.length
    );
    ok(true, "The number of nodes is correct");

    const nodeEls = [...issueEl.querySelectorAll(".compatibility-node-item")];
    for (const node of nodes) {
      const nodeEl = nodeEls.find(el => el.textContent === node);
      ok(nodeEl, "The text content of the node element is correct");
    }
  }
}

/**
 * Get IssueItem of given property from given element.
 *
 * @param {String} property
 * @param {Element} element
 * @return {Element}
 */
function getIssueItem(property, element) {
  return element.querySelector(`[data-qa-property=\"\\"${property}\\"\"]`);
}

/**
 * Toggle enable/disable checkbox of a specific property on rule view.
 *
 * @param {Inspector} inspector
 * @param {Number} ruleIndex
 * @param {Number} propIndex
 */
async function togglePropStatusOnRuleView(inspector, ruleIndex, propIndex) {
  const ruleView = inspector.getPanel("ruleview").view;
  const rule = getRuleViewRuleEditor(ruleView, ruleIndex).rule;
  // In case of inline style changes, we track the mutations via the
  // inspector's markupmutation event to react to dynamic style changes
  // which Resource Watcher doesn't cover yet.
  // If an inline style is applied to the element, we need to wait on the
  // markupmutation event
  const onMutation =
    ruleIndex === 0 ? inspector.once("markupmutation") : Promise.resolve();
  const textProp = rule.textProps[propIndex];
  const onRuleviewChanged = ruleView.once("ruleview-changed");
  textProp.editor.enable.click();
  await Promise.all([onRuleviewChanged, onMutation]);
}

/**
 * Return a promise which waits for COMPATIBILITY_UPDATE_SELECTED_NODE_COMPLETE action.
 *
 * @param {Object} store
 * @return {Promise}
 */
function waitForUpdateSelectedNodeAction(store) {
  return waitForDispatch(store, COMPATIBILITY_UPDATE_SELECTED_NODE_COMPLETE);
}

/**
 * Return a promise which waits for COMPATIBILITY_UPDATE_TOP_LEVEL_TARGET_COMPLETE action.
 *
 * @param {Object} store
 * @return {Promise}
 */
function waitForUpdateTopLevelTargetAction(store) {
  return waitForDispatch(store, COMPATIBILITY_UPDATE_TOP_LEVEL_TARGET_COMPLETE);
}