Merge pull request #673 from danvk/track-code-size
[dygraphs.git] / auto_tests / tests / CanvasAssertions.js
1 // Copyright (c) 2011 Google, Inc.
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
27 var CanvasAssertions = {};
28
29 /**
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 */
39 CanvasAssertions.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 /**
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 *
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.
71 */
72 CanvasAssertions.assertLineDrawn = function(proxy, p1, p2, predicate) {
73 CanvasAssertions.cleanPathAttrs_(proxy.calls__);
74 // found = 1 when prior loop found p1.
75 // found = 2 when prior loop found p2.
76 var priorFound = 0;
77 for (var i = 0; i < proxy.calls__.length; i++) {
78 var call = proxy.calls__[i];
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) {
91 if (CanvasAssertions.match(predicate, call)) {
92 return;
93 }
94 }
95 if (priorFound == 2 && matchp1) {
96 if (CanvasAssertions.match(predicate, call)) {
97 return;
98 }
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 throw "Can't find a line drawn between " + p1 +
119 " and " + p2 + " with attributes " + toString(predicate);
120 };
121
122 /**
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 */
134 CanvasAssertions.getLinesDrawn = function(proxy, predicate) {
135 CanvasAssertions.cleanPathAttrs_(proxy.calls__);
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) {
143 if (CanvasAssertions.match(predicate, call)) {
144 lines.push([lastCall, call]);
145 }
146 }
147 }
148
149 lastCall = (call.name === "lineTo" || call.name === "moveTo") ? call : null;
150 }
151 return lines;
152 };
153
154 /**
155 * Verifies that every call to context.save() has a matching call to
156 * context.restore().
157 */
158 CanvasAssertions.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 };
175
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 */
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?
183 CanvasAssertions.numLinesDrawn = function(proxy, color) {
184 CanvasAssertions.cleanPathAttrs_(proxy.calls__);
185 var num_lines = 0;
186 var num_potential_calls = 0;
187 for (var i = 0; i < proxy.calls__.length; i++) {
188 var call = proxy.calls__[i];
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.
196 if (call.properties.strokeStyle == color) {
197 num_lines += num_potential_calls;
198 }
199 num_potential_calls = 0;
200 }
201 }
202 return num_lines;
203 };
204
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 */
212 CanvasAssertions.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
218 CanvasAssertions.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;
224 };
225
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.
230 * If it's null, this function returns true.
231 */
232 CanvasAssertions.match = function(predicate, call) {
233 if (predicate === null) {
234 return true;
235 }
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 }
243 }
244 }
245 return true;
246 };