1 // Copyright (c) 2011 Google, Inc.
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:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
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
22 * @fileoverview Assertions and other code used to test a canvas proxy.
24 * @author konigsberg@google.com (Robert Konigsberg)
27 var CanvasAssertions
= {};
30 * Updates path attributes to match fill/stroke operations.
32 * This sets fillStyle to undefined for stroked paths,
33 * and strokeStyle to undefined for filled paths, to simplify
34 * matchers such as numLinesDrawn.
37 * @param {Array.<Object>} List of operations.
39 CanvasAssertions
.cleanPathAttrs_
= function(calls
) {
41 for (var i
= calls
.length
- 1; i
>= 0; --i
) {
44 if (name
== 'stroke') {
46 } else if (name
== 'fill') {
48 } else if (name
== 'lineTo') {
50 call
.properties
.fillStyle
= undefined
;
52 call
.properties
.strokeStyle
= undefined
;
60 * Assert that a line is drawn between the two points
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)
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.
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.
77 for (var i
= 0; i
< proxy
.calls__
.length
; i
++) {
78 var call
= proxy
.calls__
[i
];
80 // This disables lineTo -> moveTo pairs.
81 if (call
.name
== "moveTo" && priorFound
> 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
)) {
95 if (priorFound
== 2 && matchp1
) {
96 if (CanvasAssertions
.match(predicate
, call
)) {
100 found
= matchp1
? 1 : 2;
106 var toString
= function(x
) {
108 for (var prop
in x
) {
109 if (x
.hasOwnProperty(prop
)) {
113 s
= s
+ prop
+ ": " + x
[prop
];
118 throw "Can't find a line drawn between " + p1
+
119 " and " + p2
+ " with attributes " + toString(predicate
);
123 * Return the lines drawn with specific attributes.
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)
131 * attrs is meant to be used when you want to track things like
132 * color and stroke width.
134 CanvasAssertions
.getLinesDrawn
= function(proxy
, predicate
) {
135 CanvasAssertions
.cleanPathAttrs_(proxy
.calls__
);
138 for (var i
= 0; i
< proxy
.calls__
.length
; i
++) {
139 var call
= proxy
.calls__
[i
];
141 if (call
.name
== "lineTo") {
142 if (lastCall
!= null) {
143 if (CanvasAssertions
.match(predicate
, call
)) {
144 lines
.push([lastCall
, call
]);
149 lastCall
= (call
.name
=== "lineTo" || call
.name
=== "moveTo") ? call
: null;
155 * Verifies that every call to context.save() has a matching call to
158 CanvasAssertions
.assertBalancedSaveRestore
= function(proxy
) {
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") {
165 fail("Too many calls to restore()");
172 fail("Missing matching 'context.restore()' calls.");
177 * Checks how many lines of the given color have been drawn.
178 * @return {Integer} The number of lines of the given color.
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__
);
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
;
199 num_potential_calls
= 0;
206 * Asserts that a series of lines are connected. For example,
207 * assertConsecutiveLinesDrawn(proxy, [[x1, y1], [x2, y2], [x3, y3]], predicate)
209 * assertLineDrawn(proxy, [x1, y1], [x2, y2], predicate)
210 * assertLineDrawn(proxy, [x2, y2], [x3, y3], predicate)
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
);
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;
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.
232 CanvasAssertions
.match
= function(predicate
, call
) {
233 if (predicate
=== null) {
236 if (typeof(predicate
) === "function") {
237 return predicate(call
);
239 for (var attr
in predicate
) {
240 if (predicate
.hasOwnProperty(attr
) && predicate
[attr
] != call
.properties
[attr
]) {