Commit | Line | Data |
---|---|---|
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 | ||
27 | var 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 | */ | |
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 | /** | |
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 | 72 | CanvasAssertions.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 | }; | |
89fdcedb DV |
118 | throw "Can't find a line drawn between " + p1 + |
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 | 134 | CanvasAssertions.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 | */ | |
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 | }; | |
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 | 183 | CanvasAssertions.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 | */ | |
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 | ||
644eff8b RK |
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; | |
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 | */ |
232 | CanvasAssertions.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 | }; |
e8c70e4e DV |
247 | |
248 | export default CanvasAssertions; |