Merge pull request #674 from danvk/module
[dygraphs.git] / auto_tests / tests / CanvasAssertions.js
index 1ef0573..ef37fb9 100644 (file)
 var CanvasAssertions = {};
 
 /**
+ * Updates path attributes to match fill/stroke operations.
+ *
+ * This sets fillStyle to undefined for stroked paths,
+ * and strokeStyle to undefined for filled paths, to simplify
+ * matchers such as numLinesDrawn.
+ *
+ * @private
+ * @param {Array.<Object>} List of operations.
+ */
+CanvasAssertions.cleanPathAttrs_ = function(calls) {
+  var isStroked = true;
+  for (var i = calls.length - 1; i >= 0; --i) {
+    var call = calls[i];
+    var name = call.name;
+    if (name == 'stroke') {
+      isStroked = true;
+    } else if (name == 'fill') {
+      isStroked = false;
+    } else if (name == 'lineTo') {
+      if (isStroked) {
+        call.properties.fillStyle = undefined;
+      } else {
+        call.properties.strokeStyle = undefined;
+      }
+    }
+  }
+};
+
+
+/**
  * Assert that a line is drawn between the two points
  *
  * This merely looks for one of these four possibilities:
@@ -35,10 +65,12 @@ var CanvasAssertions = {};
  * lineTo(p1) -> lineTo(p2)
  * lineTo(p2) -> lineTo(p1)
  *
- * attrs is meant to be used when you want to track things like
- * color and stroke width.
+ * predicate is meant to be used when you want to track things like
+ * color and stroke width. It can either be a hash of context properties,
+ * or a function that accepts the current call.
  */
-CanvasAssertions.assertLineDrawn = function(proxy, p1, p2, attrs) {
+CanvasAssertions.assertLineDrawn = function(proxy, p1, p2, predicate) {
+  CanvasAssertions.cleanPathAttrs_(proxy.calls__);
   // found = 1 when prior loop found p1.
   // found = 2 when prior loop found p2.
   var priorFound = 0;
@@ -56,12 +88,14 @@ CanvasAssertions.assertLineDrawn = function(proxy, p1, p2, attrs) {
       var matchp2 = CanvasAssertions.matchPixels(p2, call.args);
       if (matchp1 || matchp2) {
         if (priorFound == 1 && matchp2) {
-// TODO -- add property test here  CanvasAssertions.matchAttributes(attrs, call.properties)
-          return;
+          if (CanvasAssertions.match(predicate, call)) {
+            return;
+          }
         }
         if (priorFound == 2 && matchp1) {
-       // TODO -- add property test here  CanvasAssertions.matchAttributes(attrs, call.properties)
-          return;
+          if (CanvasAssertions.match(predicate, call)) {
+            return;
+          }
         }
         found = matchp1 ? 1 : 2;
       }
@@ -81,8 +115,104 @@ CanvasAssertions.assertLineDrawn = function(proxy, p1, p2, attrs) {
     }
     return s + "}";
   };
-  fail("Can't find a line drawn between " + p1 +
-      " and " + p2 + " with attributes " + toString(attrs));
+  throw "Can't find a line drawn between " + p1 +
+      " and " + p2 + " with attributes " + toString(predicate);
+};
+
+/**
+ * Return the lines drawn with specific attributes.
+ *
+ * This merely looks for one of these four possibilities:
+ * moveTo(p1) -> lineTo(p2)
+ * moveTo(p2) -> lineTo(p1)
+ * lineTo(p1) -> lineTo(p2)
+ * lineTo(p2) -> lineTo(p1)
+ *
+ * attrs is meant to be used when you want to track things like
+ * color and stroke width.
+ */
+CanvasAssertions.getLinesDrawn = function(proxy, predicate) {
+  CanvasAssertions.cleanPathAttrs_(proxy.calls__);
+  var lastCall;
+  var lines = [];
+  for (var i = 0; i < proxy.calls__.length; i++) {
+    var call = proxy.calls__[i];
+
+    if (call.name == "lineTo") {
+      if (lastCall != null) {
+        if (CanvasAssertions.match(predicate, call)) {
+          lines.push([lastCall, call]);
+        }
+      }
+    }
+
+    lastCall = (call.name === "lineTo" || call.name === "moveTo") ? call : null;
+  }
+  return lines;
+};
+
+/**
+ * Verifies that every call to context.save() has a matching call to
+ * context.restore().
+ */
+CanvasAssertions.assertBalancedSaveRestore = function(proxy) {
+  var depth = 0;
+  for (var i = 0; i < proxy.calls__.length; i++) {
+    var call = proxy.calls__[i];
+    if (call.name == "save") depth++
+    if (call.name == "restore") {
+      if (depth == 0) {
+        fail("Too many calls to restore()");
+      }
+      depth--;
+    }
+  }
+
+  if (depth > 0) {
+    fail("Missing matching 'context.restore()' calls.");
+  }
+};
+
+/**
+ * Checks how many lines of the given color have been drawn.
+ * @return {Integer} The number of lines of the given color.
+ */
+// TODO(konigsberg): change 'color' to predicate? color is the
+// common case. Possibly allow predicate to be function, hash, or
+// string representing color?
+CanvasAssertions.numLinesDrawn = function(proxy, color) {
+  CanvasAssertions.cleanPathAttrs_(proxy.calls__);
+  var num_lines = 0;
+  var num_potential_calls = 0;
+  for (var i = 0; i < proxy.calls__.length; i++) {
+    var call = proxy.calls__[i];
+    if (call.name == "beginPath") {
+      num_potential_calls = 0;
+    } else if (call.name == "lineTo") {
+      num_potential_calls++;
+    } else if (call.name == "stroke") {
+      // note: Don't simplify these two conditionals into one. The
+      // separation simplifies debugging tricky tests.
+      if (call.properties.strokeStyle == color) {
+        num_lines += num_potential_calls;
+      }
+      num_potential_calls = 0;
+    }
+  }
+  return num_lines;
+};
+
+/**
+ * Asserts that a series of lines are connected. For example,
+ * assertConsecutiveLinesDrawn(proxy, [[x1, y1], [x2, y2], [x3, y3]], predicate)
+ * is shorthand for
+ * assertLineDrawn(proxy, [x1, y1], [x2, y2], predicate)
+ * assertLineDrawn(proxy, [x2, y2], [x3, y3], predicate)
+ */
+CanvasAssertions.assertConsecutiveLinesDrawn = function(proxy, segments, predicate) {
+  for (var i = 0; i < segments.length - 1; i++) {
+    CanvasAssertions.assertLineDrawn(proxy, segments[i], segments[i+1], predicate);
+  }
 }
 
 CanvasAssertions.matchPixels = function(expected, actual) {
@@ -91,13 +221,28 @@ CanvasAssertions.matchPixels = function(expected, actual) {
   // who knows what pixel a value of 5.8888 results in.
   return Math.abs(expected[0] - actual[0]) < 1 &&
       Math.abs(expected[1] - actual[1]) < 1;
-}
+};
 
-CanvasAssertions.matchAttributes = function(expected, actual) {
-  for (var attr in expected) {
-    if (expected.hasOwnProperty(attr) && expected[attr] != actual[attr]) {
-      return false;
+/**
+ * For matching a proxy call against defined conditions.
+ * predicate can either by a hash of items compared against call.properties,
+ * or it can be a function that accepts the call, and returns true or false.
+ * If it's null, this function returns true.
+ */
+CanvasAssertions.match = function(predicate, call) {
+  if (predicate === null) {
+    return true;
+  }
+  if (typeof(predicate) === "function") {
+    return predicate(call);
+  } else {
+    for (var attr in predicate) {
+      if (predicate.hasOwnProperty(attr) && predicate[attr] != call.properties[attr]) {
+        return false;
+      }
     }
   }
   return true;
-}
+};
+
+export default CanvasAssertions;