Bug 1306054 - [inactive CSS] Fix current tests and add new test r=rcaliman
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Tue, 07 May 2019 14:59:05 +0000
changeset 532251 85d5010b19abc7a58ce617aff2a39360fc54eee5
parent 532250 ecf6843307fa68c647f15eca2344d9d34f079ce7
child 532252 362df4629f8f1fd5ed0eece429b77499a77bb955
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcaliman
bugs1306054
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1306054 - [inactive CSS] Fix current tests and add new test r=rcaliman ### Try https://treeherder.mozilla.org/#/jobs?repo=try&revision=0bad22682ebfa917a91e9b95dab5345093f68d79 Differential Revision: https://phabricator.services.mozilla.com/D29025
devtools/client/inspector/rules/test/browser.ini
devtools/client/inspector/rules/test/browser_rules_inactive_css_flexbox.js
devtools/client/inspector/rules/test/browser_rules_inactive_css_grid.js
devtools/client/inspector/rules/test/head.js
devtools/client/inspector/test/head.js
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -190,16 +190,18 @@ skip-if = (os == "win" && debug) # bug 9
 [browser_rules_grid-toggle_03.js]
 [browser_rules_grid-toggle_04.js]
 [browser_rules_grid-toggle_05.js]
 [browser_rules_gridline-names-autocomplete.js]
 [browser_rules_guessIndentation.js]
 [browser_rules_highlight-element-rule.js]
 [browser_rules_highlight-property.js]
 [browser_rules_highlight-used-fonts.js]
+[browser_rules_inactive_css_flexbox.js]
+[browser_rules_inactive_css_grid.js]
 [browser_rules_inherited-properties_01.js]
 [browser_rules_inherited-properties_02.js]
 [browser_rules_inherited-properties_03.js]
 [browser_rules_inherited-properties_04.js]
 [browser_rules_inline-source-map.js]
 [browser_rules_inline-style-order.js]
 [browser_rules_invalid.js]
 [browser_rules_invalid-source-map.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inactive_css_flexbox.js
@@ -0,0 +1,170 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test inactive flex properties.
+
+const TEST_URI = `
+<head>
+  <style>
+    #container {
+      width: 200px;
+      height: 100px;
+      border: 1px solid #000;
+      align-content: space-between;
+      order: 1;
+    }
+
+    .flex-item {
+      flex-basis: auto;
+      flex-grow: 1;
+      flex-shrink: 1;
+      flex-direction: row;
+    }
+
+    #self-aligned {
+      align-self: stretch;
+    }
+  </style>
+</head>
+<body>
+    <h1>browser_rules_inactive_css_flexbox.js</h1>
+    <div id="container" style="display:flex">
+      <div class="flex-item item-1" style="order:1">1</div>
+      <div class="flex-item item-2" style="order:2">2</div>
+      <div class="flex-item item-3" style="order:3">3</div>
+    </div>
+    <div id="self-aligned"></div>
+</body>`;
+
+const BEFORE = [
+  {
+    selector: "#self-aligned",
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "stretch",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: ".item-2",
+    activeDeclarations: [
+      {
+        declarations: {
+          "order": "2",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          "flex-basis": "auto",
+          "flex-grow": "1",
+          "flex-shrink": "1",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.container",
+        declaration: {
+          "flex-direction": "row",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: "#container",
+    activeDeclarations: [
+      {
+        declarations: {
+          "display": "flex",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          width: "200px",
+          height: "100px",
+          border: "1px solid #000",
+          "align-content": "space-between",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "order": "1",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    waitFor: "inspector-updated",
+  },
+];
+
+const AFTER = [
+  {
+    selector: ".item-2",
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "order": "2",
+        },
+        ruleIndex: 0,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "flex-basis": "auto",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "flex-grow": "1",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "flex-shrink": "1",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.container",
+        declaration: {
+          "flex-direction": "row",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+];
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const {inspector, view} = await openRuleView();
+
+  await runInactiveCSSTests(view, inspector, BEFORE);
+
+  // Toggle `display:flex` to disabled.
+  await toggleDeclaration(inspector, view, 0, {
+    display: "flex",
+  });
+  await view.once("ruleview-refreshed");
+  await runInactiveCSSTests(view, inspector, AFTER);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inactive_css_grid.js
@@ -0,0 +1,170 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test inactive grid properties.
+
+const TEST_URI = `
+<head>
+  <style>
+    #container {
+      width: 200px;
+      height: 100px;
+      border: 1px solid #000;
+      column-gap: 10px;
+      row-gap: 10px;
+      align-self: start;
+    }
+
+    .item-1 {
+      grid-column-start: 1;
+      grid-column-end: 3;
+      grid-row-start: 1;
+      grid-row-end: auto;
+      flex-direction: row
+    }
+
+    #self-aligned {
+      align-self: stretch;
+    }
+  </style>
+</head>
+<body>
+    <h1>browser_rules_inactive_css_grid.js</h1>
+    <div id="container" style="display:grid">
+      <div class="grid-item item-1">1</div>
+      <div class="grid-item item-2">2</div>
+      <div class="grid-item item-3">3</div>
+      <div class="grid-item item-4">4</div>
+    </div>
+    <div id="self-aligned"></div>
+</body>`;
+
+const BEFORE = [
+  {
+    selector: "#self-aligned",
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "stretch",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: ".item-1",
+    activeDeclarations: [
+      {
+        declarations: {
+          "grid-column-start": "1",
+          "grid-column-end": "3",
+          "grid-row-start": "1",
+          "grid-row-end": "auto",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.container",
+        declaration: {
+          "flex-direction": "row",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: "#container",
+    activeDeclarations: [
+      {
+        declarations: {
+          display: "grid",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          width: "200px",
+          height: "100px",
+          border: "1px solid #000",
+          "column-gap": "10px",
+          "row-gap": "10px",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "start",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    waitFor: "inspector-updated",
+  },
+];
+
+const AFTER = [
+  {
+    activeDeclarations: [
+      {
+        declarations: {
+          display: "grid",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          width: "200px",
+          height: "100px",
+          border: "1px solid #000",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.container",
+        declaration: {
+          "column-gap": "10px",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.grid.container",
+        declaration: {
+          "row-gap": "10px",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "start",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+];
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const {inspector, view} = await openRuleView();
+
+  await runInactiveCSSTests(view, inspector, BEFORE);
+
+  // Toggle `display:grid` to disabled.
+  await toggleDeclaration(inspector, view, 0, {
+    display: "grid",
+  });
+  await view.once("ruleview-refreshed");
+  await runInactiveCSSTests(view, inspector, AFTER);
+});
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -563,17 +563,17 @@ async function toggleClassPanelCheckBox(
   const onMutation = view.inspector.once("markupmutation");
   checkBox.click();
   info("Waiting for a markupmutation as a result of toggling this class");
   await onMutation;
 }
 
 /**
  * Verify the content of the class-panel.
- * @param {CssRuleView} view The rule-view isntance
+ * @param {CssRuleView} view The rule-view instance
  * @param {Array} classes The list of expected classes. Each item in this array is an
  * object with the following properties: {name: {String}, state: {Boolean}}
  */
 function checkClassPanelContent(view, classes) {
   const checkBoxNodeList = view.classPanel.querySelectorAll("[type=checkbox]");
   is(checkBoxNodeList.length, classes.length,
      "The panel contains the expected number of checkboxes");
 
@@ -601,8 +601,209 @@ async function openEyedropper(view, swat
 
   const dropperButton = tooltip.container.querySelector("#eyedropper-button");
 
   info("Click on the eyedropper icon");
   const onOpened = tooltip.once("eyedropper-opened");
   dropperButton.click();
   await onOpened;
 }
+
+/**
+ * Gets a set of declarations for a rule index.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {Number} ruleIndex
+ *        The index we expect the rule to have in the rule-view.
+ *
+ * @returns A map containing stringified property declarations e.g.
+ *          [
+ *            {
+ *              "color:red":
+ *                {
+ *                  propertyName: "color",
+ *                  propertyValue: "red",
+ *                  warnings: "This won't work",
+ *                  used: true,
+ *                }
+ *            },
+ *            ...
+ *          ]
+ */
+function getPropertiesForRuleIndex(view, ruleIndex) {
+  const declaration = new Map();
+  const ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+
+  for (const currProp of ruleEditor.rule.textProps) {
+    const icon = currProp.editor.unusedState;
+
+    declaration.set(`${currProp.name}:${currProp.value}`, {
+      propertyName: currProp.name,
+      propertyValue: currProp.value,
+      warnings: icon.title ? icon.title.split("\n") : [],
+      used: !currProp.editor.element.classList.contains("unused"),
+    });
+  }
+
+  return declaration;
+}
+
+/**
+ * Toggle a declaration disabled or enabled.
+ *
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox.
+ * @param {ruleView} view
+ *        The rule-view instance
+ * @param {Number} ruleIndex
+ *        The index of the CSS rule where we can find the declaration to be
+ *        toggled.
+ * @param {Object} declaration
+ *        An object representing the declaration e.g. { color: "red" }.
+ */
+async function toggleDeclaration(inspector, view, ruleIndex, declaration) {
+  const ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+  const [[ name, value ]] = Object.entries(declaration);
+
+  let textProp = null;
+  for (const currProp of ruleEditor.rule.textProps) {
+    if (currProp.name === name && currProp.value === value) {
+      textProp = currProp;
+      break;
+    }
+  }
+
+  const dec = `${name}:${value}`;
+  ok(textProp, `Declaration "${dec}" found`);
+
+  const newStatus = textProp.enabled ? "disabled" : "enabled";
+  info(`Toggling declaration "${dec}" of rule ${ruleIndex} to ${newStatus}`);
+
+  await togglePropStatus(view, textProp);
+}
+
+/**
+ * Check that a declaration is marked inactive and that it has the expected
+ * warning.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {Number} ruleIndex
+ *        The index we expect the rule to have in the rule-view.
+ * @param {Object} declaration
+ *        An object representing the declaration e.g. { color: "red" }.
+ * @param {String} warningL10nString
+ *        l10n string representing an expected warning.
+ */
+function checkDeclarationIsInactive(view, ruleIndex, declaration, warningL10nString) {
+  const declarations = getPropertiesForRuleIndex(view, ruleIndex);
+  const [[ name, value ]] = Object.entries(declaration);
+  const dec = `${name}:${value}`;
+  const { used, warnings } = declarations.get(dec);
+
+  ok(!used, `"${dec}" is inactive`);
+  is(warnings.length, 1, `"${dec}" has a warning`);
+
+  const warning = INSPECTOR_L10N.getFormatStr(warningL10nString, name);
+  is(warnings[0], warning, `The warning on "${dec}" is correct`);
+}
+
+/**
+ * Check that a declaration is marked active.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {Number} ruleIndex
+ *        The index we expect the rule to have in the rule-view.
+ * @param {Object} declaration
+ *        An object representing the declaration e.g. { color: "red" }.
+ */
+function checkDeclarationIsActive(view, ruleIndex, declaration) {
+  const declarations = getPropertiesForRuleIndex(view, ruleIndex);
+  const [[ name, value ]] = Object.entries(declaration);
+  const dec = `${name}:${value}`;
+  const { used, warnings } = declarations.get(dec);
+
+  ok(used, `${dec} is active`);
+  is(warnings.length, 0, `${dec} has no warnings`);
+}
+
+/**
+ * Inactive CSS test runner.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox.
+ * @param {Array} tests
+ *        An array of test object for this method to consume e.g.
+ *          [
+ *            {
+ *              selector: "#flex-item",
+ *              activeDeclarations: [
+ *                {
+ *                  declarations: {
+ *                    "order": "2",
+ *                  },
+ *                  ruleIndex: 0,
+ *                },
+ *                {
+ *                  declarations: {
+ *                    "flex-basis": "auto",
+ *                    "flex-grow": "1",
+ *                    "flex-shrink": "1",
+ *                  },
+ *                  ruleIndex: 1,
+ *                },
+ *              ],
+ *              inactiveDeclarations: [
+ *                {
+ *                  l10n: "rule.inactive.css.not.flex.container",
+ *                  declaration: {
+ *                    "flex-direction": "row",
+ *                  },
+ *                  ruleIndex: 1,
+ *                },
+ *              ],
+ *              waitFor: "markupmutation",
+ *            },
+ *            ...
+ *          ]
+ */
+async function runInactiveCSSTests(view, inspector, tests) {
+  for (const test of tests) {
+    let event = null;
+
+    if (test.waitFor) {
+      event = inspector.once(test.waitFor);
+    }
+
+    if (test.selector) {
+      await selectNode(test.selector, inspector);
+    }
+
+    if (test.waitFor) {
+      await event;
+    }
+
+    if (test.activeDeclarations) {
+      // Check whether declarations are marked as used.
+      for (const activeDeclarations of test.activeDeclarations) {
+        for (const [name, value] of Object.entries(activeDeclarations.declarations)) {
+          checkDeclarationIsActive(view, activeDeclarations.ruleIndex, {
+            [name]: value,
+          });
+        }
+      }
+    }
+
+    if (test.inactiveDeclarations) {
+      for (const inactiveDeclaration of test.inactiveDeclarations) {
+        // Check that declaration is unused and has a warning.
+        checkDeclarationIsInactive(view,
+                                   inactiveDeclaration.ruleIndex,
+                                   inactiveDeclaration.declaration,
+                                   inactiveDeclaration.l10n);
+      }
+    }
+  }
+}
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -770,30 +770,30 @@ async function assertTooltipShownOnHover
  * @param {CssRuleView|ComputedView|...} view
  *        The instance of an inspector panel
  * @param {DOMElement} target
  *        The DOM Element on which a tooltip should appear
  *
  * @return a promise that resolves with the tooltip object
  */
 async function assertShowPreviewTooltip(view, target) {
+  const name = "previewTooltip";
+
+  // Get the tooltip. If it does not exist one will be created.
+  const tooltip = view.tooltips.getTooltip(name);
+  ok(tooltip, `Tooltip '${name}' has been instantiated`);
+
+  const shown = tooltip.once("shown");
   const mouseEvent = new target.ownerDocument.defaultView.MouseEvent("mousemove", {
     bubbles: true,
   });
   target.dispatchEvent(mouseEvent);
 
-  const name = "previewTooltip";
-  ok(view.tooltips._instances.has(name),
-    `Tooltip '${name}' has been instantiated`);
-  const tooltip = view.tooltips.getTooltip(name);
-
-  if (!tooltip.isVisible()) {
-    info("Waiting for tooltip to be shown");
-    await tooltip.once("shown");
-  }
+  info("Waiting for tooltip to be shown");
+  await shown;
 
   ok(tooltip.isVisible(), `The tooltip '${name}' is visible`);
 
   return tooltip;
 }
 
 /**
  * Given a `tooltip` instance, fake a mouse event on `target` DOM element