test_SVGxxxList.xhtml (51856B)
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <!-- 3 https://bugzilla.mozilla.org/show_bug.cgi?id=515116 4 --> 5 <head> 6 <title>Generic tests for SVG animated length lists</title> 7 <script src="/tests/SimpleTest/SimpleTest.js"></script> 8 <script type="text/javascript" src="matrixUtils.js"></script> 9 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 10 </head> 11 <body> 12 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug 515116</a> 13 <p id="display"></p> 14 <div id="content"> 15 <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100" 16 onload="this.pauseAnimations();"> 17 <defs> 18 <filter> 19 <feComponentTransfer> 20 <feFuncR id="feFuncR" type="table"/> 21 </feComponentTransfer> 22 </filter> 23 </defs> 24 <text id="text">text</text> 25 <path id="path"/> 26 <polyline id="polyline"/> 27 <g id="g"/> 28 </svg> 29 </div> 30 <pre id="test"> 31 <script class="testbody" type="text/javascript"> 32 <![CDATA[ 33 34 35 SimpleTest.waitForExplicitFinish(); 36 37 /* 38 This file runs a series of type-agnostic tests to check the state of the mini DOM trees that represent various SVG 'list' attributes (including checking the "object identity" of the objects in those trees) in the face of various changes, both with and without the complication of SMIL animation being active. 39 40 For additional high level information on the tests that are run, see the comment for 'create_animate_elements' below. 41 42 To have the battery of generic tests run for a new list attribute, add an element with that attribute to the document, then add a JavaScript object literal to the following 'tests' array with the following properties: 43 44 target_element_id 45 The ID of the element that has the attribute that is to be tested. 46 attr_name 47 The name of the attribute that is to be tested. 48 prop_name 49 The name of the DOM property that corresponds to the attribute that is to 50 be tested. For some list types the SVGAnimatedXxxList interface is 51 inherited by the element interface rather than the element having a 52 property of that type, and in others the list type is not animatable so 53 there is no SVGAnimatedXxxList interface for that list type. In these 54 cases this property should be set to null. 55 bv_name 56 The name of the DOM base value property for the attribute that is to be 57 tested. This is usually 'baseVal', but not always. In the case of 58 SVGStringList, which is not animatable, this is the name of the 59 SVGStringList property. 60 av_name 61 The name of the DOM anim value property for the attribute that is to be 62 tested. This is usually 'animVal' but not always. In the case of 63 SVGStringList, which is not animatable, this should be set to null. 64 el_type 65 The name of the SVGXxxElement interface on which the property corresponding 66 to the attribute being tested is defined. 67 prop_type 68 The name of the SVGAnimatedXxxList interface (e.g. SVGAnimatedLengthList), 69 if it exists, and if the element has a property is of this type (as 70 opposed to the element interface inheriting it). 71 list_type 72 The name of the SVGXxxList interface implemented by the baseVal and 73 animVal objects. 74 item_type 75 The name of the SVGXxx interface implemented by the list items. 76 attr_val_3a: 77 attr_val_3b: 78 Two attribute values containing three different items. 79 attr_val_4 80 An attribute value containing four items. 81 attr_val_5a: 82 attr_val_5b: 83 Two attribute values containing five different items. 84 attr_val_5b_firstItem_x3_constructor: 85 Function to construct a list-item that should match the first item in a 86 SVGXxxList after three repeats of a cumulative animation to attr_val_5b. 87 This function takes t.item_constructor as its only argument. 88 item_constructor: 89 Function to create a dummy list item. 90 item_is: 91 Function to compare two list items for equality, like "is()". If this 92 property is omitted, it is assumed that we can just compare 93 "item.value" (which is the case for most list types). 94 */ 95 // helper method 96 function keys(obj) { 97 var rval = []; 98 for (var prop in obj) { 99 rval.push(prop); 100 } 101 return rval; 102 } 103 104 var tests = [ 105 { 106 // SVGLengthList test: 107 target_element_id: "text", 108 attr_name: "x", 109 prop_name: "x", 110 bv_name: "baseVal", 111 av_name: "animVal", 112 el_type: "SVGTextElement", 113 prop_type: "SVGAnimatedLengthList", 114 list_type: "SVGLengthList", 115 item_type: "SVGLength", 116 attr_val_3a: "10 20ex, 30in", 117 attr_val_3b: "30in 10, 20ex", 118 attr_val_4: "10 20ex, 30in ,40cm", 119 attr_val_5a: "10 20ex, 30in ,40cm , 50%", 120 attr_val_5b: "20 50%, 20ex ,30in , 40cm", 121 attr_val_5b_firstItem_x3_constructor(constructor) { 122 var expected = constructor(); 123 expected.value = 60; 124 return expected; 125 }, 126 item_constructor() { 127 // We need this function literal to avoid "Illegal operation on 128 // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO. 129 return document.getElementById("svg").createSVGLength(); 130 }, 131 }, 132 { 133 // SVGNumberList test: 134 target_element_id: "text", 135 attr_name: "rotate", 136 prop_name: "rotate", 137 bv_name: "baseVal", 138 av_name: "animVal", 139 el_type: "SVGTextElement", 140 prop_type: "SVGAnimatedNumberList", 141 list_type: "SVGNumberList", 142 item_type: "SVGNumber", 143 attr_val_3a: "0 20 40", 144 attr_val_3b: "60 40 20", 145 attr_val_4: "40 20 10 80", 146 attr_val_5a: "90 30 60 20 70", 147 attr_val_5b: "30 20 70 30 90", 148 attr_val_5b_firstItem_x3_constructor(constructor) { 149 var expected = constructor(); 150 expected.value = 90; 151 return expected; 152 }, 153 item_constructor() { 154 // We need this function literal to avoid "Illegal operation on 155 // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO. 156 return document.getElementById("svg").createSVGNumber(); 157 }, 158 }, 159 { 160 // SVGNumberList test: 161 target_element_id: "feFuncR", 162 attr_name: "tableValues", 163 prop_name: "tableValues", 164 bv_name: "baseVal", 165 av_name: "animVal", 166 el_type: "SVGFEComponentTransferElement", 167 prop_type: "SVGAnimatedNumberList", 168 list_type: "SVGNumberList", 169 item_type: "SVGNumber", 170 attr_val_3a: "0 .5 .2", 171 attr_val_3b: "1 .7 .1", 172 attr_val_4: ".5 .3 .8 .2", 173 attr_val_5a: "3 4 5 6 7", 174 attr_val_5b: "7 6 5 4 3", 175 attr_val_5b_firstItem_x3_constructor(constructor) { 176 var expected = constructor(); 177 expected.value = 21; 178 return expected; 179 }, 180 item_constructor() { 181 // We need this function literal to avoid "Illegal operation on 182 // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO. 183 return document.getElementById("svg").createSVGNumber(); 184 }, 185 }, 186 { 187 // SVGPointList test: 188 target_element_id: "polyline", 189 attr_name: "points", 190 prop_name: null, // SVGAnimatedPoints is an inherited interface! 191 bv_name: "points", 192 av_name: "animatedPoints", 193 el_type: "SVGPolylineElement", 194 prop_type: null, 195 list_type: "SVGPointList", 196 item_type: "SVGPoint", 197 attr_val_3a: " 10,10 50,50 90,10 ", 198 attr_val_3b: " 10,50 50,10 90,50 ", 199 attr_val_4: " 10,10 50,50 90,10 200,100 ", 200 attr_val_5a: " 10,10 50,50 90,10 130,50 170,10 ", 201 attr_val_5b: " 50,10 50,10 90,50 130,10 170,50 ", 202 attr_val_5b_firstItem_x3_constructor(constructor) { 203 var expected = constructor(); 204 expected.x = 150; 205 expected.y = 30; 206 return expected; 207 }, 208 item_constructor() { 209 // XXX return different values each time 210 return document.getElementById("svg").createSVGPoint(); 211 }, 212 item_is(itemA, itemB, message) { 213 ok(typeof(itemA.x) != "undefined" && 214 typeof(itemB.x) != "undefined", 215 "expecting x property"); 216 ok(typeof(itemA.y) != "undefined" && 217 typeof(itemB.y) != "undefined", 218 "expecting y property"); 219 220 is(itemA.x, itemB.x, message); 221 is(itemA.y, itemB.y, message); 222 }, 223 }, 224 { 225 // SVGStringList test: 226 target_element_id: "g", 227 attr_name: "requiredExtensions", // systemLanguage, viewTarget 228 prop_name: null, // SVGStringList attributes are not animatable 229 bv_name: "requiredExtensions", 230 av_name: null, 231 el_type: "SVGGElement", 232 prop_type: null, 233 list_type: "SVGStringList", 234 item_type: "DOMString", 235 attr_val_3a: "http://www.w3.org/TR/SVG11/feature#Shape http://www.w3.org/TR/SVG11/feature#Image " + 236 "http://www.w3.org/TR/SVG11/feature#Style", 237 attr_val_3b: "http://www.w3.org/TR/SVG11/feature#CoreAttribute http://www.w3.org/TR/SVG11/feature#Structure " + 238 "http://www.w3.org/TR/SVG11/feature#Gradient", 239 attr_val_4: "http://www.w3.org/TR/SVG11/feature#Pattern http://www.w3.org/TR/SVG11/feature#Clip " + 240 "http://www.w3.org/TR/SVG11/feature#Mask http://www.w3.org/TR/SVG11/feature#Extensibility", 241 attr_val_5a: "http://www.w3.org/TR/SVG11/feature#BasicStructure http://www.w3.org/TR/SVG11/feature#BasicText " + 242 "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute " + 243 "http://www.w3.org/TR/SVG11/feature#BasicClip", 244 attr_val_5b: "http://www.w3.org/TR/SVG11/feature#DocumentEventsAttribute http://www.w3.org/TR/SVG11/feature#GraphicalEventsAttribute " + 245 "http://www.w3.org/TR/SVG11/feature#AnimationEventsAttribute http://www.w3.org/TR/SVG11/feature#Hyperlinking " + 246 "http://www.w3.org/TR/SVG11/feature#XlinkAttribute", 247 item_constructor() { 248 return "http://www.w3.org/TR/SVG11/feature#XlinkAttribute"; 249 }, 250 }, 251 { 252 // SVGTransformList test: 253 target_element_id: "g", 254 attr_name: "transform", // gradientTransform, patternTransform 255 prop_name: "transform", 256 bv_name: "baseVal", 257 av_name: "animVal", 258 el_type: "SVGGElement", 259 prop_type: "SVGAnimatedTransformList", 260 list_type: "SVGTransformList", 261 item_type: "SVGTransform", 262 attr_val_3a: "translate(20 10) rotate(90 10 10) skewX(45)", 263 attr_val_3b: "translate(30 40) scale(2) matrix(1 2 3 4 5 6)", 264 attr_val_4: "scale(3 2) translate(19) skewY(2) rotate(-10)", 265 attr_val_5a: 266 "translate(20) rotate(-10) skewY(3) matrix(1 2 3 4 5 6) scale(0.5)", 267 attr_val_5b: 268 "skewX(45) rotate(45 -10 -10) skewX(-45) scale(2) matrix(6 5 4 3 2 1)", 269 // SVGTransformList animation addition is tested in 270 // test_SVGTransformListAddition.xhtml so we don't need: 271 // - attr_val_3b 272 // - attr_val_3b 273 // - attr_val_5b_firstItem_x3_constructor 274 // But we populate the first two anyway just in case they are later used for 275 // something other than testing animation. 276 // attr_val_5b_firstItem_x3_constructor is only used for animation 277 item_constructor() { 278 // XXX populate the matrix with different values each time 279 return document.getElementById("svg").createSVGTransform(); 280 }, 281 item_is(itemA, itemB, message) { 282 ok(typeof(itemA.type) != "undefined" && 283 typeof(itemB.type) != "undefined", 284 "expecting type property"); 285 ok(typeof(itemA.matrix) != "undefined" && 286 typeof(itemB.matrix) != "undefined", 287 "expecting matrix property"); 288 ok(typeof(itemA.angle) != "undefined" && 289 typeof(itemB.angle) != "undefined", 290 "expecting matrix property"); 291 292 is(itemA.type, itemB.type, message); 293 is(itemA.angle, itemB.angle, message); 294 cmpMatrix(itemA.matrix, itemB.matrix, message); 295 }, 296 }, 297 ]; 298 299 300 /* 301 This function returns a DocumentFragment with three 'animate' element children. The duration of the three animations is as follows: 302 303 animation 1: | *-----------*-----------*-----------* 304 animation 2: | *--* 305 animation 3: | *--* 306 |___________________________________________> time (s) 307 | | | | | | | | | | | | | | | 308 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 309 310 The first animation repeats once so that we can test state on a repeat animation. 311 312 The second animation overrides the first animation for a short time, and has fewer list items than the first animation. This allows us to test object identity and other state on and after an overriding animation. Specifically, it allows us to check whether animVal list items are kept or discarded after the end of an overriding animation that has fewer items. 313 314 The third animation has additive="sum", with fewer items than the lower priority animation 1, allowing us to test object identity and other state in that scenario. TODO: some type aware tests to check whether the composite fails or works? 315 316 At t=0s and t=1s we test the effect of an attribute value changes in the absence and presence of SMIL animation respectively. 317 318 At t=10s we programmatically remove the fill="freeze" from animation 1. 319 */ 320 function create_animate_elements(test) { 321 var SVG_NS = "http://www.w3.org/2000/svg"; 322 var df = document.createDocumentFragment(); 323 324 if (is_transform_attr(test.attr_name)) { 325 // animateTransform is "special". Although it targets an 326 // SVGAnimatedTransformList it only takes SVGTransform values as 327 // animation values. Therefore all the assumptions we're testing about the 328 // length of lists don't apply. We simply have to test it separately. 329 // This is done in test_SVGTransformListAddition.xhtml. 330 return df; // Return the empty document fragment 331 } 332 333 var animate1 = document.createElementNS(SVG_NS, "animate"); 334 var animate2 = document.createElementNS(SVG_NS, "animate"); 335 var animate3 = document.createElementNS(SVG_NS, "animate"); 336 337 animate1.setAttribute("attributeName", test.attr_name); 338 animate1.setAttribute("from", test.attr_val_5a); 339 animate1.setAttribute("to", test.attr_val_5b); 340 animate1.setAttribute("begin", "1s"); 341 animate1.setAttribute("dur", "4s"); 342 animate1.setAttribute("repeatCount", "3"); 343 animate1.setAttribute("accumulate", "sum"); 344 animate1.setAttribute("fill", "freeze"); 345 df.appendChild(animate1); 346 347 animate2.setAttribute("attributeName", test.attr_name); 348 animate2.setAttribute("from", test.attr_val_3a); 349 animate2.setAttribute("to", test.attr_val_3b); 350 animate2.setAttribute("begin", "2s"); 351 animate2.setAttribute("dur", "1s"); 352 df.appendChild(animate2); 353 354 animate3.setAttribute("attributeName", test.attr_name); 355 animate3.setAttribute("from", test.attr_val_3a); 356 animate3.setAttribute("to", test.attr_val_3b); 357 animate3.setAttribute("begin", "7s"); 358 animate3.setAttribute("dur", "1s"); 359 animate3.setAttribute("additive", "sum"); 360 df.appendChild(animate3); 361 362 return df; 363 } 364 365 function is_transform_attr(attr_name) { 366 return attr_name == "transform" || 367 attr_name == "gradientTransform" || 368 attr_name == "patternTransform"; 369 } 370 371 function get_array_of_list_items(list) { 372 let array = []; 373 for (var i = 0; i < list.numberOfItems; ++i) { 374 array.push(list.getItem(i)); 375 } 376 return array; 377 } 378 379 380 /** 381 * This function tests the SVGXxxList API for the base val list. This means 382 * running tests for the following property and methods: 383 * 384 * numberOfItems 385 * clear() 386 * SVGLength initialize(in SVGLength newItem) 387 * SVGLength getItem(in unsigned long index) 388 * SVGLength insertItemBefore(in SVGLength newItem, in unsigned long index) 389 * SVGLength replaceItem(in SVGLength newItem, in unsigned long index) 390 * SVGLength removeItem(in unsigned long index) 391 * SVGLength appendItem(in SVGLength newItem) 392 * 393 * @param t A test from the 'tests' array. 394 */ 395 function run_baseVal_API_tests() { 396 var res, threw; 397 398 for (var t of tests) { 399 // Test .clear(): 400 401 t.element.setAttribute(t.attr_name, t.attr_val_4); 402 403 is(t.baseVal.numberOfItems, 4, 404 "The " + t.list_type + " object should contain four list items."); 405 406 res = t.baseVal.clear(); 407 408 is(t.baseVal.numberOfItems, 0, 409 "The method " + t.list_type + ".clear() should clear the " + t.list_type + 410 " object."); 411 is(res, undefined, 412 "The method " + t.list_type + ".clear() should not return a value."); 413 ok(t.element.hasAttribute(t.attr_name), 414 "The method " + t.list_type + ".clear() should not remove the attribute."); 415 ok(t.element.getAttribute(t.attr_name) === "", 416 "Cleared " + t.attr_name + " (" + t.list_type + ") but did not get an " + 417 "empty string back."); 418 419 t.baseVal.clear(); 420 421 // Test empty strings 422 423 t.element.setAttribute(t.attr_name, ""); 424 ok(t.element.getAttribute(t.attr_name) === "", 425 "Set an empty attribute value for " + t.attr_name + " (" + t.list_type + 426 ") but did not get an empty string back."); 427 428 // Test removed attributes 429 430 t.element.removeAttribute(t.attr_name); 431 ok(t.element.getAttribute(t.attr_name) === null, 432 "Removed attribute value for " + t.attr_name + " (" + t.list_type + 433 ") but did not get null back."); 434 ok(!t.element.hasAttribute(t.attr_name), 435 "Removed attribute value for " + t.attr_name + " (" + t.list_type + 436 ") but hasAttribute still returns true."); 437 438 // Test .initialize(): 439 440 t.element.setAttribute(t.attr_name, t.attr_val_4); 441 442 var item = t.item_constructor(); 443 // Our current implementation of 'initialize' for most list types performs 444 // a 'clear' followed by an 'insertItemBefore'. This results in two 445 // modification events being dispatched. SVGStringList however avoids the 446 // additional clear. 447 res = t.baseVal.initialize(item); 448 449 450 is(t.baseVal.numberOfItems, 1, 451 "The " + t.list_type + " object should contain one list item."); 452 ok(res === item, 453 "The list item returned by " + t.list_type + ".initialize() should be the " + 454 "exact same object as the item that was passed to that method, since " + 455 "the item that was passed to that method did not already belong to a " + 456 "list."); 457 ok(t.baseVal.getItem(0) === item, 458 "The list item at index 0 should be the exact same object as the " + 459 "object that was passed to the " + t.list_type + ".initialize() method, " + 460 "since the item that was passed to that method did not already " + 461 "belong to a list."); 462 463 t.element.setAttribute(t.attr_name, t.attr_val_4); 464 465 if (t.item_type != "DOMString") { 466 var old_items = get_array_of_list_items(t.baseVal); 467 item = t.baseVal.getItem(3); 468 res = t.baseVal.initialize(item); 469 470 ok(res !== item && 471 t.baseVal.getItem(0) !== item && 472 t.baseVal.getItem(0) !== old_items[0] && 473 res === t.baseVal.getItem(0), 474 "The method " + t.list_type + ".initialize() should clone the object that " + 475 "is passed in if that object is already in a list."); 476 // [SVGWG issue] not what the spec currently says 477 478 479 item = t.baseVal.getItem(0); 480 res = t.baseVal.initialize(item); 481 482 ok(res !== item && 483 t.baseVal.getItem(0) !== item, 484 "The method " + t.list_type + ".initialize() should clone the object that " + 485 "is passed in, even if that object is the only item in that list."); 486 // [SVGWG issue] not what the spec currently says 487 488 threw = false; 489 try { 490 t.baseVal.initialize({}); 491 } catch (e) { 492 threw = true; 493 } 494 ok(threw, 495 "The method " + t.list_type + ".initialize() should throw if passed an " + 496 "object of the wrong type."); 497 } 498 499 // Test .insertItemBefore(): 500 501 t.element.setAttribute(t.attr_name, t.attr_val_4); 502 503 old_items = get_array_of_list_items(t.baseVal); 504 item = t.item_constructor(); 505 res = t.baseVal.insertItemBefore(item, 2); 506 507 is(t.baseVal.numberOfItems, 5, 508 "The " + t.list_type + " object should contain five list items."); 509 ok(res === item, 510 "The list item returned by " + t.list_type + ".insertItemBefore() should " + 511 "be the exact same object as the item that was passed to that method, " + 512 "since the item that was passed to that method did not already belong " + 513 "to a list."); 514 ok(t.baseVal.getItem(2) === item, 515 "The list item at index 2 should be the exact same object as the " + 516 "object that was passed to the " + t.list_type + ".insertItemBefore() " + 517 "method, since the item that was passed to that method did not " + 518 "already belong to a list."); 519 ok(t.baseVal.getItem(3) === old_items[2], 520 "The list item that was at index 2 should be at index 3 after " + 521 "inserting a new item at index 2 using the " + t.list_type + 522 ".insertItemBefore() method."); 523 524 item = t.item_constructor(); 525 t.baseVal.insertItemBefore(item, 100); 526 527 ok(t.baseVal.getItem(5) === item, 528 "When the index passed to the " + t.list_type + ".insertItemBefore() " + 529 "method is out of bounds, the supplied list item should be appended " + 530 "to the list."); 531 532 item = t.baseVal.getItem(4); 533 res = t.baseVal.insertItemBefore(item, 2); 534 535 is(t.baseVal.numberOfItems, 7, 536 "The " + t.list_type + " object should contain seven list items."); 537 if (t.item_type != "DOMString") { 538 ok(res !== item && 539 t.baseVal.getItem(2) !== item && 540 t.baseVal.getItem(2) !== old_items[2] && 541 res === t.baseVal.getItem(2), 542 "The method " + t.list_type + ".insertItemBefore() should clone the " + 543 "object that is passed in if that object is already in a list."); 544 // [SVGWG issue] not what the spec currently says 545 } 546 547 item = t.baseVal.getItem(2); 548 res = t.baseVal.insertItemBefore(item, 2); 549 550 is(t.baseVal.numberOfItems, 8, 551 "The " + t.list_type + " object should contain eight list items."); 552 if (t.item_type != "DOMString") { 553 ok(res !== item && 554 t.baseVal.getItem(2) !== item, 555 "The method " + t.list_type + ".insertItemBefore() should clone the " + 556 "object that is passed in, even if that object is the item in " + 557 "the list at the index specified."); 558 // [SVGWG issue] not what the spec currently says 559 560 threw = false; 561 try { 562 t.baseVal.insertItemBefore({}, 2); 563 } catch (e) { 564 threw = true; 565 } 566 ok(threw, 567 "The method " + t.list_type + ".insertItemBefore() should throw if passed " + 568 "an object of the wrong type."); 569 } 570 571 // Test .replaceItem(): 572 573 t.element.setAttribute(t.attr_name, t.attr_val_4); 574 575 old_items = get_array_of_list_items(t.baseVal); 576 item = t.item_constructor(); 577 res = t.baseVal.replaceItem(item, 2); 578 579 is(t.baseVal.numberOfItems, 4, 580 "The " + t.list_type + " object should contain four list items."); 581 if (t.item_type != "DOMString") { 582 ok(res === item, 583 "The list item returned by " + t.list_type + ".replaceItem() should be " + 584 "the exact same object as the item that was passed to that method, " + 585 "since the item that was passed to that method did not already belong " + 586 "to a list."); 587 } 588 ok(t.baseVal.getItem(2) === item, 589 "The list item at index 2 should be the exact same object as the " + 590 "object that was passed to the " + t.list_type + ".replaceItem() method, " + 591 "since the item that was passed to that method did not already belong " + 592 "to a list."); 593 ok(t.baseVal.getItem(3) === old_items[3], 594 "The list item that was at index 3 should still be at index 3 after " + 595 "the item at index 2 was replaced using the " + t.list_type + 596 ".replaceItem() method."); 597 598 item = t.item_constructor(); 599 600 threw = false; 601 try { 602 t.baseVal.replaceItem(item, 100); 603 } catch (e) { 604 threw = true; 605 } 606 ok(threw, 607 "The method " + t.list_type + ".replaceItem() should throw if passed " + 608 "an index that is out of bounds."); 609 610 old_items = get_array_of_list_items(t.baseVal); 611 item = t.baseVal.getItem(3); 612 res = t.baseVal.replaceItem(item, 1); 613 614 is(t.baseVal.numberOfItems, 4, 615 "The " + t.list_type + " object should contain four list items."); 616 if (t.item_type != "DOMString") { 617 ok(res !== item && 618 t.baseVal.getItem(1) !== item && 619 t.baseVal.getItem(1) !== old_items[1] && 620 res === t.baseVal.getItem(1), 621 "The method " + t.list_type + ".replaceItem() should clone the object " + 622 "that is passed in if that object is already in a list."); 623 // [SVGWG issue] not what the spec currently says 624 } 625 626 item = t.baseVal.getItem(1); 627 res = t.baseVal.replaceItem(item, 1); 628 629 is(t.baseVal.numberOfItems, 4, 630 "The " + t.list_type + " object should contain four list items."); 631 if (t.item_type != "DOMString") { 632 ok(res !== item && 633 t.baseVal.getItem(1) !== item, 634 "The method " + t.list_type + ".replaceItem() should clone the object " + 635 "that is passed in, even if the object that object and the object " + 636 "that is being replaced are the exact same objects."); 637 // [SVGWG issue] not what the spec currently says 638 639 threw = false; 640 try { 641 t.baseVal.replaceItem({}, 2); 642 } catch (e) { 643 threw = true; 644 } 645 ok(threw, 646 "The method " + t.list_type + ".replaceItem() should throw if passed " + 647 "an object of the wrong type."); 648 } 649 650 // Test .removeItem(): 651 652 t.element.setAttribute(t.attr_name, t.attr_val_4); 653 654 old_items = get_array_of_list_items(t.baseVal); 655 item = t.baseVal.getItem(2); 656 res = t.baseVal.removeItem(2); 657 658 is(t.baseVal.numberOfItems, 3, 659 "The " + t.list_type + " object should contain three list items."); 660 if (t.item_type != "DOMString") { 661 ok(res === item, 662 "The list item returned by " + t.list_type + ".removeItem() should be the " + 663 "exact same object as the item that was at the specified index."); 664 } 665 ok(t.baseVal.getItem(1) === old_items[1], 666 "The list item that was at index 1 should still be at index 1 after " + 667 "the item at index 2 was removed using the " + t.list_type + 668 ".replaceItem() method."); 669 ok(t.baseVal.getItem(2) === old_items[3], 670 "The list item that was at index 3 should still be at index 2 after " + 671 "the item at index 2 was removed using the " + t.list_type + 672 ".replaceItem() method."); 673 674 threw = false; 675 try { 676 t.baseVal.removeItem(100); 677 } catch (e) { 678 threw = true; 679 } 680 ok(threw, 681 "The method " + t.list_type + ".removeItem() should throw if passed " + 682 "an index that is out of bounds."); 683 684 // Test .appendItem(): 685 686 t.element.setAttribute(t.attr_name, t.attr_val_4); 687 688 old_items = get_array_of_list_items(t.baseVal); 689 item = t.item_constructor(); 690 res = t.baseVal.appendItem(item); 691 692 is(t.baseVal.numberOfItems, 5, 693 "The " + t.list_type + " object should contain five list items."); 694 ok(res === item, 695 "The list item returned by " + t.list_type + ".appendItem() should be the " + 696 "exact same object as the item that was passed to that method, since " + 697 "the item that was passed to that method did not already belong " + 698 "to a list."); 699 ok(t.baseVal.getItem(4) === item, 700 "The last list item should be the exact same object as the object " + 701 "that was passed to the " + t.list_type + ".appendItem() method, since " + 702 "the item that was passed to that method did not already belong to " + 703 "a list."); 704 ok(t.baseVal.getItem(3) === old_items[3], 705 "The list item that was at index 4 should still be at index 4 after " + 706 "appending a new item using the " + t.list_type + ".appendItem() " + 707 "method."); 708 709 item = t.baseVal.getItem(2); 710 res = t.baseVal.appendItem(item); 711 712 is(t.baseVal.numberOfItems, 6, 713 "The " + t.list_type + " object should contain six list items."); 714 if (t.item_type != "DOMString") { 715 ok(res !== item && 716 t.baseVal.getItem(5) !== item && 717 res === t.baseVal.getItem(5), 718 "The method " + t.list_type + ".appendItem() should clone the object " + 719 "that is passed in if that object is already in a list."); 720 // [SVGWG issue] not what the spec currently says 721 } 722 723 item = t.baseVal.getItem(5); 724 res = t.baseVal.appendItem(item); 725 726 is(t.baseVal.numberOfItems, 7, 727 "The " + t.list_type + " object should contain seven list items."); 728 if (t.item_type != "DOMString") { 729 ok(res !== item && 730 t.baseVal.getItem(6) !== item, 731 "The method " + t.list_type + ".appendItem() should clone the object " + 732 "that is passed in, if that object is already the last item in " + 733 "that list."); 734 // [SVGWG issue] not what the spec currently says 735 736 threw = false; 737 try { 738 t.baseVal.appendItem({}); 739 } catch (e) { 740 threw = true; 741 } 742 ok(threw, 743 "The method " + t.list_type + ".appendItem() should throw if passed " + 744 "an object of the wrong type."); 745 } 746 747 // Test removal and addition events 748 749 t.element.removeAttribute(t.attr_name); 750 t.element.removeAttributeNS(null, t.attr_name); 751 res = t.baseVal.appendItem(item); 752 } 753 } 754 755 756 /** 757 * This function tests the SVGXxxList API for the anim val list (see also the 758 * comment for test_baseVal_API). 759 */ 760 function run_animVal_API_tests() { 761 var threw, item; 762 763 for (var t of tests) { 764 if (!t.animVal) 765 continue; // SVGStringList isn't animatable 766 767 item = t.item_constructor(); 768 769 t.element.setAttribute(t.attr_name, t.attr_val_4); 770 771 is(t.animVal.numberOfItems, 4, 772 "The " + t.list_type + " object should contain four list items."); 773 774 // Test .clear(): 775 776 threw = false; 777 try { 778 t.animVal.clear(); 779 } catch (e) { 780 threw = true; 781 } 782 ok(threw, 783 "The method " + t.list_type + ".clear() should throw when called on an " + 784 "anim val list, since anim val lists should be readonly."); 785 786 // Test .getItem(): 787 788 item = t.animVal.getItem(2); 789 ok(item != null && item === t.animVal.getItem(2), 790 "The method " + t.list_type + ".getItem() should work when called on an " + 791 "anim val list, and always return the exact same object."); 792 793 // .initialize() 794 795 threw = false; 796 try { 797 t.animVal.initialize(item); 798 } catch (e) { 799 threw = true; 800 } 801 ok(threw, 802 "The method " + t.list_type + ".initialize() should throw when called on " + 803 "an anim val list, since anim val lists should be readonly."); 804 805 // Test .insertItemBefore(): 806 807 threw = false; 808 try { 809 t.animVal.insertItemBefore(item, 2); 810 } catch (e) { 811 threw = true; 812 } 813 ok(threw, 814 "The method " + t.list_type + ".insertItemBefore() should throw when " + 815 "called on an anim val list, since anim val lists should be readonly."); 816 817 // Test .replaceItem(): 818 819 threw = false; 820 try { 821 t.animVal.replaceItem(item, 2); 822 } catch (e) { 823 threw = true; 824 } 825 ok(threw, 826 "The method " + t.list_type + ".replaceItem() should throw when called " + 827 "on an anim val list, since anim val lists should be readonly."); 828 829 // Test .removeItem(): 830 831 threw = false; 832 try { 833 t.animVal.removeItem(2); 834 } catch (e) { 835 threw = true; 836 } 837 ok(threw, 838 "The method " + t.list_type + ".removeItem() should throw when called " + 839 "on an anim val list, since anim val lists should be readonly."); 840 841 // Test .appendItem(): 842 843 threw = false; 844 try { 845 t.animVal.appendItem(item); 846 } catch (e) { 847 threw = true; 848 } 849 ok(threw, 850 "The method " + t.list_type + ".appendItem() should throw when called " + 851 "on an anim val list, since anim val lists should be readonly."); 852 } 853 } 854 855 856 /** 857 * This function runs some basic tests to check the effect of setAttribute() 858 * calls on object identity, without taking SMIL animation into consideration. 859 */ 860 function run_basic_setAttribute_tests() { 861 for (var t of tests) { 862 // Since the t.prop, t.baseVal and t.animVal objects should never ever 863 // change, we leave testing of them to our caller so that it can check 864 // them after all the other mutations such as SMIL changes. 865 866 t.element.setAttribute(t.attr_name, t.attr_val_4); 867 868 ok(t.baseVal.numberOfItems == 4 && t.baseVal.getItem(3) != null, 869 "The length of the " + t.list_type + " object for " + t.bv_path + " should " + 870 "have been set to 4 by the setAttribute() call."); 871 872 if (t.animVal) { 873 ok(t.baseVal.numberOfItems == t.animVal.numberOfItems, 874 "When no animations are active, the " + t.list_type + " objects for " + 875 t.bv_path + " and " + t.av_path + " should be the same length (4)."); 876 877 ok(t.baseVal !== t.animVal, 878 "The " + t.list_type + " objects for " + t.bv_path + " and " + t.av_path + 879 " should be different objects."); 880 881 ok(t.baseVal.getItem(0) !== t.animVal.getItem(0), 882 "The " + t.item_type + " list items in the " + t.list_type + " objects for " + 883 t.bv_path + " and " + t.av_path + " should be different objects."); 884 } 885 886 // eslint-disable-next-line no-self-compare 887 ok(t.baseVal.getItem(0) === t.baseVal.getItem(0), 888 "The exact same " + t.item_type + " DOM object should be returned each " + 889 "time the item at a given index in the " + t.list_type + " for " + 890 t.bv_path + " is accessed, given that the index was not made invalid " + 891 "by a change in list length between the successive accesses."); 892 893 if (t.animVal) { 894 // eslint-disable-next-line no-self-compare 895 ok(t.animVal.getItem(0) === t.animVal.getItem(0), 896 "The exact same " + t.item_type + " DOM object should be returned each " + 897 "time the item at a given index in the " + t.list_type + " for " + 898 t.av_path + " is accessed, given that the index was not made invalid " + 899 "by a change in list length between the successive accesses."); 900 } 901 902 // Test the effect of setting the attribute to new values: 903 904 t.old_baseVal_items = get_array_of_list_items(t.baseVal); 905 if (t.animVal) { 906 t.old_animVal_items = get_array_of_list_items(t.animVal); 907 } 908 909 t.element.setAttribute(t.attr_name, t.attr_val_3a); 910 t.element.setAttribute(t.attr_name, t.attr_val_5a); 911 912 ok(t.baseVal.numberOfItems == 5 && t.baseVal.getItem(4) != null, 913 "The length of the " + t.list_type + " object for " + t.bv_path + " should " + 914 "have been set to 5 by the setAttribute() call."); 915 916 if (t.animVal) { 917 ok(t.baseVal.numberOfItems == t.animVal.numberOfItems, 918 "Since no animations are active, the length of the " + t.list_type + " " + 919 "objects for " + t.bv_path + " and " + t.av_path + " should be the same " + 920 "(5)."); 921 } 922 923 if (t.item_type != "DOMString") { 924 ok(t.baseVal.getItem(2) === t.old_baseVal_items[2], 925 "After its attribute changes, list items in the " + t.list_type + " for " + 926 t.bv_path + " that are at indexes that existed prior to the attribute " + 927 "change should be the exact same objects as the objects that were " + 928 "at those indexes prior to the attribute change."); 929 930 ok(t.baseVal.getItem(3) !== t.old_baseVal_items[3], 931 "After its attribute changes, list items in the " + t.list_type + " for " + 932 t.bv_path + " that are at indexes that did not exist prior to the " + 933 "attribute change should not be the same objects as any objects that " + 934 "were at those indexes at some earlier time."); 935 } 936 937 if (t.animVal) { 938 ok(t.animVal.getItem(2) === t.old_animVal_items[2], 939 "After its attribute changes, list items in the " + t.list_type + " for " + 940 t.av_path + " that are at indexes that existed prior to the attribute " + 941 "change should be the exact same objects as the objects that were " + 942 "at those indexes prior to the attribute change."); 943 944 ok(t.animVal.getItem(3) !== t.old_animVal_items[3], 945 "After its attribute changes, list items in the " + t.list_type + " for " + 946 t.av_path + " that are at indexes that did not exist prior to the " + 947 "attribute change should not be the same objects as any objects " + 948 "that were at those indexes at some earlier time."); 949 } 950 } 951 } 952 953 /** 954 * This function verifies that a list's animVal is kept in sync with its 955 * baseVal, when we add & remove items from the baseVal. 956 */ 957 function run_list_mutation_tests() { 958 for (var t of tests) { 959 if (t.animVal) { 960 // Test removeItem() 961 // ================= 962 // Save second item in baseVal list; then make it the first item, and 963 // check that animVal is updated accordingly. 964 t.element.setAttribute(t.attr_name, t.attr_val_4); 965 966 var secondVal = t.baseVal.getItem(1); 967 var removedFirstVal = t.baseVal.removeItem(0); 968 t.item_is(t.animVal.getItem(0), secondVal, 969 "animVal for " + t.attr_name + " needs update after first item " + 970 "removed"); 971 972 // Repeat with last item 973 var secondToLastVal = t.baseVal.getItem(1); 974 var removedLastVal = t.baseVal.removeItem(2); 975 976 var threw = false; 977 try { 978 t.animVal.getItem(2); 979 } catch (e) { 980 threw = true; 981 } 982 ok(threw, 983 "The method " + t.attr_name + ".animVal.getItem() for previously-final " + 984 "index should throw after final item is removed from baseVal."); 985 986 t.item_is(t.animVal.getItem(1), secondToLastVal, 987 "animVal for " + t.attr_name + " needs update after last item " + 988 "removed"); 989 990 // Test insertItemBefore() 991 // ======================= 992 // Reset base value, insert value @ start, check that animVal is updated. 993 t.element.setAttribute(t.attr_name, t.attr_val_3a); 994 t.baseVal.insertItemBefore(removedLastVal, 0); 995 t.item_is(t.animVal.getItem(0), removedLastVal, 996 "animVal for " + t.attr_name + " needs update after insert at " + 997 "beginning"); 998 999 // Repeat with insert at end 1000 t.element.setAttribute(t.attr_name, t.attr_val_3a); 1001 t.baseVal.insertItemBefore(removedFirstVal, t.baseVal.numberOfItems); 1002 t.item_is(t.animVal.getItem(t.baseVal.numberOfItems - 1), 1003 removedFirstVal, 1004 "animVal for " + t.attr_name + " needs update after insert at end"); 1005 1006 // Test appendItem() 1007 // ================= 1008 var dummy = t.item_constructor(); 1009 t.baseVal.appendItem(dummy); 1010 t.item_is(t.animVal.getItem(t.baseVal.numberOfItems - 1), dummy, 1011 "animVal for " + t.attr_name + " needs update after appendItem"); 1012 1013 // Test clear() 1014 // ============ 1015 t.baseVal.clear(); 1016 threw = false; 1017 try { 1018 t.animVal.getItem(0); 1019 } catch (e) { 1020 threw = true; 1021 } 1022 ok(threw, 1023 "The method " + t.attr_name + ".animVal.getItem() should throw after " + 1024 "we've cleared baseVal."); 1025 1026 is(t.animVal.numberOfItems, 0, 1027 "animVal for " + t.attr_name + " should be empty after baseVal cleared"); 1028 1029 // Test initialize() 1030 // ================= 1031 t.element.setAttribute(t.attr_name, t.attr_val_3a); 1032 t.baseVal.initialize(dummy); 1033 1034 is(t.animVal.numberOfItems, 1, 1035 "animVal for " + t.attr_name + " should have length 1 after initialize"); 1036 t.item_is(t.animVal.getItem(0), dummy, 1037 "animVal for " + t.attr_name + " needs update after initialize"); 1038 } 1039 } 1040 } 1041 1042 /** 1043 * In this function we run a series of tests at various points along the SMIL 1044 * animation timeline, using SVGSVGElement.setCurrentTime() to move forward 1045 * along the timeline. 1046 * 1047 * See the comment for create_animate_elements() for details of the animations 1048 * and their timings. 1049 */ 1050 function run_animation_timeline_tests() { 1051 var svg = document.getElementById("svg"); 1052 1053 for (var t of tests) { 1054 // Skip if there is no animVal for this test or if it is a transform list 1055 // since these are handled specially 1056 if (!t.animVal || is_transform_attr(t.attr_name)) 1057 continue; 1058 1059 svg.setCurrentTime(0); // reset timeline 1060 1061 // Reset attributes before moving along the timeline and triggering SMIL: 1062 t.element.setAttribute(t.attr_name, t.attr_val_4); 1063 t.old_baseVal_items = get_array_of_list_items(t.baseVal); 1064 t.old_animVal_items = get_array_of_list_items(t.animVal); 1065 1066 1067 /******************** t = 1s ********************/ 1068 1069 svg.setCurrentTime(1); // begin first animation 1070 1071 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && 1072 t.baseVal.getItem(3) === t.old_baseVal_items[3], 1073 "The start of an animation should never affect the " + t.list_type + 1074 " for " + t.bv_path + ", or its list items."); 1075 1076 ok(t.animVal.numberOfItems == 5 && t.animVal.getItem(4) != null, 1077 "The start of the animation should have changed the number of items " + 1078 "in the " + t.list_type + " for " + t.bv_path + " to 5."); 1079 1080 // TODO 1081 ok(t.animVal.getItem(3) === t.old_animVal_items[3], 1082 "When affected by SMIL animation, list items in the " + t.list_type + 1083 " for " + t.bv_path + " that are at indexes that existed prior to the " + 1084 "start of the animation should be the exact same objects as the " + 1085 "objects that were at those indexes prior to the start of the " + 1086 "animation."); 1087 1088 t.old_animVal_items = get_array_of_list_items(t.animVal); 1089 1090 t.element.setAttribute(t.attr_name, t.attr_val_3a); 1091 1092 ok(t.baseVal.numberOfItems == 3 && 1093 t.baseVal.getItem(2) === t.old_baseVal_items[2], 1094 "Setting the underlying attribute should change the items in the " + 1095 t.list_type + " for " + t.bv_path + ", including when an animation is " + 1096 "in progress."); 1097 1098 ok(t.animVal.numberOfItems == 5 && 1099 t.animVal.getItem(4) === t.old_animVal_items[4], 1100 "Setting the underlying attribute should not change the " + t.list_type + 1101 " for " + t.bv_path + " when an animation that does not depend on the " + 1102 "base val is in progress."); 1103 1104 t.element.setAttribute(t.attr_name, t.attr_val_4); // reset 1105 1106 t.old_baseVal_items = get_array_of_list_items(t.baseVal); 1107 t.old_animVal_items = get_array_of_list_items(t.animVal); 1108 1109 1110 /******************** t = 2s ********************/ 1111 1112 svg.setCurrentTime(2); // begin override animation 1113 1114 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && 1115 t.baseVal.getItem(3) === t.old_baseVal_items[3], 1116 "The start of an override animation should never affect the " + 1117 t.list_type + " for " + t.bv_path + ", or its list items."); 1118 1119 is(t.animVal.numberOfItems, 3, 1120 "The start of the override animation should have changed the number " + 1121 "of items in the " + t.list_type + " for " + t.bv_path + " to 3."); 1122 1123 ok(t.animVal.getItem(2) === t.old_animVal_items[2], 1124 "When affected by an override SMIL animation, list items in the " + 1125 t.list_type + " for " + t.bv_path + " that are at indexes that existed " + 1126 "prior to the start of the animation should be the exact same " + 1127 "objects as the objects that were at those indexes prior to the " + 1128 "start of that animation."); 1129 1130 t.old_animVal_items = get_array_of_list_items(t.animVal); 1131 1132 1133 /******************** t = 3s ********************/ 1134 1135 svg.setCurrentTime(3); // end of override animation 1136 1137 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && 1138 t.baseVal.getItem(3) === t.old_baseVal_items[3], 1139 "The end of an override animation should never affect the " + 1140 t.list_type + " for " + t.bv_path + ", or its list items."); 1141 1142 is(t.animVal.numberOfItems, 5, 1143 "At the end of the override animation, the number of items in the " + 1144 t.list_type + " for " + t.bv_path + " should have reverted to 5."); 1145 1146 ok(t.animVal.getItem(2) === t.old_animVal_items[2], 1147 "At the end of the override animation, list items in the " + 1148 t.list_type + " for " + t.bv_path + " that are at indexes that existed " + 1149 "prior to the end of the animation should be the exact same " + 1150 "objects as the objects that were at those indexes prior to the " + 1151 "end of that animation."); 1152 1153 t.old_animVal_items = get_array_of_list_items(t.animVal); 1154 1155 1156 /******************** t = 5s ********************/ 1157 1158 svg.setCurrentTime(5); // animation repeat point 1159 1160 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && 1161 t.baseVal.getItem(3) === t.old_baseVal_items[3], 1162 "When a SMIL animation repeats, it should never affect the " + 1163 t.list_type + " for " + t.bv_path + ", or its list items."); 1164 1165 ok(t.animVal.numberOfItems == t.old_animVal_items.length && 1166 t.animVal.getItem(4) === t.old_animVal_items[4], 1167 "When an animation repeats, the list items that are at a given " + 1168 "index in the " + t.list_type + " for " + t.av_path + " should be the exact " + 1169 "same objects as were at that index before the repeat occurred."); 1170 1171 1172 /******************** t = 6s ********************/ 1173 1174 svg.setCurrentTime(6); // inside animation repeat 1175 1176 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && 1177 t.baseVal.getItem(3) === t.old_baseVal_items[3], 1178 "When a SMIL animation repeats, it should never affect the " + 1179 t.list_type + " for " + t.bv_path + ", or its list items."); 1180 1181 ok(t.animVal.numberOfItems == t.old_animVal_items.length && 1182 t.animVal.getItem(4) === t.old_animVal_items[4], 1183 "When an animation repeats, the list items that are at a given " + 1184 "index in the " + t.list_type + " for " + t.av_path + " should be the exact " + 1185 "same objects as were at that index before the repeat occurred."); 1186 1187 1188 /******************** t = 7s ********************/ 1189 1190 svg.setCurrentTime(7); // start of additive="sum" animation 1191 1192 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && 1193 t.baseVal.getItem(3) === t.old_baseVal_items[3], 1194 "When a new SMIL animation starts and should blend with an " + 1195 "underlying animation, it should never affect the " + 1196 t.list_type + " for " + t.bv_path + ", or its list items."); 1197 1198 if (t.list_type == "SVGLengthList") { 1199 // Length lists are a special case where it makes sense to allow shorter 1200 // lists to be composed on top of longer lists (but not necessarily vice 1201 // versa - see comment below). 1202 1203 ok(t.animVal.numberOfItems == t.old_animVal_items.length && 1204 t.animVal.getItem(3) === t.old_animVal_items[3], 1205 'When an animation with additive="sum" is added on top of an ' + 1206 "existing animation that has more list items, the length of the " + 1207 t.list_type + " for " + t.av_path + " should not change."); 1208 } else { 1209 1210 /* TODO 1211 ok(false, 1212 'Decide what to do here - see ' + 1213 'https://bugzilla.mozilla.org/show_bug.cgi?id=573716 - we ' + 1214 'probably should be discarding any animation sandwich layers from ' + 1215 'a layer that fails to add, on up.'); 1216 */ 1217 1218 // In other words, we wouldn't need the if-else check here. 1219 1220 } 1221 1222 // XXX what if the higher priority sandwich layer has *more* list items 1223 // than the underlying animation? In that case we would need to 1224 // distinguish between different SVGLengthList attributes, since although 1225 // all SVGLengthList attributes allow a short list to be added to longer 1226 // list, they do not all allow a longer list to be added to shorter list. 1227 // Specifically that would not be good for 'x' and 'y' on <text> since 1228 // lengths there are not naturally zero. See the comment in 1229 // SVGLengthListSMILAttr::Add(). 1230 1231 1232 /******************** t = 13s ********************/ 1233 1234 svg.setCurrentTime(13); // all animations have finished, but one is frozen 1235 1236 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && 1237 t.baseVal.getItem(3) === t.old_baseVal_items[3], 1238 "When a SMIL animation ends, it should never affect the " + 1239 t.list_type + " for " + t.bv_path + ", or its list items."); 1240 1241 is(t.animVal.numberOfItems, 5, 1242 "Even though all SMIL animation have finished, the number " + 1243 "of items in the " + t.list_type + " for " + t.av_path + 1244 " should still be more than the same as the number of items in " + 1245 t.bv_path + " since one of the animations is still frozen."); 1246 1247 var expected = t.attr_val_5b_firstItem_x3_constructor(t.item_constructor); 1248 t.item_is(t.animVal.getItem(0), expected, 1249 'animation with accumulate="sum" and repeatCount="3" for attribute "' + 1250 t.attr_name + '" should end up at 3x the "to" value.'); 1251 1252 // Unfreeze frozen animation (removing its effects) 1253 var frozen_animate_element = 1254 t.element.querySelector('animate[fill][attributeName="' + t.attr_name + '"]'); 1255 frozen_animate_element.removeAttribute("fill"); 1256 1257 ok(t.animVal.numberOfItems == t.baseVal.numberOfItems, 1258 "Once all SMIL animation have finished and been un-frozen, the number " + 1259 "of items in the " + t.list_type + " for " + t.av_path + 1260 " should be the same as the number of items in " + t.bv_path + "."); 1261 1262 ok(t.animVal.getItem(2) === t.old_animVal_items[2], 1263 "Even after an animation finishes and is un-frozen, the list items " + 1264 "that are at a given index in the " + t.list_type + " for " + t.av_path + 1265 " should be the exact same objects as were at that index before the " + 1266 "end and unfreezing of the animation occurred."); 1267 } 1268 } 1269 1270 1271 function run_tests() { 1272 // Initialize each test object with some useful properties, and create their 1273 // 'animate' elements. Note that 'prop' and 'animVal' may be null. 1274 for (let t of tests) { 1275 t.element = document.getElementById(t.target_element_id); 1276 t.prop = t.prop_name ? t.element[t.prop_name] : null; 1277 t.baseVal = ( t.prop || t.element )[t.bv_name]; 1278 t.animVal = t.av_name ? ( t.prop || t.element )[t.av_name] : null; 1279 t.bv_path = t.el_type + "." + 1280 (t.prop ? t.prop_name + "." : "") + 1281 t.bv_name; // e.g. 'SVGTextElement.x.baseVal' 1282 if (t.animVal) { 1283 t.av_path = t.el_type + "." + 1284 (t.prop ? t.prop_name + "." : "") + 1285 t.av_name; 1286 } 1287 t.prop_type = t.prop_type || null; 1288 1289 // use fallback 'is' function, if none was provided. 1290 if (!t.item_is) { 1291 t.item_is = function(itemA, itemB, message) { 1292 ok(typeof(itemA.value) != "undefined" && 1293 typeof(itemB.value) != "undefined", 1294 "expecting value property"); 1295 is(itemA.value, itemB.value, message); 1296 }; 1297 } 1298 1299 if (t.animVal) { 1300 t.element.appendChild(create_animate_elements(t)); 1301 } 1302 } 1303 1304 // Run the major test groups: 1305 1306 run_baseVal_API_tests(); 1307 run_animVal_API_tests(); 1308 run_basic_setAttribute_tests(); 1309 run_list_mutation_tests(); 1310 run_animation_timeline_tests(); 1311 1312 // After all the other test manipulations, we check that the following 1313 // objects have still not changed, since they never should: 1314 1315 for (let t of tests) { 1316 if (t.prop) { 1317 ok(t.prop === t.element[t.prop_name], 1318 "The same " + t.prop_type + " object should ALWAYS be returned for " + 1319 t.el_type + "." + t.prop_name + " each time it is accessed."); 1320 } 1321 1322 ok(t.baseVal === ( t.prop || t.element )[t.bv_name], 1323 "The same " + t.list_type + " object should ALWAYS be returned for " + 1324 t.el_type + "." + t.prop_name + "." + t.bv_name + " each time it is accessed."); 1325 1326 if (t.animVal) { 1327 ok(t.animVal === ( t.prop || t.element )[t.av_name], 1328 "The same " + t.list_type + " object should ALWAYS be returned for " + 1329 t.el_type + "." + t.prop_name + "." + t.av_name + " each time it is accessed."); 1330 } 1331 } 1332 1333 SimpleTest.finish(); 1334 } 1335 1336 window.addEventListener("load", 1337 () => SimpleTest.executeSoon(run_tests) 1338 ); 1339 1340 ]]> 1341 </script> 1342 </pre> 1343 </body> 1344 </html>