dcos-dygraphs
Version:
dygraphs is a fast, flexible open source JavaScript charting library.
249 lines (230 loc) • 7.77 kB
JavaScript
// Copyright (c) 2011 Google, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/**
* @fileoverview Assertions and other code used to test a canvas proxy.
*
* @author konigsberg@google.com (Robert Konigsberg)
*/
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:
* moveTo(p1) -> lineTo(p2)
* moveTo(p2) -> lineTo(p1)
* lineTo(p1) -> lineTo(p2)
* lineTo(p2) -> lineTo(p1)
*
* 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, predicate) {
CanvasAssertions.cleanPathAttrs_(proxy.calls__);
// found = 1 when prior loop found p1.
// found = 2 when prior loop found p2.
var priorFound = 0;
for (var i = 0; i < proxy.calls__.length; i++) {
var call = proxy.calls__[i];
// This disables lineTo -> moveTo pairs.
if (call.name == "moveTo" && priorFound > 0) {
priorFound = 0;
}
var found = 0;
if (call.name == "moveTo" || call.name == "lineTo") {
var matchp1 = CanvasAssertions.matchPixels(p1, call.args);
var matchp2 = CanvasAssertions.matchPixels(p2, call.args);
if (matchp1 || matchp2) {
if (priorFound == 1 && matchp2) {
if (CanvasAssertions.match(predicate, call)) {
return;
}
}
if (priorFound == 2 && matchp1) {
if (CanvasAssertions.match(predicate, call)) {
return;
}
}
found = matchp1 ? 1 : 2;
}
}
priorFound = found;
}
var toString = function(x) {
var s = "{";
for (var prop in x) {
if (x.hasOwnProperty(prop)) {
if (s.length > 1) {
s = s + ", ";
}
s = s + prop + ": " + x[prop];
}
}
return s + "}";
};
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) {
// Expect array of two integers. Assuming the values are within one
// integer unit of each other. This should be tightened down by someone
// 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;
};
/**
* 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;