Bug 1291322 - Manually throttle CssRuleView tests; r?tromey draft
authorGreg Tatum <tatum.creative@gmail.com>
Tue, 02 Aug 2016 11:27:27 -0500
changeset 396348 9e0ee8dcfc55e2a96134ef8f1837323b1e8f0246
parent 396347 4f072e4cc25ac1b0d68887e58f3ee2d5912a8173
child 527166 18913c7f424d464c6b4b4aebeb50cffc047aba3e
push id24963
push userbmo:gtatum@mozilla.com
push dateWed, 03 Aug 2016 17:11:57 +0000
reviewerstromey
bugs1291322
milestone51.0a1
Bug 1291322 - Manually throttle CssRuleView tests; r?tromey This patch provides a mechanism for overriding the throttle function in inspector tests with a manually controllable throttle. I did modify a few tests to not check for a second "ruleview-changed" as it required using a setTimeout to defer the code to the next frame to get it to work as it currently exists. I figured it was safer to just not flush the throttle, and reduce how many "ruleview-changed" events that the test expected. MozReview-Commit-ID: 8AIMouZjLBf
devtools/client/inspector/rules/test/browser_rules_add-property_02.js
devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js
devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
devtools/client/inspector/rules/test/browser_rules_keybindings.js
devtools/client/inspector/rules/test/browser_rules_livepreview.js
devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js
devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js
devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
devtools/client/inspector/rules/test/browser_rules_user-property-reset.js
devtools/client/inspector/rules/test/head.js
devtools/client/inspector/test/shared-head.js
--- a/devtools/client/inspector/rules/test/browser_rules_add-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property_02.js
@@ -35,20 +35,19 @@ add_task(function* () {
   // (see bug 761665).
   EventUtils.synthesizeMouse(input, 1, 1, {}, view.styleWindow);
   input.select();
 
   info("Entering the property name");
   editor.input.value = "background-color";
 
   info("Pressing RETURN and waiting for the value field focus");
-  // Pressing ENTER triggeres 2 changed events, one for the new property, and
-  // one for the preview.
-  let onNameAdded = waitForNEvents(view, "ruleview-changed", 2);
+  let onNameAdded = view.once("ruleview-changed");
   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+
   yield onNameAdded;
 
   editor = inplaceEditor(view.styleDocument.activeElement);
 
   is(ruleEditor.rule.textProps.length, 2,
     "Should have created a new text property.");
   is(ruleEditor.propertyList.children.length, 2,
     "Should have created a property editor.");
--- a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
@@ -115,16 +115,19 @@ function* testCompletion([key, completio
 
   // Also listening for popup opened/closed events if needed.
   let popupEvent = open ? "popup-opened" : "popup-closed";
   let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
 
   info("Synthesizing key " + key);
   EventUtils.synthesizeKey(key, {}, view.styleWindow);
 
+  // Flush the throttle for the preview text.
+  view.throttle.flush();
+
   yield onSuggest;
   yield onPopupEvent;
 
   info("Checking the state");
   if (completion !== null) {
     is(editor.input.value, completion, "Correct value is autocompleted");
   }
   if (!open) {
--- a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
@@ -93,16 +93,20 @@ function* testCompletion([key, modifiers
 
   info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
 
   // Also listening for popup opened/closed events if needed.
   let popupEvent = open ? "popup-opened" : "popup-closed";
   let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
 
   EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
+
+  // Flush the throttle for the preview text.
+  view.throttle.flush();
+
   yield onDone;
   yield onPopupEvent;
 
   // The key might have been a TAB or shift-TAB, in which case the editor will
   // be a new one
   editor = inplaceEditor(view.styleDocument.activeElement);
 
   info("Checking the state");
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
@@ -101,16 +101,20 @@ function* testCompletion([key, modifiers
   }
 
   // Also listening for popup opened/closed events if needed.
   let popupEvent = open ? "popup-opened" : "popup-closed";
   let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
 
   info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
   EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
+
+  // Flush the throttle for the preview text.
+  view.throttle.flush();
+
   yield onDone;
   yield onPopupEvent;
 
   info("Checking the state");
   if (completion !== null) {
     // The key might have been a TAB or shift-TAB, in which case the editor will
     // be a new one
     editor = inplaceEditor(view.styleDocument.activeElement);
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js
@@ -36,22 +36,24 @@ add_task(function* () {
   is(bgcItem.label, "background-color",
     "Check the expected completion element is background-color.");
   editor.popup.selectedIndex = itemIndex;
 
   info("Select the background-color suggestion with a mouse click.");
   let onSuggest = editor.once("after-suggest");
   let node = editor.popup.elements.get(bgcItem);
   EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window);
+
   yield onSuggest;
   is(editor.input.value, "background-color", "Correct value is autocompleted");
 
   info("Press RETURN to move the focus to a property value editor.");
-  let onModifications = waitForNEvents(view, "ruleview-changed", 2);
+  let onModifications = view.once("ruleview-changed");
   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+
   yield onModifications;
 
   // Getting the new value editor after focus
   editor = inplaceEditor(view.styleDocument.activeElement);
   let textProp = ruleEditor.rule.textProps[1];
 
   is(ruleEditor.rule.textProps.length, 2,
     "Created a new text property.");
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
@@ -94,16 +94,17 @@ add_task(function* () {
 
   info("Select the background-color suggestion with a mouse click.");
   let onRuleviewChanged = view.once("ruleview-changed");
   let onSuggest = editor.once("after-suggest");
 
   let node = editor.popup._list.childNodes[editor.popup.selectedIndex];
   EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window);
 
+  view.throttle.flush();
   yield onSuggest;
   yield onRuleviewChanged;
 
   is(editor.input.value, EXPECTED_CSS_VALUE,
     "Input value correctly autocompleted");
 
   info("Press ESCAPE to leave the input.");
   onRuleviewChanged = view.once("ruleview-changed");
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
@@ -67,16 +67,17 @@ function* runTestData(view, {value, comm
 
   let editor = yield focusEditableField(view, propEditor.valueSpan);
   is(inplaceEditor(propEditor.valueSpan), editor,
     "Focused editor should be the value span.");
 
   info("Entering test data " + value);
   let onRuleViewChanged = view.once("ruleview-changed");
   EventUtils.sendString(value, view.styleWindow);
+  view.throttle.flush();
   yield onRuleViewChanged;
 
   info("Entering the commit key " + commitKey + " " + modifiers);
   onRuleViewChanged = view.once("ruleview-changed");
   let onBlur = once(editor.input, "blur");
   EventUtils.synthesizeKey(commitKey, modifiers);
   yield onBlur;
   yield onRuleViewChanged;
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
@@ -38,16 +38,18 @@ function* editAndCheck(view) {
     "padding-top", newPaddingValue);
   let onRefreshAfterPreview = once(view, "ruleview-changed");
 
   info("Entering a new value");
   EventUtils.sendString(newPaddingValue, view.styleWindow);
 
   info("Waiting for the throttled previewValue to apply the " +
     "changes to document");
+
+  view.throttle.flush();
   yield onPropertyChange;
 
   info("Waiting for ruleview-refreshed after previewValue was applied.");
   yield onRefreshAfterPreview;
 
   let onBlur = once(editor.input, "blur");
 
   info("Entering the commit key and finishing edit");
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
@@ -230,16 +230,17 @@ function* runIncrementTest(propertyEdito
   for (let test in tests) {
     yield testIncrement(editor, tests[test], view, propertyEditor);
   }
 
   // Blur the field to put back the UI in its initial state (and avoid pending
   // requests when the test ends).
   let onRuleViewChanged = view.once("ruleview-changed");
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+  view.throttle.flush();
   yield onRuleViewChanged;
 }
 
 function* testIncrement(editor, options, view) {
   editor.input.value = options.start;
   let input = editor.input;
 
   if (options.selectAll) {
@@ -258,16 +259,19 @@ function* testIncrement(editor, options,
   if (options.pageDown) {
     key = "VK_PAGE_DOWN";
   } else if (options.pageUp) {
     key = "VK_PAGE_UP";
   }
 
   EventUtils.synthesizeKey(key, {altKey: options.alt, shiftKey: options.shift},
     view.styleWindow);
+
   yield onKeyUp;
+
   // Only expect a change if the value actually changed!
-  if (options.start !== options.end) {
+  if (options.start !== options.end) {
+    view.throttle.flush();
     yield onRuleViewChanged;
   }
 
   is(input.value, options.end, "Value changed to " + options.end);
 }
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
@@ -64,16 +64,17 @@ function* testEditProperty(inspector, ru
 
   info("Entering a value following by a semi-colon to commit it");
   let onBlur = once(editor.input, "blur");
   // Use sendChar() to pass each character as a string so that we can test
   // prop.editor.warning.hidden after each character.
   for (let ch of "red;") {
     let onPreviewDone = ruleView.once("ruleview-changed");
     EventUtils.sendChar(ch, ruleView.styleWindow);
+    ruleView.throttle.flush();
     yield onPreviewDone;
     is(prop.editor.warning.hidden, true,
       "warning triangle is hidden or shown as appropriate");
   }
   yield onBlur;
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
--- a/devtools/client/inspector/rules/test/browser_rules_keybindings.js
+++ b/devtools/client/inspector/rules/test/browser_rules_keybindings.js
@@ -17,21 +17,20 @@ add_task(function* () {
 
   info("Focus the new property editable field to create a color property");
   let ruleEditor = getRuleViewRuleEditor(view, 0);
   let editor = yield focusNewRuleViewProperty(ruleEditor);
   editor.input.value = "color";
 
   info("Typing ENTER to focus the next field: property value");
   let onFocus = once(brace.parentNode, "focus", true);
-  // The rule view changes twice, once for the first field to loose focus
-  // and a second time for the second field to gain focus
-  let onRuleViewChanged = view.once("ruleview-changed").then(
-    () => view.once("ruleview-changed"));
+  let onRuleViewChanged = view.once("ruleview-changed");
+
   EventUtils.sendKey("return");
+
   yield onFocus;
   yield onRuleViewChanged;
   ok(true, "The value field was focused");
 
   info("Entering a property value");
   editor = getCurrentInplaceEditor(view);
   editor.input.value = "green";
 
--- a/devtools/client/inspector/rules/test/browser_rules_livepreview.js
+++ b/devtools/client/inspector/rules/test/browser_rules_livepreview.js
@@ -48,16 +48,17 @@ function* testLivePreviewData(data, rule
   info("Focusing the property value inplace-editor");
   let editor = yield focusEditableField(ruleView, propEditor.valueSpan);
   is(inplaceEditor(propEditor.valueSpan), editor,
     "The focused editor is the value");
 
   info("Entering value in the editor: " + data.value);
   let onPreviewDone = ruleView.once("ruleview-changed");
   EventUtils.sendString(data.value, ruleView.styleWindow);
+  ruleView.throttle.flush();
   yield onPreviewDone;
 
   let onValueDone = ruleView.once("ruleview-changed");
   if (data.escape) {
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   } else {
     EventUtils.synthesizeKey("VK_RETURN", {});
   }
--- a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js
@@ -14,19 +14,17 @@ add_task(function* () {
   let {inspector, view} = yield openRuleView();
   yield selectNode("div", inspector);
   yield testCreateNewMultiUnfinished(inspector, view);
 });
 
 function* testCreateNewMultiUnfinished(inspector, view) {
   let ruleEditor = getRuleViewRuleEditor(view, 0);
   let onMutation = inspector.once("markupmutation");
-  // There are 2 rule-view updates: one for the preview and one for
-  // the final commit.
-  let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
+  let onRuleViewChanged = view.once("ruleview-changed");
   yield createNewRuleViewProperty(ruleEditor,
     "color:blue;background : orange   ; text-align:center; border-color: ");
   yield onMutation;
   yield onRuleViewChanged;
 
   is(ruleEditor.rule.textProps.length, 4,
     "Should have created new text properties.");
   is(ruleEditor.propertyList.children.length, 4,
--- a/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js
@@ -10,20 +10,17 @@
 const TEST_URI = "<div>Test Element</div>";
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("div", inspector);
 
   let ruleEditor = getRuleViewRuleEditor(view, 0);
-  // Expect 2 ruleview-changed events.
-  // - one when focusing the property-name editor
-  // - one after pressing RETURN, which will focus the property-value editor
-  let onDone = waitForNEvents(view, "ruleview-changed", 2);
+  let onDone = view.once("ruleview-changed");
   yield createNewRuleViewProperty(ruleEditor, "width:");
   yield onDone;
 
   is(ruleEditor.rule.textProps.length, 1,
     "Should have created a new text property.");
   is(ruleEditor.propertyList.children.length, 1,
     "Should have created a property editor.");
 
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
@@ -56,16 +56,17 @@ add_task(function* () {
 
   // Getting the new value editor after focus
   editor = inplaceEditor(view.styleDocument.activeElement);
   let propEditor = ruleEditor.rule.textProps[2].editor;
 
   info("Entering a value and bluring the field to expect a rule change");
   onRuleViewChanged = view.once("ruleview-changed");
   editor.input.value = "100%";
+  view.throttle.flush();
   yield onRuleViewChanged;
 
   onRuleViewChanged = view.once("ruleview-changed");
   editor.input.blur();
   yield onRuleViewChanged;
 
   ok(propEditor.container.classList.contains("ruleview-highlight"),
     "margin-left text property is correctly highlighted.");
--- a/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js
+++ b/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js
@@ -47,20 +47,20 @@ function* modifyRuleViewWidth(value, rul
 
   ok(editor.input, "The inplace-editor field is ready");
   info("Setting the new value");
   editor.input.value = value;
 
   info("Pressing return and waiting for the field to blur and for the " +
     "markup-view to show the mutation");
   let onBlur = once(editor.input, "blur", true);
-  let onMutation = inspector.once("markupmutation");
+  let onStyleChanged = waitForStyleModification(inspector);
   EventUtils.sendKey("return");
   yield onBlur;
-  yield onMutation;
+  yield onStyleChanged;
 
   info("Escaping out of the new property field that has been created after " +
     "the value was edited");
   let onNewFieldBlur = once(ruleView.styleDocument.activeElement, "blur", true);
   EventUtils.sendKey("escape");
   yield onNewFieldBlur;
 }
 
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -525,16 +525,17 @@ var addProperty = Task.async(function* (
   is(editor, inplaceEditor(textProp.editor.valueSpan),
      "The inplace editor appeared for the value");
 
   info("Adding value " + value);
   // Setting the input value schedules a preview to be shown in 10ms which
   // triggers a ruleview-changed event (see bug 1209295).
   let onPreview = view.once("ruleview-changed");
   editor.input.value = value;
+  view.throttle.flush();
   yield onPreview;
 
   let onValueAdded = view.once("ruleview-changed");
   EventUtils.synthesizeKey(commitValueWith, {}, view.styleWindow);
   yield onValueAdded;
 
   if (blurNewProperty) {
     view.styleDocument.activeElement.blur();
@@ -563,16 +564,17 @@ var setProperty = Task.async(function* (
   yield focusEditableField(view, textProp.editor.valueSpan);
 
   let onPreview = view.once("ruleview-changed");
   if (value === null) {
     EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow);
   } else {
     EventUtils.sendString(value, view.styleWindow);
   }
+  view.throttle.flush();
   yield onPreview;
 
   let onValueDone = view.once("ruleview-changed");
   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
   yield onValueDone;
 
   if (blurNewProperty) {
     view.styleDocument.activeElement.blur();
@@ -785,8 +787,28 @@ function openStyleContextMenuAndGetAllIt
     if (item.submenu) {
       return addItem(item.submenu.items);
     }
     return item;
   }));
 
   return allItems;
 }
+
+/**
+ * Wait for a markupmutation event on the inspector that is for a style modification.
+ * @param {InspectorPanel} inspector
+ * @return {Promise}
+ */
+function waitForStyleModification(inspector) {
+  return new Promise(function (resolve) {
+    function checkForStyleModification(name, mutations) {
+      for (let mutation of mutations) {
+        if (mutation.type === "attributes" && mutation.attributeName === "style") {
+          inspector.off("markupmutation", checkForStyleModification);
+          resolve();
+          return;
+        }
+      }
+    }
+    inspector.on("markupmutation", checkForStyleModification);
+  });
+}
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -62,16 +62,20 @@ var openInspectorSidebarTab = Task.async
  * Open the toolbox, with the inspector tool visible, and the rule-view
  * sidebar tab selected.
  *
  * @return a promise that resolves when the inspector is ready and the rule view
  * is visible and ready
  */
 function openRuleView() {
   return openInspectorSidebarTab("ruleview").then(data => {
+    // Replace the view to use a custom throttle function that can be triggered manually
+    // through an additional ".flush()" property.
+    data.inspector.ruleview.view.throttle = manualThrottle();
+
     return {
       toolbox: data.toolbox,
       inspector: data.inspector,
       testActor: data.testActor,
       view: data.inspector.ruleview.view
     };
   });
 }
@@ -145,8 +149,38 @@ function getNodeFront(selector, {walker}
  */
 var selectNode = Task.async(function* (selector, inspector, reason = "test") {
   info("Selecting the node for '" + selector + "'");
   let nodeFront = yield getNodeFront(selector, inspector);
   let updated = inspector.once("inspector-updated");
   inspector.selection.setNodeFront(nodeFront, reason);
   yield updated;
 });
+
+/**
+ * Create a throttling function that can be manually "flushed". This is to replace the
+ * use of the `throttle` function from `devtools/client/inspector/shared/utils.js`, which
+ * has a setTimeout that can cause intermittents.
+ * @return {Function} This function has the same function signature as throttle, but
+ *                    the property `.flush()` has been added for flushing out any
+ *                    throttled calls.
+ */
+function manualThrottle() {
+  let calls = [];
+
+  function throttle(func, wait, scope) {
+    return function () {
+      let existingCall = calls.find(call => call.func === func);
+      if (existingCall) {
+        existingCall.args = arguments;
+      } else {
+        calls.push({ func, wait, scope, args: arguments });
+      }
+    };
+  }
+
+  throttle.flush = function () {
+    calls.forEach(({func, scope, args}) => func.apply(scope, args));
+    calls = [];
+  };
+
+  return throttle;
+}