Use "legend" option with "follow" value. Hide legend on deselect.
[dygraphs.git] / auto_tests / tests / CanvasAssertions.js
CommitLineData
718ad8e2 1// Copyright (c) 2011 Google, Inc.
644eff8b
RK
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21/**
22 * @fileoverview Assertions and other code used to test a canvas proxy.
23 *
24 * @author konigsberg@google.com (Robert Konigsberg)
25 */
26
27var CanvasAssertions = {};
28
29/**
30a5cfc6
KW
30 * Updates path attributes to match fill/stroke operations.
31 *
32 * This sets fillStyle to undefined for stroked paths,
33 * and strokeStyle to undefined for filled paths, to simplify
34 * matchers such as numLinesDrawn.
35 *
36 * @private
37 * @param {Array.<Object>} List of operations.
38 */
39CanvasAssertions.cleanPathAttrs_ = function(calls) {
40 var isStroked = true;
41 for (var i = calls.length - 1; i >= 0; --i) {
42 var call = calls[i];
43 var name = call.name;
44 if (name == 'stroke') {
45 isStroked = true;
46 } else if (name == 'fill') {
47 isStroked = false;
48 } else if (name == 'lineTo') {
49 if (isStroked) {
50 call.properties.fillStyle = undefined;
51 } else {
52 call.properties.strokeStyle = undefined;
53 }
54 }
55 }
56};
57
58
59/**
644eff8b
RK
60 * Assert that a line is drawn between the two points
61 *
62 * This merely looks for one of these four possibilities:
63 * moveTo(p1) -> lineTo(p2)
64 * moveTo(p2) -> lineTo(p1)
65 * lineTo(p1) -> lineTo(p2)
66 * lineTo(p2) -> lineTo(p1)
67 *
bbb9a8f3
RK
68 * predicate is meant to be used when you want to track things like
69 * color and stroke width. It can either be a hash of context properties,
70 * or a function that accepts the current call.
644eff8b 71 */
bbb9a8f3 72CanvasAssertions.assertLineDrawn = function(proxy, p1, p2, predicate) {
30a5cfc6 73 CanvasAssertions.cleanPathAttrs_(proxy.calls__);
644eff8b
RK
74 // found = 1 when prior loop found p1.
75 // found = 2 when prior loop found p2.
76 var priorFound = 0;
7165f97b
RK
77 for (var i = 0; i < proxy.calls__.length; i++) {
78 var call = proxy.calls__[i];
644eff8b
RK
79
80 // This disables lineTo -> moveTo pairs.
81 if (call.name == "moveTo" && priorFound > 0) {
82 priorFound = 0;
83 }
84
85 var found = 0;
86 if (call.name == "moveTo" || call.name == "lineTo") {
87 var matchp1 = CanvasAssertions.matchPixels(p1, call.args);
88 var matchp2 = CanvasAssertions.matchPixels(p2, call.args);
89 if (matchp1 || matchp2) {
90 if (priorFound == 1 && matchp2) {
bbb9a8f3
RK
91 if (CanvasAssertions.match(predicate, call)) {
92 return;
93 }
644eff8b
RK
94 }
95 if (priorFound == 2 && matchp1) {
30a5cfc6 96 if (CanvasAssertions.match(predicate, call)) {
bbb9a8f3
RK
97 return;
98 }
644eff8b
RK
99 }
100 found = matchp1 ? 1 : 2;
101 }
102 }
103 priorFound = found;
104 }
105
106 var toString = function(x) {
107 var s = "{";
108 for (var prop in x) {
109 if (x.hasOwnProperty(prop)) {
110 if (s.length > 1) {
111 s = s + ", ";
112 }
113 s = s + prop + ": " + x[prop];
114 }
115 }
116 return s + "}";
117 };
118 fail("Can't find a line drawn between " + p1 +
71f94616 119 " and " + p2 + " with attributes " + toString(predicate));
6278f6fe
DV
120};
121
122/**
8337da0a
RK
123 * Return the lines drawn with specific attributes.
124 *
125 * This merely looks for one of these four possibilities:
126 * moveTo(p1) -> lineTo(p2)
127 * moveTo(p2) -> lineTo(p1)
128 * lineTo(p1) -> lineTo(p2)
129 * lineTo(p2) -> lineTo(p1)
130 *
131 * attrs is meant to be used when you want to track things like
132 * color and stroke width.
133 */
bbb9a8f3 134CanvasAssertions.getLinesDrawn = function(proxy, predicate) {
30a5cfc6 135 CanvasAssertions.cleanPathAttrs_(proxy.calls__);
8337da0a
RK
136 var lastCall;
137 var lines = [];
138 for (var i = 0; i < proxy.calls__.length; i++) {
139 var call = proxy.calls__[i];
140
141 if (call.name == "lineTo") {
142 if (lastCall != null) {
71f94616 143 if (CanvasAssertions.match(predicate, call)) {
bbb9a8f3
RK
144 lines.push([lastCall, call]);
145 }
8337da0a
RK
146 }
147 }
148
149 lastCall = (call.name === "lineTo" || call.name === "moveTo") ? call : null;
150 }
151 return lines;
152};
153
154/**
6278f6fe
DV
155 * Verifies that every call to context.save() has a matching call to
156 * context.restore().
157 */
158CanvasAssertions.assertBalancedSaveRestore = function(proxy) {
159 var depth = 0;
160 for (var i = 0; i < proxy.calls__.length; i++) {
161 var call = proxy.calls__[i];
162 if (call.name == "save") depth++
163 if (call.name == "restore") {
164 if (depth == 0) {
165 fail("Too many calls to restore()");
166 }
167 depth--;
168 }
169 }
170
171 if (depth > 0) {
172 fail("Missing matching 'context.restore()' calls.");
173 }
174};
644eff8b 175
063e83ba
DV
176/**
177 * Checks how many lines of the given color have been drawn.
178 * @return {Integer} The number of lines of the given color.
179 */
bbb9a8f3
RK
180// TODO(konigsberg): change 'color' to predicate? color is the
181// common case. Possibly allow predicate to be function, hash, or
182// string representing color?
063e83ba 183CanvasAssertions.numLinesDrawn = function(proxy, color) {
30a5cfc6 184 CanvasAssertions.cleanPathAttrs_(proxy.calls__);
063e83ba 185 var num_lines = 0;
32ddef6e 186 var num_potential_calls = 0;
063e83ba
DV
187 for (var i = 0; i < proxy.calls__.length; i++) {
188 var call = proxy.calls__[i];
32ddef6e
DV
189 if (call.name == "beginPath") {
190 num_potential_calls = 0;
191 } else if (call.name == "lineTo") {
192 num_potential_calls++;
193 } else if (call.name == "stroke") {
194 // note: Don't simplify these two conditionals into one. The
195 // separation simplifies debugging tricky tests.
32c4d92b 196 if (call.properties.strokeStyle == color) {
32ddef6e 197 num_lines += num_potential_calls;
32c4d92b 198 }
32ddef6e 199 num_potential_calls = 0;
063e83ba
DV
200 }
201 }
202 return num_lines;
6278f6fe 203};
063e83ba 204
71f94616
RK
205/**
206 * Asserts that a series of lines are connected. For example,
207 * assertConsecutiveLinesDrawn(proxy, [[x1, y1], [x2, y2], [x3, y3]], predicate)
208 * is shorthand for
209 * assertLineDrawn(proxy, [x1, y1], [x2, y2], predicate)
210 * assertLineDrawn(proxy, [x2, y2], [x3, y3], predicate)
211 */
212CanvasAssertions.assertConsecutiveLinesDrawn = function(proxy, segments, predicate) {
213 for (var i = 0; i < segments.length - 1; i++) {
214 CanvasAssertions.assertLineDrawn(proxy, segments[i], segments[i+1], predicate);
215 }
216}
217
644eff8b
RK
218CanvasAssertions.matchPixels = function(expected, actual) {
219 // Expect array of two integers. Assuming the values are within one
220 // integer unit of each other. This should be tightened down by someone
221 // who knows what pixel a value of 5.8888 results in.
222 return Math.abs(expected[0] - actual[0]) < 1 &&
223 Math.abs(expected[1] - actual[1]) < 1;
6278f6fe 224};
644eff8b 225
bbb9a8f3
RK
226/**
227 * For matching a proxy call against defined conditions.
228 * predicate can either by a hash of items compared against call.properties,
229 * or it can be a function that accepts the call, and returns true or false.
71f94616 230 * If it's null, this function returns true.
bbb9a8f3
RK
231 */
232CanvasAssertions.match = function(predicate, call) {
71f94616
RK
233 if (predicate === null) {
234 return true;
235 }
bbb9a8f3
RK
236 if (typeof(predicate) === "function") {
237 return predicate(call);
238 } else {
239 for (var attr in predicate) {
240 if (predicate.hasOwnProperty(attr) && predicate[attr] != call.properties[attr]) {
241 return false;
242 }
644eff8b
RK
243 }
244 }
245 return true;
6278f6fe 246};