dom/smil/test/smilTestUtils.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 sts=2 et: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 // Note: Class syntax roughly based on:
michael@0 8 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Inheritance
michael@0 9 const SVG_NS = "http://www.w3.org/2000/svg";
michael@0 10 const XLINK_NS = "http://www.w3.org/1999/xlink";
michael@0 11
michael@0 12 const MPATH_TARGET_ID = "smilTestUtilsTestingPath";
michael@0 13
michael@0 14 function extend(child, supertype)
michael@0 15 {
michael@0 16 child.prototype.__proto__ = supertype.prototype;
michael@0 17 }
michael@0 18
michael@0 19 // General Utility Methods
michael@0 20 var SMILUtil =
michael@0 21 {
michael@0 22 // Returns the first matched <svg> node in the document
michael@0 23 getSVGRoot : function()
michael@0 24 {
michael@0 25 return SMILUtil.getFirstElemWithTag("svg");
michael@0 26 },
michael@0 27
michael@0 28 // Returns the first element in the document with the matching tag
michael@0 29 getFirstElemWithTag : function(aTargetTag)
michael@0 30 {
michael@0 31 var elemList = document.getElementsByTagName(aTargetTag);
michael@0 32 return (elemList.length == 0 ? null : elemList[0]);
michael@0 33 },
michael@0 34
michael@0 35 // Simple wrapper for getComputedStyle
michael@0 36 getComputedStyleSimple: function(elem, prop)
michael@0 37 {
michael@0 38 return window.getComputedStyle(elem, null).getPropertyValue(prop);
michael@0 39 },
michael@0 40
michael@0 41 getAttributeValue: function(elem, attr)
michael@0 42 {
michael@0 43 if (attr.attrName == SMILUtil.getMotionFakeAttributeName()) {
michael@0 44 // Fake motion "attribute" -- "computed value" is the element's CTM
michael@0 45 return elem.getCTM();
michael@0 46 }
michael@0 47 if (attr.attrType == "CSS") {
michael@0 48 return SMILUtil.getComputedStyleWrapper(elem, attr.attrName);
michael@0 49 }
michael@0 50 if (attr.attrType == "XML") {
michael@0 51 // XXXdholbert This is appropriate for mapped attributes, but not
michael@0 52 // for other attributes.
michael@0 53 return SMILUtil.getComputedStyleWrapper(elem, attr.attrName);
michael@0 54 }
michael@0 55 },
michael@0 56
michael@0 57 // Smart wrapper for getComputedStyle, which will generate a "fake" computed
michael@0 58 // style for recognized shorthand properties (font, overflow, marker)
michael@0 59 getComputedStyleWrapper : function(elem, propName)
michael@0 60 {
michael@0 61 // Special cases for shorthand properties (which aren't directly queriable
michael@0 62 // via getComputedStyle)
michael@0 63 var computedStyle;
michael@0 64 if (propName == "font") {
michael@0 65 var subProps = ["font-style", "font-variant", "font-weight",
michael@0 66 "font-size", "line-height", "font-family"];
michael@0 67 for (var i in subProps) {
michael@0 68 var subPropStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]);
michael@0 69 if (subPropStyle) {
michael@0 70 if (subProps[i] == "line-height") {
michael@0 71 // There needs to be a "/" before line-height
michael@0 72 subPropStyle = "/ " + subPropStyle;
michael@0 73 }
michael@0 74 if (!computedStyle) {
michael@0 75 computedStyle = subPropStyle;
michael@0 76 } else {
michael@0 77 computedStyle = computedStyle + " " + subPropStyle;
michael@0 78 }
michael@0 79 }
michael@0 80 }
michael@0 81 } else if (propName == "marker") {
michael@0 82 var subProps = ["marker-end", "marker-mid", "marker-start"];
michael@0 83 for (var i in subProps) {
michael@0 84 if (!computedStyle) {
michael@0 85 computedStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]);
michael@0 86 } else {
michael@0 87 is(computedStyle, SMILUtil.getComputedStyleSimple(elem, subProps[i]),
michael@0 88 "marker sub-properties should match each other " +
michael@0 89 "(they shouldn't be individually set)");
michael@0 90 }
michael@0 91 }
michael@0 92 } else if (propName == "overflow") {
michael@0 93 var subProps = ["overflow-x", "overflow-y"];
michael@0 94 for (var i in subProps) {
michael@0 95 if (!computedStyle) {
michael@0 96 computedStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]);
michael@0 97 } else {
michael@0 98 is(computedStyle, SMILUtil.getComputedStyleSimple(elem, subProps[i]),
michael@0 99 "overflow sub-properties should match each other " +
michael@0 100 "(they shouldn't be individually set)");
michael@0 101 }
michael@0 102 }
michael@0 103 } else {
michael@0 104 computedStyle = SMILUtil.getComputedStyleSimple(elem, propName);
michael@0 105 }
michael@0 106 return computedStyle;
michael@0 107 },
michael@0 108
michael@0 109 // This method hides (i.e. sets "display: none" on) all of the given node's
michael@0 110 // descendents. It also hides the node itself, if requested.
michael@0 111 hideSubtree : function(node, hideNodeItself, useXMLAttribute)
michael@0 112 {
michael@0 113 // Hide node, if requested
michael@0 114 if (hideNodeItself) {
michael@0 115 if (useXMLAttribute) {
michael@0 116 if (node.setAttribute) {
michael@0 117 node.setAttribute("display", "none");
michael@0 118 }
michael@0 119 } else if (node.style) {
michael@0 120 node.style.display = "none";
michael@0 121 }
michael@0 122 }
michael@0 123
michael@0 124 // Hide node's descendents
michael@0 125 var child = node.firstChild;
michael@0 126 while (child) {
michael@0 127 SMILUtil.hideSubtree(child, true, useXMLAttribute);
michael@0 128 child = child.nextSibling;
michael@0 129 }
michael@0 130 },
michael@0 131
michael@0 132 getMotionFakeAttributeName : function() {
michael@0 133 return "_motion";
michael@0 134 },
michael@0 135 };
michael@0 136
michael@0 137
michael@0 138 var CTMUtil =
michael@0 139 {
michael@0 140 CTM_COMPONENTS_ALL : ["a", "b", "c", "d", "e", "f"],
michael@0 141 CTM_COMPONENTS_ROTATE : ["a", "b", "c", "d" ],
michael@0 142
michael@0 143 // Function to generate a CTM Matrix from a "summary"
michael@0 144 // (a 3-tuple containing [tX, tY, theta])
michael@0 145 generateCTM : function(aCtmSummary)
michael@0 146 {
michael@0 147 if (!aCtmSummary || aCtmSummary.length != 3) {
michael@0 148 ok(false, "Unexpected CTM summary tuple length: " + aCtmSummary.length);
michael@0 149 }
michael@0 150 var tX = aCtmSummary[0];
michael@0 151 var tY = aCtmSummary[1];
michael@0 152 var theta = aCtmSummary[2];
michael@0 153 var cosTheta = Math.cos(theta);
michael@0 154 var sinTheta = Math.sin(theta);
michael@0 155 var newCtm = { a : cosTheta, c: -sinTheta, e: tX,
michael@0 156 b : sinTheta, d: cosTheta, f: tY };
michael@0 157 return newCtm;
michael@0 158 },
michael@0 159
michael@0 160 /// Helper for isCtmEqual
michael@0 161 isWithinDelta : function(aTestVal, aExpectedVal, aErrMsg, aIsTodo) {
michael@0 162 var testFunc = aIsTodo ? todo : ok;
michael@0 163 const delta = 0.00001; // allowing margin of error = 10^-5
michael@0 164 ok(aTestVal >= aExpectedVal - delta &&
michael@0 165 aTestVal <= aExpectedVal + delta,
michael@0 166 aErrMsg + " | got: " + aTestVal + ", expected: " + aExpectedVal);
michael@0 167 },
michael@0 168
michael@0 169 assertCTMEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck,
michael@0 170 aErrMsg, aIsTodo) {
michael@0 171 var foundCTMDifference = false;
michael@0 172 for (var j in aComponentsToCheck) {
michael@0 173 var curComponent = aComponentsToCheck[j];
michael@0 174 if (!aIsTodo) {
michael@0 175 CTMUtil.isWithinDelta(aLeftCtm[curComponent], aRightCtm[curComponent],
michael@0 176 aErrMsg + " | component: " + curComponent, false);
michael@0 177 } else if (aLeftCtm[curComponent] != aRightCtm[curComponent]) {
michael@0 178 foundCTMDifference = true;
michael@0 179 }
michael@0 180 }
michael@0 181
michael@0 182 if (aIsTodo) {
michael@0 183 todo(!foundCTMDifference, aErrMsg + " | (currently marked todo)");
michael@0 184 }
michael@0 185 },
michael@0 186
michael@0 187 assertCTMNotEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck,
michael@0 188 aErrMsg, aIsTodo) {
michael@0 189 // CTM should not match initial one
michael@0 190 var foundCTMDifference = false;
michael@0 191 for (var j in aComponentsToCheck) {
michael@0 192 var curComponent = aComponentsToCheck[j];
michael@0 193 if (aLeftCtm[curComponent] != aRightCtm[curComponent]) {
michael@0 194 foundCTMDifference = true;
michael@0 195 break; // We found a difference, as expected. Success!
michael@0 196 }
michael@0 197 }
michael@0 198
michael@0 199 if (aIsTodo) {
michael@0 200 todo(foundCTMDifference, aErrMsg + " | (currently marked todo)");
michael@0 201 } else {
michael@0 202 ok(foundCTMDifference, aErrMsg);
michael@0 203 }
michael@0 204 },
michael@0 205 };
michael@0 206
michael@0 207
michael@0 208 // Wrapper for timing information
michael@0 209 function SMILTimingData(aBegin, aDur)
michael@0 210 {
michael@0 211 this._begin = aBegin;
michael@0 212 this._dur = aDur;
michael@0 213 }
michael@0 214 SMILTimingData.prototype =
michael@0 215 {
michael@0 216 _begin: null,
michael@0 217 _dur: null,
michael@0 218 getBeginTime : function() { return this._begin; },
michael@0 219 getDur : function() { return this._dur; },
michael@0 220 getEndTime : function() { return this._begin + this._dur; },
michael@0 221 getFractionalTime : function(aPortion)
michael@0 222 {
michael@0 223 return this._begin + aPortion * this._dur;
michael@0 224 },
michael@0 225 }
michael@0 226
michael@0 227 /**
michael@0 228 * Attribute: a container for information about an attribute we'll
michael@0 229 * attempt to animate with SMIL in our tests.
michael@0 230 *
michael@0 231 * See also the factory methods below: NonAnimatableAttribute(),
michael@0 232 * NonAdditiveAttribute(), and AdditiveAttribute().
michael@0 233 *
michael@0 234 * @param aAttrName The name of the attribute
michael@0 235 * @param aAttrType The type of the attribute ("CSS" vs "XML")
michael@0 236 * @param aTargetTag The name of an element that this attribute could be
michael@0 237 * applied to.
michael@0 238 * @param aIsAnimatable A bool indicating whether this attribute is defined as
michael@0 239 * animatable in the SVG spec.
michael@0 240 * @param aIsAdditive A bool indicating whether this attribute is defined as
michael@0 241 * additive (i.e. supports "by" animation) in the SVG spec.
michael@0 242 */
michael@0 243 function Attribute(aAttrName, aAttrType, aTargetTag,
michael@0 244 aIsAnimatable, aIsAdditive)
michael@0 245 {
michael@0 246 this.attrName = aAttrName;
michael@0 247 this.attrType = aAttrType;
michael@0 248 this.targetTag = aTargetTag;
michael@0 249 this.isAnimatable = aIsAnimatable;
michael@0 250 this.isAdditive = aIsAdditive;
michael@0 251 }
michael@0 252 Attribute.prototype =
michael@0 253 {
michael@0 254 // Member variables
michael@0 255 attrName : null,
michael@0 256 attrType : null,
michael@0 257 isAnimatable : null,
michael@0 258 testcaseList : null,
michael@0 259 };
michael@0 260
michael@0 261 // Generators for Attribute objects. These allow lists of attribute
michael@0 262 // definitions to be more human-readible than if we were using Attribute() with
michael@0 263 // boolean flags, e.g. "Attribute(..., true, true), Attribute(..., true, false)
michael@0 264 function NonAnimatableAttribute(aAttrName, aAttrType, aTargetTag)
michael@0 265 {
michael@0 266 return new Attribute(aAttrName, aAttrType, aTargetTag, false, false);
michael@0 267 }
michael@0 268 function NonAdditiveAttribute(aAttrName, aAttrType, aTargetTag)
michael@0 269 {
michael@0 270 return new Attribute(aAttrName, aAttrType, aTargetTag, true, false);
michael@0 271 }
michael@0 272 function AdditiveAttribute(aAttrName, aAttrType, aTargetTag)
michael@0 273 {
michael@0 274 return new Attribute(aAttrName, aAttrType, aTargetTag, true, true);
michael@0 275 }
michael@0 276
michael@0 277 /**
michael@0 278 * TestcaseBundle: a container for a group of tests for a particular attribute
michael@0 279 *
michael@0 280 * @param aAttribute An Attribute object for the attribute
michael@0 281 * @param aTestcaseList An array of AnimTestcase objects
michael@0 282 */
michael@0 283 function TestcaseBundle(aAttribute, aTestcaseList, aSkipReason)
michael@0 284 {
michael@0 285 this.animatedAttribute = aAttribute;
michael@0 286 this.testcaseList = aTestcaseList;
michael@0 287 this.skipReason = aSkipReason;
michael@0 288 }
michael@0 289 TestcaseBundle.prototype =
michael@0 290 {
michael@0 291 // Member variables
michael@0 292 animatedAttribute : null,
michael@0 293 testcaseList : null,
michael@0 294 skipReason : null,
michael@0 295
michael@0 296 // Methods
michael@0 297 go : function(aTimingData) {
michael@0 298 if (this.skipReason) {
michael@0 299 todo(false, "Skipping a bundle for '" + this.animatedAttribute.attrName +
michael@0 300 "' because: " + this.skipReason);
michael@0 301 } else {
michael@0 302 // Sanity Check: Bundle should have > 0 testcases
michael@0 303 if (!this.testcaseList || !this.testcaseList.length) {
michael@0 304 ok(false, "a bundle for '" + this.animatedAttribute.attrName +
michael@0 305 "' has no testcases");
michael@0 306 }
michael@0 307
michael@0 308 var targetElem =
michael@0 309 SMILUtil.getFirstElemWithTag(this.animatedAttribute.targetTag);
michael@0 310
michael@0 311 if (!targetElem) {
michael@0 312 ok(false, "Error: can't find an element of type '" +
michael@0 313 this.animatedAttribute.targetTag +
michael@0 314 "', so I can't test property '" +
michael@0 315 this.animatedAttribute.attrName + "'");
michael@0 316 return;
michael@0 317 }
michael@0 318
michael@0 319 for (var testcaseIdx in this.testcaseList) {
michael@0 320 var testcase = this.testcaseList[testcaseIdx];
michael@0 321 if (testcase.skipReason) {
michael@0 322 todo(false, "Skipping a testcase for '" +
michael@0 323 this.animatedAttribute.attrName +
michael@0 324 "' because: " + testcase.skipReason);
michael@0 325 } else {
michael@0 326 testcase.runTest(targetElem, this.animatedAttribute,
michael@0 327 aTimingData, false);
michael@0 328 testcase.runTest(targetElem, this.animatedAttribute,
michael@0 329 aTimingData, true);
michael@0 330 }
michael@0 331 }
michael@0 332 }
michael@0 333 },
michael@0 334 };
michael@0 335
michael@0 336 /**
michael@0 337 * AnimTestcase: an abstract class that represents an animation testcase.
michael@0 338 * (e.g. a set of "from"/"to" values to test)
michael@0 339 */
michael@0 340 function AnimTestcase() {} // abstract => no constructor
michael@0 341 AnimTestcase.prototype =
michael@0 342 {
michael@0 343 // Member variables
michael@0 344 _animElementTagName : "animate", // Can be overridden for e.g. animateColor
michael@0 345 computedValMap : null,
michael@0 346 skipReason : null,
michael@0 347
michael@0 348 // Methods
michael@0 349 /**
michael@0 350 * runTest: Runs this AnimTestcase
michael@0 351 *
michael@0 352 * @param aTargetElem The node to be targeted in our test animation.
michael@0 353 * @param aTargetAttr An Attribute object representing the attribute
michael@0 354 * to be targeted in our test animation.
michael@0 355 * @param aTimeData A SMILTimingData object with timing information for
michael@0 356 * our test animation.
michael@0 357 * @param aIsFreeze If true, indicates that our test animation should use
michael@0 358 * fill="freeze"; otherwise, we'll default to fill="remove".
michael@0 359 */
michael@0 360 runTest : function(aTargetElem, aTargetAttr, aTimeData, aIsFreeze)
michael@0 361 {
michael@0 362 // SANITY CHECKS
michael@0 363 if (!SMILUtil.getSVGRoot().animationsPaused()) {
michael@0 364 ok(false, "Should start each test with animations paused");
michael@0 365 }
michael@0 366 if (SMILUtil.getSVGRoot().getCurrentTime() != 0) {
michael@0 367 ok(false, "Should start each test at time = 0");
michael@0 368 }
michael@0 369
michael@0 370 // SET UP
michael@0 371 // Cache initial computed value
michael@0 372 var baseVal = SMILUtil.getAttributeValue(aTargetElem, aTargetAttr);
michael@0 373
michael@0 374 // Create & append animation element
michael@0 375 var anim = this.setupAnimationElement(aTargetAttr, aTimeData, aIsFreeze);
michael@0 376 aTargetElem.appendChild(anim);
michael@0 377
michael@0 378 // Build a list of [seek-time, expectedValue, errorMessage] triplets
michael@0 379 var seekList = this.buildSeekList(aTargetAttr, baseVal, aTimeData, aIsFreeze);
michael@0 380
michael@0 381 // DO THE ACTUAL TESTING
michael@0 382 this.seekAndTest(seekList, aTargetElem, aTargetAttr);
michael@0 383
michael@0 384 // CLEAN UP
michael@0 385 aTargetElem.removeChild(anim);
michael@0 386 SMILUtil.getSVGRoot().setCurrentTime(0);
michael@0 387 },
michael@0 388
michael@0 389 // HELPER FUNCTIONS
michael@0 390 // setupAnimationElement: <animate> element
michael@0 391 // Subclasses should extend this parent method
michael@0 392 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
michael@0 393 {
michael@0 394 var animElement = document.createElementNS(SVG_NS,
michael@0 395 this._animElementTagName);
michael@0 396 animElement.setAttribute("attributeName", aAnimAttr.attrName);
michael@0 397 animElement.setAttribute("attributeType", aAnimAttr.attrType);
michael@0 398 animElement.setAttribute("begin", aTimeData.getBeginTime());
michael@0 399 animElement.setAttribute("dur", aTimeData.getDur());
michael@0 400 if (aIsFreeze) {
michael@0 401 animElement.setAttribute("fill", "freeze");
michael@0 402 }
michael@0 403 return animElement;
michael@0 404 },
michael@0 405
michael@0 406 buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
michael@0 407 {
michael@0 408 if (!aAnimAttr.isAnimatable) {
michael@0 409 return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData,
michael@0 410 "defined as non-animatable in SVG spec");
michael@0 411 }
michael@0 412 if (this.computedValMap.noEffect) {
michael@0 413 return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData,
michael@0 414 "testcase specified to have no effect");
michael@0 415 }
michael@0 416 return this.buildSeekListAnimated(aAnimAttr, aBaseVal,
michael@0 417 aTimeData, aIsFreeze)
michael@0 418 },
michael@0 419
michael@0 420 seekAndTest : function(aSeekList, aTargetElem, aTargetAttr)
michael@0 421 {
michael@0 422 var svg = document.getElementById("svg");
michael@0 423 for (var i in aSeekList) {
michael@0 424 var entry = aSeekList[i];
michael@0 425 SMILUtil.getSVGRoot().setCurrentTime(entry[0]);
michael@0 426 is(SMILUtil.getAttributeValue(aTargetElem, aTargetAttr),
michael@0 427 entry[1], entry[2]);
michael@0 428 }
michael@0 429 },
michael@0 430
michael@0 431 // methods that expect to be overridden in subclasses
michael@0 432 buildSeekListStatic : function(aAnimAttr, aBaseVal,
michael@0 433 aTimeData, aReasonStatic) {},
michael@0 434 buildSeekListAnimated : function(aAnimAttr, aBaseVal,
michael@0 435 aTimeData, aIsFreeze) {},
michael@0 436 };
michael@0 437
michael@0 438
michael@0 439 // Abstract parent class to share code between from-to & from-by testcases.
michael@0 440 function AnimTestcaseFrom() {} // abstract => no constructor
michael@0 441 AnimTestcaseFrom.prototype =
michael@0 442 {
michael@0 443 // Member variables
michael@0 444 from : null,
michael@0 445
michael@0 446 // Methods
michael@0 447 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
michael@0 448 {
michael@0 449 // Call super, and then add my own customization
michael@0 450 var animElem = AnimTestcase.prototype.setupAnimationElement.apply(this,
michael@0 451 [aAnimAttr, aTimeData, aIsFreeze]);
michael@0 452 animElem.setAttribute("from", this.from)
michael@0 453 return animElem;
michael@0 454 },
michael@0 455
michael@0 456 buildSeekListStatic : function(aAnimAttr, aBaseVal, aTimeData, aReasonStatic)
michael@0 457 {
michael@0 458 var seekList = new Array();
michael@0 459 var msgPrefix = aAnimAttr.attrName +
michael@0 460 ": shouldn't be affected by animation ";
michael@0 461 seekList.push([aTimeData.getBeginTime(), aBaseVal,
michael@0 462 msgPrefix + "(at animation begin) - " + aReasonStatic]);
michael@0 463 seekList.push([aTimeData.getFractionalTime(1/2), aBaseVal,
michael@0 464 msgPrefix + "(at animation mid) - " + aReasonStatic]);
michael@0 465 seekList.push([aTimeData.getEndTime(), aBaseVal,
michael@0 466 msgPrefix + "(at animation end) - " + aReasonStatic]);
michael@0 467 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), aBaseVal,
michael@0 468 msgPrefix + "(after animation end) - " + aReasonStatic]);
michael@0 469 return seekList;
michael@0 470 },
michael@0 471
michael@0 472 buildSeekListAnimated : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
michael@0 473 {
michael@0 474 var seekList = new Array();
michael@0 475 var msgPrefix = aAnimAttr.attrName + ": ";
michael@0 476 if (aTimeData.getBeginTime() > 0.1) {
michael@0 477 seekList.push([aTimeData.getBeginTime() - 0.1,
michael@0 478 aBaseVal,
michael@0 479 msgPrefix + "checking that base value is set " +
michael@0 480 "before start of animation"]);
michael@0 481 }
michael@0 482
michael@0 483 seekList.push([aTimeData.getBeginTime(),
michael@0 484 this.computedValMap.fromComp || this.from,
michael@0 485 msgPrefix + "checking that 'from' value is set " +
michael@0 486 "at start of animation"]);
michael@0 487 seekList.push([aTimeData.getFractionalTime(1/2),
michael@0 488 this.computedValMap.midComp ||
michael@0 489 this.computedValMap.toComp || this.to,
michael@0 490 msgPrefix + "checking value halfway through animation"]);
michael@0 491
michael@0 492 var finalMsg;
michael@0 493 var expectedEndVal;
michael@0 494 if (aIsFreeze) {
michael@0 495 expectedEndVal = this.computedValMap.toComp || this.to;
michael@0 496 finalMsg = msgPrefix + "[freeze-mode] checking that final value is set ";
michael@0 497 } else {
michael@0 498 expectedEndVal = aBaseVal;
michael@0 499 finalMsg = msgPrefix +
michael@0 500 "[remove-mode] checking that animation is cleared ";
michael@0 501 }
michael@0 502 seekList.push([aTimeData.getEndTime(),
michael@0 503 expectedEndVal, finalMsg + "at end of animation"]);
michael@0 504 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(),
michael@0 505 expectedEndVal, finalMsg + "after end of animation"]);
michael@0 506 return seekList;
michael@0 507 },
michael@0 508 }
michael@0 509 extend(AnimTestcaseFrom, AnimTestcase);
michael@0 510
michael@0 511 /*
michael@0 512 * A testcase for a simple "from-to" animation
michael@0 513 * @param aFrom The 'from' value
michael@0 514 * @param aTo The 'to' value
michael@0 515 * @param aComputedValMap A hash-map that contains some computed values,
michael@0 516 * if they're needed, as follows:
michael@0 517 * - fromComp: Computed value version of |aFrom| (if different from |aFrom|)
michael@0 518 * - midComp: Computed value that we expect to visit halfway through the
michael@0 519 * animation (if different from |aTo|)
michael@0 520 * - toComp: Computed value version of |aTo| (if different from |aTo|)
michael@0 521 * - noEffect: Special flag -- if set, indicates that this testcase is
michael@0 522 * expected to have no effect on the computed value. (e.g. the
michael@0 523 * given values are invalid.)
michael@0 524 * @param aSkipReason If this test-case is known to currently fail, this
michael@0 525 * parameter should be a string explaining why.
michael@0 526 * Otherwise, this value should be null (or omitted).
michael@0 527 *
michael@0 528 */
michael@0 529 function AnimTestcaseFromTo(aFrom, aTo, aComputedValMap, aSkipReason)
michael@0 530 {
michael@0 531 this.from = aFrom;
michael@0 532 this.to = aTo;
michael@0 533 this.computedValMap = aComputedValMap || {}; // Let aComputedValMap be omitted
michael@0 534 this.skipReason = aSkipReason;
michael@0 535 }
michael@0 536 AnimTestcaseFromTo.prototype =
michael@0 537 {
michael@0 538 // Member variables
michael@0 539 to : null,
michael@0 540
michael@0 541 // Methods
michael@0 542 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
michael@0 543 {
michael@0 544 // Call super, and then add my own customization
michael@0 545 var animElem = AnimTestcaseFrom.prototype.setupAnimationElement.apply(this,
michael@0 546 [aAnimAttr, aTimeData, aIsFreeze]);
michael@0 547 animElem.setAttribute("to", this.to)
michael@0 548 return animElem;
michael@0 549 },
michael@0 550 }
michael@0 551 extend(AnimTestcaseFromTo, AnimTestcaseFrom);
michael@0 552
michael@0 553 /*
michael@0 554 * A testcase for a simple "from-by" animation.
michael@0 555 *
michael@0 556 * @param aFrom The 'from' value
michael@0 557 * @param aBy The 'by' value
michael@0 558 * @param aComputedValMap A hash-map that contains some computed values that
michael@0 559 * we expect to visit, as follows:
michael@0 560 * - fromComp: Computed value version of |aFrom| (if different from |aFrom|)
michael@0 561 * - midComp: Computed value that we expect to visit halfway through the
michael@0 562 * animation (|aFrom| + |aBy|/2)
michael@0 563 * - toComp: Computed value of the animation endpoint (|aFrom| + |aBy|)
michael@0 564 * - noEffect: Special flag -- if set, indicates that this testcase is
michael@0 565 * expected to have no effect on the computed value. (e.g. the
michael@0 566 * given values are invalid. Or the attribute may be animatable
michael@0 567 * and additive, but the particular "from" & "by" values that
michael@0 568 * are used don't support addition.)
michael@0 569 * @param aSkipReason If this test-case is known to currently fail, this
michael@0 570 * parameter should be a string explaining why.
michael@0 571 * Otherwise, this value should be null (or omitted).
michael@0 572 */
michael@0 573 function AnimTestcaseFromBy(aFrom, aBy, aComputedValMap, aSkipReason)
michael@0 574 {
michael@0 575 this.from = aFrom;
michael@0 576 this.by = aBy;
michael@0 577 this.computedValMap = aComputedValMap;
michael@0 578 this.skipReason = aSkipReason;
michael@0 579 if (this.computedValMap &&
michael@0 580 !this.computedValMap.noEffect && !this.computedValMap.toComp) {
michael@0 581 ok(false, "AnimTestcaseFromBy needs expected computed final value");
michael@0 582 }
michael@0 583 }
michael@0 584 AnimTestcaseFromBy.prototype =
michael@0 585 {
michael@0 586 // Member variables
michael@0 587 by : null,
michael@0 588
michael@0 589 // Methods
michael@0 590 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
michael@0 591 {
michael@0 592 // Call super, and then add my own customization
michael@0 593 var animElem = AnimTestcaseFrom.prototype.setupAnimationElement.apply(this,
michael@0 594 [aAnimAttr, aTimeData, aIsFreeze]);
michael@0 595 animElem.setAttribute("by", this.by)
michael@0 596 return animElem;
michael@0 597 },
michael@0 598 buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
michael@0 599 {
michael@0 600 if (!aAnimAttr.isAdditive) {
michael@0 601 return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData,
michael@0 602 "defined as non-additive in SVG spec");
michael@0 603 }
michael@0 604 // Just use inherited method
michael@0 605 return AnimTestcaseFrom.prototype.buildSeekList.apply(this,
michael@0 606 [aAnimAttr, aBaseVal, aTimeData, aIsFreeze]);
michael@0 607 },
michael@0 608 }
michael@0 609 extend(AnimTestcaseFromBy, AnimTestcaseFrom);
michael@0 610
michael@0 611 /*
michael@0 612 * A testcase for a "paced-mode" animation
michael@0 613 * @param aValues An array of values, to be used as the "Values" list
michael@0 614 * @param aComputedValMap A hash-map that contains some computed values,
michael@0 615 * if they're needed, as follows:
michael@0 616 * - comp0: The computed value at the start of the animation
michael@0 617 * - comp1_6: The computed value exactly 1/6 through animation
michael@0 618 * - comp1_3: The computed value exactly 1/3 through animation
michael@0 619 * - comp2_3: The computed value exactly 2/3 through animation
michael@0 620 * - comp1: The computed value of the animation endpoint
michael@0 621 * The math works out easiest if...
michael@0 622 * (a) aValuesString has 3 entries in its values list: vA, vB, vC
michael@0 623 * (b) dist(vB, vC) = 2 * dist(vA, vB)
michael@0 624 * With this setup, we can come up with expected intermediate values according
michael@0 625 * to the following rules:
michael@0 626 * - comp0 should be vA
michael@0 627 * - comp1_6 should be us halfway between vA and vB
michael@0 628 * - comp1_3 should be vB
michael@0 629 * - comp2_3 should be halfway between vB and vC
michael@0 630 * - comp1 should be vC
michael@0 631 * @param aSkipReason If this test-case is known to currently fail, this
michael@0 632 * parameter should be a string explaining why.
michael@0 633 * Otherwise, this value should be null (or omitted).
michael@0 634 */
michael@0 635 function AnimTestcasePaced(aValuesString, aComputedValMap, aSkipReason)
michael@0 636 {
michael@0 637 this.valuesString = aValuesString;
michael@0 638 this.computedValMap = aComputedValMap;
michael@0 639 this.skipReason = aSkipReason;
michael@0 640 if (this.computedValMap &&
michael@0 641 (!this.computedValMap.comp0 ||
michael@0 642 !this.computedValMap.comp1_6 ||
michael@0 643 !this.computedValMap.comp1_3 ||
michael@0 644 !this.computedValMap.comp2_3 ||
michael@0 645 !this.computedValMap.comp1)) {
michael@0 646 ok(false, "This AnimTestcasePaced has an incomplete computed value map");
michael@0 647 }
michael@0 648 }
michael@0 649 AnimTestcasePaced.prototype =
michael@0 650 {
michael@0 651 // Member variables
michael@0 652 valuesString : null,
michael@0 653
michael@0 654 // Methods
michael@0 655 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
michael@0 656 {
michael@0 657 // Call super, and then add my own customization
michael@0 658 var animElem = AnimTestcase.prototype.setupAnimationElement.apply(this,
michael@0 659 [aAnimAttr, aTimeData, aIsFreeze]);
michael@0 660 animElem.setAttribute("values", this.valuesString)
michael@0 661 animElem.setAttribute("calcMode", "paced");
michael@0 662 return animElem;
michael@0 663 },
michael@0 664 buildSeekListAnimated : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
michael@0 665 {
michael@0 666 var seekList = new Array();
michael@0 667 var msgPrefix = aAnimAttr.attrName + ": checking value ";
michael@0 668 seekList.push([aTimeData.getBeginTime(),
michael@0 669 this.computedValMap.comp0,
michael@0 670 msgPrefix + "at start of animation"]);
michael@0 671 seekList.push([aTimeData.getFractionalTime(1/6),
michael@0 672 this.computedValMap.comp1_6,
michael@0 673 msgPrefix + "1/6 of the way through animation."]);
michael@0 674 seekList.push([aTimeData.getFractionalTime(1/3),
michael@0 675 this.computedValMap.comp1_3,
michael@0 676 msgPrefix + "1/3 of the way through animation."]);
michael@0 677 seekList.push([aTimeData.getFractionalTime(2/3),
michael@0 678 this.computedValMap.comp2_3,
michael@0 679 msgPrefix + "2/3 of the way through animation."]);
michael@0 680
michael@0 681 var finalMsg;
michael@0 682 var expectedEndVal;
michael@0 683 if (aIsFreeze) {
michael@0 684 expectedEndVal = this.computedValMap.comp1;
michael@0 685 finalMsg = aAnimAttr.attrName +
michael@0 686 ": [freeze-mode] checking that final value is set ";
michael@0 687 } else {
michael@0 688 expectedEndVal = aBaseVal;
michael@0 689 finalMsg = aAnimAttr.attrName +
michael@0 690 ": [remove-mode] checking that animation is cleared ";
michael@0 691 }
michael@0 692 seekList.push([aTimeData.getEndTime(),
michael@0 693 expectedEndVal, finalMsg + "at end of animation"]);
michael@0 694 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(),
michael@0 695 expectedEndVal, finalMsg + "after end of animation"]);
michael@0 696 return seekList;
michael@0 697 },
michael@0 698 buildSeekListStatic : function(aAnimAttr, aBaseVal, aTimeData, aReasonStatic)
michael@0 699 {
michael@0 700 var seekList = new Array();
michael@0 701 var msgPrefix =
michael@0 702 aAnimAttr.attrName + ": shouldn't be affected by animation ";
michael@0 703 seekList.push([aTimeData.getBeginTime(), aBaseVal,
michael@0 704 msgPrefix + "(at animation begin) - " + aReasonStatic]);
michael@0 705 seekList.push([aTimeData.getFractionalTime(1/6), aBaseVal,
michael@0 706 msgPrefix + "(1/6 of the way through animation) - " +
michael@0 707 aReasonStatic]);
michael@0 708 seekList.push([aTimeData.getFractionalTime(1/3), aBaseVal,
michael@0 709 msgPrefix + "(1/3 of the way through animation) - " +
michael@0 710 aReasonStatic]);
michael@0 711 seekList.push([aTimeData.getFractionalTime(2/3), aBaseVal,
michael@0 712 msgPrefix + "(2/3 of the way through animation) - " +
michael@0 713 aReasonStatic]);
michael@0 714 seekList.push([aTimeData.getEndTime(), aBaseVal,
michael@0 715 msgPrefix + "(at animation end) - " + aReasonStatic]);
michael@0 716 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), aBaseVal,
michael@0 717 msgPrefix + "(after animation end) - " + aReasonStatic]);
michael@0 718 return seekList;
michael@0 719 },
michael@0 720 };
michael@0 721 extend(AnimTestcasePaced, AnimTestcase);
michael@0 722
michael@0 723 /*
michael@0 724 * A testcase for an <animateMotion> animation.
michael@0 725 *
michael@0 726 * @param aAttrValueHash A hash-map mapping attribute names to values.
michael@0 727 * Should include at least 'path', 'values', 'to'
michael@0 728 * or 'by' to describe the motion path.
michael@0 729 * @param aCtmMap A hash-map that contains summaries of the expected resulting
michael@0 730 * CTM at various points during the animation. The CTM is
michael@0 731 * summarized as a tuple of three numbers: [tX, tY, theta]
michael@0 732 (indicating a translate(tX,tY) followed by a rotate(theta))
michael@0 733 * - ctm0: The CTM summary at the start of the animation
michael@0 734 * - ctm1_6: The CTM summary at exactly 1/6 through animation
michael@0 735 * - ctm1_3: The CTM summary at exactly 1/3 through animation
michael@0 736 * - ctm2_3: The CTM summary at exactly 2/3 through animation
michael@0 737 * - ctm1: The CTM summary at the animation endpoint
michael@0 738 *
michael@0 739 * NOTE: For paced-mode animation (the default for animateMotion), the math
michael@0 740 * works out easiest if:
michael@0 741 * (a) our motion path has 3 points: vA, vB, vC
michael@0 742 * (b) dist(vB, vC) = 2 * dist(vA, vB)
michael@0 743 * (See discussion in header comment for AnimTestcasePaced.)
michael@0 744 *
michael@0 745 * @param aSkipReason If this test-case is known to currently fail, this
michael@0 746 * parameter should be a string explaining why.
michael@0 747 * Otherwise, this value should be null (or omitted).
michael@0 748 */
michael@0 749 function AnimMotionTestcase(aAttrValueHash, aCtmMap, aSkipReason)
michael@0 750 {
michael@0 751 this.attrValueHash = aAttrValueHash;
michael@0 752 this.ctmMap = aCtmMap;
michael@0 753 this.skipReason = aSkipReason;
michael@0 754 if (this.ctmMap &&
michael@0 755 (!this.ctmMap.ctm0 ||
michael@0 756 !this.ctmMap.ctm1_6 ||
michael@0 757 !this.ctmMap.ctm1_3 ||
michael@0 758 !this.ctmMap.ctm2_3 ||
michael@0 759 !this.ctmMap.ctm1)) {
michael@0 760 ok(false, "This AnimMotionTestcase has an incomplete CTM map");
michael@0 761 }
michael@0 762 }
michael@0 763 AnimMotionTestcase.prototype =
michael@0 764 {
michael@0 765 // Member variables
michael@0 766 _animElementTagName : "animateMotion",
michael@0 767
michael@0 768 // Implementations of inherited methods that we need to override:
michael@0 769 // --------------------------------------------------------------
michael@0 770 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
michael@0 771 {
michael@0 772 var animElement = document.createElementNS(SVG_NS,
michael@0 773 this._animElementTagName);
michael@0 774 animElement.setAttribute("begin", aTimeData.getBeginTime());
michael@0 775 animElement.setAttribute("dur", aTimeData.getDur());
michael@0 776 if (aIsFreeze) {
michael@0 777 animElement.setAttribute("fill", "freeze");
michael@0 778 }
michael@0 779 for (var attrName in this.attrValueHash) {
michael@0 780 if (attrName == "mpath") {
michael@0 781 this.createPath(this.attrValueHash[attrName]);
michael@0 782 this.createMpath(animElement);
michael@0 783 } else {
michael@0 784 animElement.setAttribute(attrName, this.attrValueHash[attrName]);
michael@0 785 }
michael@0 786 }
michael@0 787 return animElement;
michael@0 788 },
michael@0 789
michael@0 790 createPath : function(aPathDescription)
michael@0 791 {
michael@0 792 var path = document.createElementNS(SVG_NS, "path");
michael@0 793 path.setAttribute("d", aPathDescription);
michael@0 794 path.setAttribute("id", MPATH_TARGET_ID);
michael@0 795 return SMILUtil.getSVGRoot().appendChild(path);
michael@0 796 },
michael@0 797
michael@0 798 createMpath : function(aAnimElement)
michael@0 799 {
michael@0 800 var mpath = document.createElementNS(SVG_NS, "mpath");
michael@0 801 mpath.setAttributeNS(XLINK_NS, "href", "#" + MPATH_TARGET_ID);
michael@0 802 return aAnimElement.appendChild(mpath);
michael@0 803 },
michael@0 804
michael@0 805 // Override inherited seekAndTest method since...
michael@0 806 // (a) it expects a computedValMap and we have a computed-CTM map instead
michael@0 807 // and (b) it expects we might have no effect (for non-animatable attrs)
michael@0 808 buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
michael@0 809 {
michael@0 810 var seekList = new Array();
michael@0 811 var msgPrefix = "CTM mismatch ";
michael@0 812 seekList.push([aTimeData.getBeginTime(),
michael@0 813 CTMUtil.generateCTM(this.ctmMap.ctm0),
michael@0 814 msgPrefix + "at start of animation"]);
michael@0 815 seekList.push([aTimeData.getFractionalTime(1/6),
michael@0 816 CTMUtil.generateCTM(this.ctmMap.ctm1_6),
michael@0 817 msgPrefix + "1/6 of the way through animation."]);
michael@0 818 seekList.push([aTimeData.getFractionalTime(1/3),
michael@0 819 CTMUtil.generateCTM(this.ctmMap.ctm1_3),
michael@0 820 msgPrefix + "1/3 of the way through animation."]);
michael@0 821 seekList.push([aTimeData.getFractionalTime(2/3),
michael@0 822 CTMUtil.generateCTM(this.ctmMap.ctm2_3),
michael@0 823 msgPrefix + "2/3 of the way through animation."]);
michael@0 824
michael@0 825 var finalMsg;
michael@0 826 var expectedEndVal;
michael@0 827 if (aIsFreeze) {
michael@0 828 expectedEndVal = CTMUtil.generateCTM(this.ctmMap.ctm1);
michael@0 829 finalMsg = aAnimAttr.attrName +
michael@0 830 ": [freeze-mode] checking that final value is set ";
michael@0 831 } else {
michael@0 832 expectedEndVal = aBaseVal;
michael@0 833 finalMsg = aAnimAttr.attrName +
michael@0 834 ": [remove-mode] checking that animation is cleared ";
michael@0 835 }
michael@0 836 seekList.push([aTimeData.getEndTime(),
michael@0 837 expectedEndVal, finalMsg + "at end of animation"]);
michael@0 838 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(),
michael@0 839 expectedEndVal, finalMsg + "after end of animation"]);
michael@0 840 return seekList;
michael@0 841 },
michael@0 842
michael@0 843 // Override inherited seekAndTest method
michael@0 844 // (Have to use assertCTMEqual() instead of is() for comparison, to check each
michael@0 845 // component of the CTM and to allow for a small margin of error.)
michael@0 846 seekAndTest : function(aSeekList, aTargetElem, aTargetAttr)
michael@0 847 {
michael@0 848 var svg = document.getElementById("svg");
michael@0 849 for (var i in aSeekList) {
michael@0 850 var entry = aSeekList[i];
michael@0 851 SMILUtil.getSVGRoot().setCurrentTime(entry[0]);
michael@0 852 CTMUtil.assertCTMEqual(aTargetElem.getCTM(), entry[1],
michael@0 853 CTMUtil.CTM_COMPONENTS_ALL, entry[2], false);
michael@0 854 }
michael@0 855 },
michael@0 856
michael@0 857 // Override "runTest" method so we can remove any <path> element that we
michael@0 858 // created at the end of each test.
michael@0 859 runTest : function(aTargetElem, aTargetAttr, aTimeData, aIsFreeze)
michael@0 860 {
michael@0 861 AnimTestcase.prototype.runTest.apply(this,
michael@0 862 [aTargetElem, aTargetAttr, aTimeData, aIsFreeze]);
michael@0 863 var pathElem = document.getElementById(MPATH_TARGET_ID);
michael@0 864 if (pathElem) {
michael@0 865 SMILUtil.getSVGRoot().removeChild(pathElem);
michael@0 866 }
michael@0 867 }
michael@0 868 };
michael@0 869 extend(AnimMotionTestcase, AnimTestcase);
michael@0 870
michael@0 871 // MAIN METHOD
michael@0 872 function testBundleList(aBundleList, aTimingData)
michael@0 873 {
michael@0 874 for (var bundleIdx in aBundleList) {
michael@0 875 aBundleList[bundleIdx].go(aTimingData);
michael@0 876 }
michael@0 877 }

mercurial