1 <html xmlns=
"http://www.w3.org/1999/xhtml">
3 https://bugzilla.mozilla.org/show_bug.cgi?id=515116
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 <script type=
"text/javascript" src=
"MutationEventChecker.js"></script>
10 <link rel=
"stylesheet" type=
"text/css" href=
"/tests/SimpleTest/test.css" />
13 <a target=
"_blank" href=
"https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug
515116</a>
16 <svg id=
"svg" xmlns=
"http://www.w3.org/2000/svg" width=
"100" height=
"100"
17 onload=
"this.pauseAnimations();">
21 <feFuncR id=
"feFuncR" type=
"table"/>
22 </feComponentTransfer>
25 <text id=
"text">text
</text>
27 <polyline id=
"polyline"/>
32 <script class=
"testbody" type=
"text/javascript">
36 SimpleTest.waitForExplicitFinish();
39 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.
41 For additional high level information on the tests that are run, see the comment for 'create_animate_elements' below.
43 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:
46 The ID of the element that has the attribute that is to be tested.
48 The name of the attribute that is to be tested.
50 The name of the DOM property that corresponds to the attribute that is to
51 be tested. For some list types the SVGAnimatedXxxList interface is
52 inherited by the element interface rather than the element having a
53 property of that type, and in others the list type is not animatable so
54 there is no SVGAnimatedXxxList interface for that list type. In these
55 cases this property should be set to null.
57 The name of the DOM base value property for the attribute that is to be
58 tested. This is usually 'baseVal', but not always. In the case of
59 SVGStringList, which is not animatable, this is the name of the
60 SVGStringList property.
62 The name of the DOM anim value property for the attribute that is to be
63 tested. This is usually 'animVal' but not always. In the case of
64 SVGStringList, which is not animatable, this should be set to null.
66 The name of the SVGXxxElement interface on which the property corresponding
67 to the attribute being tested is defined.
69 The name of the SVGAnimatedXxxList interface (e.g. SVGAnimatedLengthList),
70 if it exists, and if the element has a property is of this type (as
71 opposed to the element interface inheriting it).
73 The name of the SVGXxxList interface implemented by the baseVal and
76 The name of the SVGXxx interface implemented by the list items.
79 Two attribute values containing three different items.
81 An attribute value containing four items.
84 Two attribute values containing five different items.
85 attr_val_5b_firstItem_x3_constructor:
86 Function to construct a list-item that should match the first item in a
87 SVGXxxList after three repeats of a cumulative animation to attr_val_5b.
88 This function takes t.item_constructor as its only argument.
90 Function to create a dummy list item.
92 Function to compare two list items for equality, like
"is()". If this
93 property is omitted, it is assumed that we can just compare
94 "item.value" (which is the case for most list types).
99 for (var prop in obj) {
107 // SVGLengthList test:
108 target_element_id:
"text",
113 el_type:
"SVGTextElement",
114 prop_type:
"SVGAnimatedLengthList",
115 list_type:
"SVGLengthList",
116 item_type:
"SVGLength",
117 attr_val_3a:
"10 20ex, 30in",
118 attr_val_3b:
"30in 10, 20ex",
119 attr_val_4:
"10 20ex, 30in ,40cm",
120 attr_val_5a:
"10 20ex, 30in ,40cm , 50%",
121 attr_val_5b:
"20 50%, 20ex ,30in , 40cm",
122 attr_val_5b_firstItem_x3_constructor(constructor) {
123 var expected = constructor();
128 // We need this function literal to avoid
"Illegal operation on
129 // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO.
130 return document.getElementById(
"svg").createSVGLength();
134 // SVGNumberList test:
135 target_element_id:
"text",
140 el_type:
"SVGTextElement",
141 prop_type:
"SVGAnimatedNumberList",
142 list_type:
"SVGNumberList",
143 item_type:
"SVGNumber",
144 attr_val_3a:
"0 20 40",
145 attr_val_3b:
"60 40 20",
146 attr_val_4:
"40 20 10 80",
147 attr_val_5a:
"90 30 60 20 70",
148 attr_val_5b:
"30 20 70 30 90",
149 attr_val_5b_firstItem_x3_constructor(constructor) {
150 var expected = constructor();
155 // We need this function literal to avoid
"Illegal operation on
156 // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO.
157 return document.getElementById(
"svg").createSVGNumber();
161 // SVGNumberList test:
162 target_element_id:
"feFuncR",
163 attr_name:
"tableValues",
164 prop_name:
"tableValues",
167 el_type:
"SVGFEComponentTransferElement",
168 prop_type:
"SVGAnimatedNumberList",
169 list_type:
"SVGNumberList",
170 item_type:
"SVGNumber",
171 attr_val_3a:
"0 .5 .2",
172 attr_val_3b:
"1 .7 .1",
173 attr_val_4:
".5 .3 .8 .2",
174 attr_val_5a:
"3 4 5 6 7",
175 attr_val_5b:
"7 6 5 4 3",
176 attr_val_5b_firstItem_x3_constructor(constructor) {
177 var expected = constructor();
182 // We need this function literal to avoid
"Illegal operation on
183 // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO.
184 return document.getElementById(
"svg").createSVGNumber();
188 // SVGPointList test:
189 target_element_id:
"polyline",
191 prop_name: null, // SVGAnimatedPoints is an inherited interface!
193 av_name:
"animatedPoints",
194 el_type:
"SVGPolylineElement",
196 list_type:
"SVGPointList",
197 item_type:
"SVGPoint",
198 attr_val_3a:
" 10,10 50,50 90,10 ",
199 attr_val_3b:
" 10,50 50,10 90,50 ",
200 attr_val_4:
" 10,10 50,50 90,10 200,100 ",
201 attr_val_5a:
" 10,10 50,50 90,10 130,50 170,10 ",
202 attr_val_5b:
" 50,10 50,10 90,50 130,10 170,50 ",
203 attr_val_5b_firstItem_x3_constructor(constructor) {
204 var expected = constructor();
210 // XXX return different values each time
211 return document.getElementById(
"svg").createSVGPoint();
213 item_is(itemA, itemB, message) {
214 ok(typeof(itemA.x) !=
"undefined" &&
215 typeof(itemB.x) !=
"undefined",
216 "expecting x property");
217 ok(typeof(itemA.y) !=
"undefined" &&
218 typeof(itemB.y) !=
"undefined",
219 "expecting y property");
221 is(itemA.x, itemB.x, message);
222 is(itemA.y, itemB.y, message);
226 // SVGStringList test:
227 target_element_id:
"g",
228 attr_name:
"requiredExtensions", // systemLanguage, viewTarget
229 prop_name: null, // SVGStringList attributes are not animatable
230 bv_name:
"requiredExtensions",
232 el_type:
"SVGGElement",
234 list_type:
"SVGStringList",
235 item_type:
"DOMString",
236 attr_val_3a:
"http://www.w3.org/TR/SVG11/feature#Shape http://www.w3.org/TR/SVG11/feature#Image " +
237 "http://www.w3.org/TR/SVG11/feature#Style",
238 attr_val_3b:
"http://www.w3.org/TR/SVG11/feature#CoreAttribute http://www.w3.org/TR/SVG11/feature#Structure " +
239 "http://www.w3.org/TR/SVG11/feature#Gradient",
240 attr_val_4:
"http://www.w3.org/TR/SVG11/feature#Pattern http://www.w3.org/TR/SVG11/feature#Clip " +
241 "http://www.w3.org/TR/SVG11/feature#Mask http://www.w3.org/TR/SVG11/feature#Extensibility",
242 attr_val_5a:
"http://www.w3.org/TR/SVG11/feature#BasicStructure http://www.w3.org/TR/SVG11/feature#BasicText " +
243 "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute " +
244 "http://www.w3.org/TR/SVG11/feature#BasicClip",
245 attr_val_5b:
"http://www.w3.org/TR/SVG11/feature#DocumentEventsAttribute http://www.w3.org/TR/SVG11/feature#GraphicalEventsAttribute " +
246 "http://www.w3.org/TR/SVG11/feature#AnimationEventsAttribute http://www.w3.org/TR/SVG11/feature#Hyperlinking " +
247 "http://www.w3.org/TR/SVG11/feature#XlinkAttribute",
249 return
"http://www.w3.org/TR/SVG11/feature#XlinkAttribute";
253 // SVGTransformList test:
254 target_element_id:
"g",
255 attr_name:
"transform", // gradientTransform, patternTransform
256 prop_name:
"transform",
259 el_type:
"SVGGElement",
260 prop_type:
"SVGAnimatedTransformList",
261 list_type:
"SVGTransformList",
262 item_type:
"SVGTransform",
263 attr_val_3a:
"translate(20 10) rotate(90 10 10) skewX(45)",
264 attr_val_3b:
"translate(30 40) scale(2) matrix(1 2 3 4 5 6)",
265 attr_val_4:
"scale(3 2) translate(19) skewY(2) rotate(-10)",
267 "translate(20) rotate(-10) skewY(3) matrix(1 2 3 4 5 6) scale(0.5)",
269 "skewX(45) rotate(45 -10 -10) skewX(-45) scale(2) matrix(6 5 4 3 2 1)",
270 // SVGTransformList animation addition is tested in
271 // test_SVGTransformListAddition.xhtml so we don't need:
274 // - attr_val_5b_firstItem_x3_constructor
275 // But we populate the first two anyway just in case they are later used for
276 // something other than testing animation.
277 // attr_val_5b_firstItem_x3_constructor is only used for animation
279 // XXX populate the matrix with different values each time
280 return document.getElementById(
"svg").createSVGTransform();
282 item_is(itemA, itemB, message) {
283 ok(typeof(itemA.type) !=
"undefined" &&
284 typeof(itemB.type) !=
"undefined",
285 "expecting type property");
286 ok(typeof(itemA.matrix) !=
"undefined" &&
287 typeof(itemB.matrix) !=
"undefined",
288 "expecting matrix property");
289 ok(typeof(itemA.angle) !=
"undefined" &&
290 typeof(itemB.angle) !=
"undefined",
291 "expecting matrix property");
293 is(itemA.type, itemB.type, message);
294 is(itemA.angle, itemB.angle, message);
295 cmpMatrix(itemA.matrix, itemB.matrix, message);
302 This function returns a DocumentFragment with three 'animate' element children. The duration of the three animations is as follows:
304 animation
1: | *-----------*-----------*-----------*
307 |___________________________________________
> time (s)
308 | | | | | | | | | | | | | | |
309 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
311 The first animation repeats once so that we can test state on a repeat animation.
313 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.
315 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?
317 At t=
0s and t=
1s we test the effect of an attribute value changes in the absence and presence of SMIL animation respectively.
319 At t=
10s we programmatically remove the
fill=
"freeze" from animation
1.
321 function create_animate_elements(test) {
322 var SVG_NS =
"http://www.w3.org/2000/svg";
323 var df = document.createDocumentFragment();
325 if (is_transform_attr(test.attr_name)) {
326 // animateTransform is
"special". Although it targets an
327 // SVGAnimatedTransformList it only takes SVGTransform values as
328 // animation values. Therefore all the assumptions we're testing about the
329 // length of lists don't apply. We simply have to test it separately.
330 // This is done in test_SVGTransformListAddition.xhtml.
331 return df; // Return the empty document fragment
334 var animate1 = document.createElementNS(SVG_NS,
"animate");
335 var animate2 = document.createElementNS(SVG_NS,
"animate");
336 var animate3 = document.createElementNS(SVG_NS,
"animate");
338 animate1.setAttribute(
"attributeName", test.attr_name);
339 animate1.setAttribute(
"from", test.attr_val_5a);
340 animate1.setAttribute(
"to", test.attr_val_5b);
341 animate1.setAttribute(
"begin",
"1s");
342 animate1.setAttribute(
"dur",
"4s");
343 animate1.setAttribute(
"repeatCount",
"3");
344 animate1.setAttribute(
"accumulate",
"sum");
345 animate1.setAttribute(
"fill",
"freeze");
346 df.appendChild(animate1);
348 animate2.setAttribute(
"attributeName", test.attr_name);
349 animate2.setAttribute(
"from", test.attr_val_3a);
350 animate2.setAttribute(
"to", test.attr_val_3b);
351 animate2.setAttribute(
"begin",
"2s");
352 animate2.setAttribute(
"dur",
"1s");
353 df.appendChild(animate2);
355 animate3.setAttribute(
"attributeName", test.attr_name);
356 animate3.setAttribute(
"from", test.attr_val_3a);
357 animate3.setAttribute(
"to", test.attr_val_3b);
358 animate3.setAttribute(
"begin",
"7s");
359 animate3.setAttribute(
"dur",
"1s");
360 animate3.setAttribute(
"additive",
"sum");
361 df.appendChild(animate3);
366 function is_transform_attr(attr_name) {
367 return attr_name ==
"transform" ||
368 attr_name ==
"gradientTransform" ||
369 attr_name ==
"patternTransform";
372 function get_array_of_list_items(list) {
374 for (var i =
0; i < list.numberOfItems; ++i) {
375 array.push(list.getItem(i));
382 * This function tests the SVGXxxList API for the base val list. This means
383 * running tests for the following property and methods:
387 * SVGLength initialize(in SVGLength newItem)
388 * SVGLength getItem(in unsigned long index)
389 * SVGLength insertItemBefore(in SVGLength newItem, in unsigned long index)
390 * SVGLength replaceItem(in SVGLength newItem, in unsigned long index)
391 * SVGLength removeItem(in unsigned long index)
392 * SVGLength appendItem(in SVGLength newItem)
394 * @param t A test from the 'tests' array.
396 function run_baseVal_API_tests() {
398 var eventChecker = new MutationEventChecker;
400 for (var t of tests) {
403 t.element.setAttribute(t.attr_name, t.attr_val_4);
405 is(t.baseVal.numberOfItems,
4,
406 "The " + t.list_type +
" object should contain four list items.");
408 eventChecker.watchAttr(t.element, t.attr_name);
409 eventChecker.expect(
"modify");
410 res = t.baseVal.clear();
412 is(t.baseVal.numberOfItems,
0,
413 "The method " + t.list_type +
".clear() should clear the " + t.list_type +
416 "The method " + t.list_type +
".clear() should not return a value.");
417 ok(t.element.hasAttribute(t.attr_name),
418 "The method " + t.list_type +
".clear() should not remove the attribute.");
419 ok(t.element.getAttribute(t.attr_name) ===
"",
420 "Cleared " + t.attr_name +
" (" + t.list_type +
") but did not get an " +
421 "empty string back.");
423 eventChecker.expect(
"");
425 eventChecker.ignoreEvents();
427 // Test empty strings
429 t.element.setAttribute(t.attr_name,
"");
430 ok(t.element.getAttribute(t.attr_name) ===
"",
431 "Set an empty attribute value for " + t.attr_name +
" (" + t.list_type +
432 ") but did not get an empty string back.");
434 // Test removed attributes
436 t.element.removeAttribute(t.attr_name);
437 ok(t.element.getAttribute(t.attr_name) === null,
438 "Removed attribute value for " + t.attr_name +
" (" + t.list_type +
439 ") but did not get null back.");
440 ok(!t.element.hasAttribute(t.attr_name),
441 "Removed attribute value for " + t.attr_name +
" (" + t.list_type +
442 ") but hasAttribute still returns true.");
444 // Test .initialize():
446 t.element.setAttribute(t.attr_name, t.attr_val_4);
448 var item = t.item_constructor();
449 // Our current implementation of 'initialize' for most list types performs
450 // a 'clear' followed by an 'insertItemBefore'. This results in two
451 // modification events being dispatched. SVGStringList however avoids the
453 var expectedModEvents =
454 t.item_type ==
"DOMString" ?
"modify" :
"modify modify";
455 eventChecker.expect(expectedModEvents);
456 res = t.baseVal.initialize(item);
457 eventChecker.ignoreEvents();
460 is(t.baseVal.numberOfItems,
1,
461 "The " + t.list_type +
" object should contain one list item.");
463 "The list item returned by " + t.list_type +
".initialize() should be the " +
464 "exact same object as the item that was passed to that method, since " +
465 "the item that was passed to that method did not already belong to a " +
467 ok(t.baseVal.getItem(
0) === item,
468 "The list item at index 0 should be the exact same object as the " +
469 "object that was passed to the " + t.list_type +
".initialize() method, " +
470 "since the item that was passed to that method did not already " +
471 "belong to a list.");
473 t.element.setAttribute(t.attr_name, t.attr_val_4);
475 if (t.item_type !=
"DOMString") {
476 var old_items = get_array_of_list_items(t.baseVal);
477 item = t.baseVal.getItem(
3);
478 res = t.baseVal.initialize(item);
481 t.baseVal.getItem(
0) !== item &&
482 t.baseVal.getItem(
0) !== old_items[
0] &&
483 res === t.baseVal.getItem(
0),
484 "The method " + t.list_type +
".initialize() should clone the object that " +
485 "is passed in if that object is already in a list.");
486 // [SVGWG issue] not what the spec currently says
489 item = t.baseVal.getItem(
0);
490 res = t.baseVal.initialize(item);
493 t.baseVal.getItem(
0) !== item,
494 "The method " + t.list_type +
".initialize() should clone the object that " +
495 "is passed in, even if that object is the only item in that list.");
496 // [SVGWG issue] not what the spec currently says
498 eventChecker.expect(
"");
501 t.baseVal.initialize({});
506 "The method " + t.list_type +
".initialize() should throw if passed an " +
507 "object of the wrong type.");
508 eventChecker.ignoreEvents();
511 // Test .insertItemBefore():
513 t.element.setAttribute(t.attr_name, t.attr_val_4);
515 old_items = get_array_of_list_items(t.baseVal);
516 item = t.item_constructor();
517 eventChecker.expect(
"modify");
518 res = t.baseVal.insertItemBefore(item,
2);
519 eventChecker.ignoreEvents();
521 is(t.baseVal.numberOfItems,
5,
522 "The " + t.list_type +
" object should contain five list items.");
524 "The list item returned by " + t.list_type +
".insertItemBefore() should " +
525 "be the exact same object as the item that was passed to that method, " +
526 "since the item that was passed to that method did not already belong " +
528 ok(t.baseVal.getItem(
2) === item,
529 "The list item at index 2 should be the exact same object as the " +
530 "object that was passed to the " + t.list_type +
".insertItemBefore() " +
531 "method, since the item that was passed to that method did not " +
532 "already belong to a list.");
533 ok(t.baseVal.getItem(
3) === old_items[
2],
534 "The list item that was at index 2 should be at index 3 after " +
535 "inserting a new item at index 2 using the " + t.list_type +
536 ".insertItemBefore() method.");
538 item = t.item_constructor();
539 t.baseVal.insertItemBefore(item,
100);
541 ok(t.baseVal.getItem(
5) === item,
542 "When the index passed to the " + t.list_type +
".insertItemBefore() " +
543 "method is out of bounds, the supplied list item should be appended " +
546 item = t.baseVal.getItem(
4);
547 res = t.baseVal.insertItemBefore(item,
2);
549 is(t.baseVal.numberOfItems,
7,
550 "The " + t.list_type +
" object should contain seven list items.");
551 if (t.item_type !=
"DOMString") {
553 t.baseVal.getItem(
2) !== item &&
554 t.baseVal.getItem(
2) !== old_items[
2] &&
555 res === t.baseVal.getItem(
2),
556 "The method " + t.list_type +
".insertItemBefore() should clone the " +
557 "object that is passed in if that object is already in a list.");
558 // [SVGWG issue] not what the spec currently says
561 item = t.baseVal.getItem(
2);
562 res = t.baseVal.insertItemBefore(item,
2);
564 is(t.baseVal.numberOfItems,
8,
565 "The " + t.list_type +
" object should contain eight list items.");
566 if (t.item_type !=
"DOMString") {
568 t.baseVal.getItem(
2) !== item,
569 "The method " + t.list_type +
".insertItemBefore() should clone the " +
570 "object that is passed in, even if that object is the item in " +
571 "the list at the index specified.");
572 // [SVGWG issue] not what the spec currently says
574 eventChecker.expect(
"");
577 t.baseVal.insertItemBefore({},
2);
582 "The method " + t.list_type +
".insertItemBefore() should throw if passed " +
583 "an object of the wrong type.");
584 eventChecker.ignoreEvents();
587 // Test .replaceItem():
589 t.element.setAttribute(t.attr_name, t.attr_val_4);
591 old_items = get_array_of_list_items(t.baseVal);
592 item = t.item_constructor();
593 eventChecker.expect(
"modify");
594 res = t.baseVal.replaceItem(item,
2);
595 eventChecker.ignoreEvents();
597 is(t.baseVal.numberOfItems,
4,
598 "The " + t.list_type +
" object should contain four list items.");
599 if (t.item_type !=
"DOMString") {
601 "The list item returned by " + t.list_type +
".replaceItem() should be " +
602 "the exact same object as the item that was passed to that method, " +
603 "since the item that was passed to that method did not already belong " +
606 ok(t.baseVal.getItem(
2) === item,
607 "The list item at index 2 should be the exact same object as the " +
608 "object that was passed to the " + t.list_type +
".replaceItem() method, " +
609 "since the item that was passed to that method did not already belong " +
611 ok(t.baseVal.getItem(
3) === old_items[
3],
612 "The list item that was at index 3 should still be at index 3 after " +
613 "the item at index 2 was replaced using the " + t.list_type +
614 ".replaceItem() method.");
616 item = t.item_constructor();
618 eventChecker.expect(
"");
621 t.baseVal.replaceItem(item,
100);
626 "The method " + t.list_type +
".replaceItem() should throw if passed " +
627 "an index that is out of bounds.");
628 eventChecker.ignoreEvents();
630 old_items = get_array_of_list_items(t.baseVal);
631 item = t.baseVal.getItem(
3);
632 res = t.baseVal.replaceItem(item,
1);
634 is(t.baseVal.numberOfItems,
4,
635 "The " + t.list_type +
" object should contain four list items.");
636 if (t.item_type !=
"DOMString") {
638 t.baseVal.getItem(
1) !== item &&
639 t.baseVal.getItem(
1) !== old_items[
1] &&
640 res === t.baseVal.getItem(
1),
641 "The method " + t.list_type +
".replaceItem() should clone the object " +
642 "that is passed in if that object is already in a list.");
643 // [SVGWG issue] not what the spec currently says
646 item = t.baseVal.getItem(
1);
647 res = t.baseVal.replaceItem(item,
1);
649 is(t.baseVal.numberOfItems,
4,
650 "The " + t.list_type +
" object should contain four list items.");
651 if (t.item_type !=
"DOMString") {
653 t.baseVal.getItem(
1) !== item,
654 "The method " + t.list_type +
".replaceItem() should clone the object " +
655 "that is passed in, even if the object that object and the object " +
656 "that is being replaced are the exact same objects.");
657 // [SVGWG issue] not what the spec currently says
661 t.baseVal.replaceItem({},
2);
666 "The method " + t.list_type +
".replaceItem() should throw if passed " +
667 "an object of the wrong type.");
670 // Test .removeItem():
672 t.element.setAttribute(t.attr_name, t.attr_val_4);
674 old_items = get_array_of_list_items(t.baseVal);
675 item = t.baseVal.getItem(
2);
676 eventChecker.expect(
"modify");
677 res = t.baseVal.removeItem(
2);
678 eventChecker.ignoreEvents();
680 is(t.baseVal.numberOfItems,
3,
681 "The " + t.list_type +
" object should contain three list items.");
682 if (t.item_type !=
"DOMString") {
684 "The list item returned by " + t.list_type +
".removeItem() should be the " +
685 "exact same object as the item that was at the specified index.");
687 ok(t.baseVal.getItem(
1) === old_items[
1],
688 "The list item that was at index 1 should still be at index 1 after " +
689 "the item at index 2 was removed using the " + t.list_type +
690 ".replaceItem() method.");
691 ok(t.baseVal.getItem(
2) === old_items[
3],
692 "The list item that was at index 3 should still be at index 2 after " +
693 "the item at index 2 was removed using the " + t.list_type +
694 ".replaceItem() method.");
696 eventChecker.expect(
"");
699 t.baseVal.removeItem(
100);
704 "The method " + t.list_type +
".removeItem() should throw if passed " +
705 "an index that is out of bounds.");
706 eventChecker.ignoreEvents();
708 // Test .appendItem():
710 t.element.setAttribute(t.attr_name, t.attr_val_4);
712 old_items = get_array_of_list_items(t.baseVal);
713 item = t.item_constructor();
714 eventChecker.expect(
"modify");
715 res = t.baseVal.appendItem(item);
716 eventChecker.ignoreEvents();
718 is(t.baseVal.numberOfItems,
5,
719 "The " + t.list_type +
" object should contain five list items.");
721 "The list item returned by " + t.list_type +
".appendItem() should be the " +
722 "exact same object as the item that was passed to that method, since " +
723 "the item that was passed to that method did not already belong " +
725 ok(t.baseVal.getItem(
4) === item,
726 "The last list item should be the exact same object as the object " +
727 "that was passed to the " + t.list_type +
".appendItem() method, since " +
728 "the item that was passed to that method did not already belong to " +
730 ok(t.baseVal.getItem(
3) === old_items[
3],
731 "The list item that was at index 4 should still be at index 4 after " +
732 "appending a new item using the " + t.list_type +
".appendItem() " +
735 item = t.baseVal.getItem(
2);
736 res = t.baseVal.appendItem(item);
738 is(t.baseVal.numberOfItems,
6,
739 "The " + t.list_type +
" object should contain six list items.");
740 if (t.item_type !=
"DOMString") {
742 t.baseVal.getItem(
5) !== item &&
743 res === t.baseVal.getItem(
5),
744 "The method " + t.list_type +
".appendItem() should clone the object " +
745 "that is passed in if that object is already in a list.");
746 // [SVGWG issue] not what the spec currently says
749 item = t.baseVal.getItem(
5);
750 res = t.baseVal.appendItem(item);
752 is(t.baseVal.numberOfItems,
7,
753 "The " + t.list_type +
" object should contain seven list items.");
754 if (t.item_type !=
"DOMString") {
756 t.baseVal.getItem(
6) !== item,
757 "The method " + t.list_type +
".appendItem() should clone the object " +
758 "that is passed in, if that object is already the last item in " +
760 // [SVGWG issue] not what the spec currently says
762 eventChecker.expect(
"");
765 t.baseVal.appendItem({});
770 "The method " + t.list_type +
".appendItem() should throw if passed " +
771 "an object of the wrong type.");
772 eventChecker.ignoreEvents();
775 // Test removal and addition events
777 eventChecker.expect(
"remove add");
778 t.element.removeAttribute(t.attr_name);
779 t.element.removeAttributeNS(null, t.attr_name);
780 res = t.baseVal.appendItem(item);
781 eventChecker.finish();
787 * This function tests the SVGXxxList API for the anim val list (see also the
788 * comment for test_baseVal_API).
790 function run_animVal_API_tests() {
793 for (var t of tests) {
795 continue; // SVGStringList isn't animatable
797 item = t.item_constructor();
799 t.element.setAttribute(t.attr_name, t.attr_val_4);
801 is(t.animVal.numberOfItems,
4,
802 "The " + t.list_type +
" object should contain four list items.");
813 "The method " + t.list_type +
".clear() should throw when called on an " +
814 "anim val list, since anim val lists should be readonly.");
818 item = t.animVal.getItem(
2);
819 ok(item != null && item === t.animVal.getItem(
2),
820 "The method " + t.list_type +
".getItem() should work when called on an " +
821 "anim val list, and always return the exact same object.");
827 t.animVal.initialize(item);
832 "The method " + t.list_type +
".initialize() should throw when called on " +
833 "an anim val list, since anim val lists should be readonly.");
835 // Test .insertItemBefore():
839 t.animVal.insertItemBefore(item,
2);
844 "The method " + t.list_type +
".insertItemBefore() should throw when " +
845 "called on an anim val list, since anim val lists should be readonly.");
847 // Test .replaceItem():
851 t.animVal.replaceItem(item,
2);
856 "The method " + t.list_type +
".replaceItem() should throw when called " +
857 "on an anim val list, since anim val lists should be readonly.");
859 // Test .removeItem():
863 t.animVal.removeItem(
2);
868 "The method " + t.list_type +
".removeItem() should throw when called " +
869 "on an anim val list, since anim val lists should be readonly.");
871 // Test .appendItem():
875 t.animVal.appendItem(item);
880 "The method " + t.list_type +
".appendItem() should throw when called " +
881 "on an anim val list, since anim val lists should be readonly.");
887 * This function runs some basic tests to check the effect of setAttribute()
888 * calls on object identity, without taking SMIL animation into consideration.
890 function run_basic_setAttribute_tests() {
891 for (var t of tests) {
892 // Since the t.prop, t.baseVal and t.animVal objects should never ever
893 // change, we leave testing of them to our caller so that it can check
894 // them after all the other mutations such as SMIL changes.
896 t.element.setAttribute(t.attr_name, t.attr_val_4);
898 ok(t.baseVal.numberOfItems ==
4 && t.baseVal.getItem(
3) != null,
899 "The length of the " + t.list_type +
" object for " + t.bv_path +
" should " +
900 "have been set to 4 by the setAttribute() call.");
903 ok(t.baseVal.numberOfItems == t.animVal.numberOfItems,
904 "When no animations are active, the " + t.list_type +
" objects for " +
905 t.bv_path +
" and " + t.av_path +
" should be the same length (4).");
907 ok(t.baseVal !== t.animVal,
908 "The " + t.list_type +
" objects for " + t.bv_path +
" and " + t.av_path +
909 " should be different objects.");
911 ok(t.baseVal.getItem(
0) !== t.animVal.getItem(
0),
912 "The " + t.item_type +
" list items in the " + t.list_type +
" objects for " +
913 t.bv_path +
" and " + t.av_path +
" should be different objects.");
916 // eslint-disable-next-line no-self-compare
917 ok(t.baseVal.getItem(
0) === t.baseVal.getItem(
0),
918 "The exact same " + t.item_type +
" DOM object should be returned each " +
919 "time the item at a given index in the " + t.list_type +
" for " +
920 t.bv_path +
" is accessed, given that the index was not made invalid " +
921 "by a change in list length between the successive accesses.");
924 // eslint-disable-next-line no-self-compare
925 ok(t.animVal.getItem(
0) === t.animVal.getItem(
0),
926 "The exact same " + t.item_type +
" DOM object should be returned each " +
927 "time the item at a given index in the " + t.list_type +
" for " +
928 t.av_path +
" is accessed, given that the index was not made invalid " +
929 "by a change in list length between the successive accesses.");
932 // Test the effect of setting the attribute to new values:
934 t.old_baseVal_items = get_array_of_list_items(t.baseVal);
936 t.old_animVal_items = get_array_of_list_items(t.animVal);
939 t.element.setAttribute(t.attr_name, t.attr_val_3a);
940 t.element.setAttribute(t.attr_name, t.attr_val_5a);
942 ok(t.baseVal.numberOfItems ==
5 && t.baseVal.getItem(
4) != null,
943 "The length of the " + t.list_type +
" object for " + t.bv_path +
" should " +
944 "have been set to 5 by the setAttribute() call.");
947 ok(t.baseVal.numberOfItems == t.animVal.numberOfItems,
948 "Since no animations are active, the length of the " + t.list_type +
" " +
949 "objects for " + t.bv_path +
" and " + t.av_path +
" should be the same " +
953 if (t.item_type !=
"DOMString") {
954 ok(t.baseVal.getItem(
2) === t.old_baseVal_items[
2],
955 "After its attribute changes, list items in the " + t.list_type +
" for " +
956 t.bv_path +
" that are at indexes that existed prior to the attribute " +
957 "change should be the exact same objects as the objects that were " +
958 "at those indexes prior to the attribute change.");
960 ok(t.baseVal.getItem(
3) !== t.old_baseVal_items[
3],
961 "After its attribute changes, list items in the " + t.list_type +
" for " +
962 t.bv_path +
" that are at indexes that did not exist prior to the " +
963 "attribute change should not be the same objects as any objects that " +
964 "were at those indexes at some earlier time.");
968 ok(t.animVal.getItem(
2) === t.old_animVal_items[
2],
969 "After its attribute changes, list items in the " + t.list_type +
" for " +
970 t.av_path +
" that are at indexes that existed prior to the attribute " +
971 "change should be the exact same objects as the objects that were " +
972 "at those indexes prior to the attribute change.");
974 ok(t.animVal.getItem(
3) !== t.old_animVal_items[
3],
975 "After its attribute changes, list items in the " + t.list_type +
" for " +
976 t.av_path +
" that are at indexes that did not exist prior to the " +
977 "attribute change should not be the same objects as any objects " +
978 "that were at those indexes at some earlier time.");
984 * This function verifies that a list's animVal is kept in sync with its
985 * baseVal, when we add & remove items from the baseVal.
987 function run_list_mutation_tests() {
988 for (var t of tests) {
992 // Save second item in baseVal list; then make it the first item, and
993 // check that animVal is updated accordingly.
994 t.element.setAttribute(t.attr_name, t.attr_val_4);
996 var secondVal = t.baseVal.getItem(
1);
997 var removedFirstVal = t.baseVal.removeItem(
0);
998 t.item_is(t.animVal.getItem(
0), secondVal,
999 "animVal for " + t.attr_name +
" needs update after first item " +
1002 // Repeat with last item
1003 var secondToLastVal = t.baseVal.getItem(
1);
1004 var removedLastVal = t.baseVal.removeItem(
2);
1008 t.animVal.getItem(
2);
1013 "The method " + t.attr_name +
".animVal.getItem() for previously-final " +
1014 "index should throw after final item is removed from baseVal.");
1016 t.item_is(t.animVal.getItem(
1), secondToLastVal,
1017 "animVal for " + t.attr_name +
" needs update after last item " +
1020 // Test insertItemBefore()
1021 // =======================
1022 // Reset base value, insert value @ start, check that animVal is updated.
1023 t.element.setAttribute(t.attr_name, t.attr_val_3a);
1024 t.baseVal.insertItemBefore(removedLastVal,
0);
1025 t.item_is(t.animVal.getItem(
0), removedLastVal,
1026 "animVal for " + t.attr_name +
" needs update after insert at " +
1029 // Repeat with insert at end
1030 t.element.setAttribute(t.attr_name, t.attr_val_3a);
1031 t.baseVal.insertItemBefore(removedFirstVal, t.baseVal.numberOfItems);
1032 t.item_is(t.animVal.getItem(t.baseVal.numberOfItems -
1),
1034 "animVal for " + t.attr_name +
" needs update after insert at end");
1036 // Test appendItem()
1037 // =================
1038 var dummy = t.item_constructor();
1039 t.baseVal.appendItem(dummy);
1040 t.item_is(t.animVal.getItem(t.baseVal.numberOfItems -
1), dummy,
1041 "animVal for " + t.attr_name +
" needs update after appendItem");
1048 t.animVal.getItem(
0);
1053 "The method " + t.attr_name +
".animVal.getItem() should throw after " +
1054 "we've cleared baseVal.");
1056 is(t.animVal.numberOfItems,
0,
1057 "animVal for " + t.attr_name +
" should be empty after baseVal cleared");
1059 // Test initialize()
1060 // =================
1061 t.element.setAttribute(t.attr_name, t.attr_val_3a);
1062 t.baseVal.initialize(dummy);
1064 is(t.animVal.numberOfItems,
1,
1065 "animVal for " + t.attr_name +
" should have length 1 after initialize");
1066 t.item_is(t.animVal.getItem(
0), dummy,
1067 "animVal for " + t.attr_name +
" needs update after initialize");
1073 * In this function we run a series of tests at various points along the SMIL
1074 * animation timeline, using SVGSVGElement.setCurrentTime() to move forward
1075 * along the timeline.
1077 * See the comment for create_animate_elements() for details of the animations
1078 * and their timings.
1080 function run_animation_timeline_tests() {
1081 var svg = document.getElementById(
"svg");
1083 for (var t of tests) {
1084 // Skip if there is no animVal for this test or if it is a transform list
1085 // since these are handled specially
1086 if (!t.animVal || is_transform_attr(t.attr_name))
1089 svg.setCurrentTime(
0); // reset timeline
1091 // Reset attributes before moving along the timeline and triggering SMIL:
1092 t.element.setAttribute(t.attr_name, t.attr_val_4);
1093 t.old_baseVal_items = get_array_of_list_items(t.baseVal);
1094 t.old_animVal_items = get_array_of_list_items(t.animVal);
1097 /** ****************** t =
1s ********************/
1099 svg.setCurrentTime(
1); // begin first animation
1101 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
1102 t.baseVal.getItem(
3) === t.old_baseVal_items[
3],
1103 "The start of an animation should never affect the " + t.list_type +
1104 " for " + t.bv_path +
", or its list items.");
1106 ok(t.animVal.numberOfItems ==
5 && t.animVal.getItem(
4) != null,
1107 "The start of the animation should have changed the number of items " +
1108 "in the " + t.list_type +
" for " + t.bv_path +
" to 5.");
1111 ok(t.animVal.getItem(
3) === t.old_animVal_items[
3],
1112 "When affected by SMIL animation, list items in the " + t.list_type +
1113 " for " + t.bv_path +
" that are at indexes that existed prior to the " +
1114 "start of the animation should be the exact same objects as the " +
1115 "objects that were at those indexes prior to the start of the " +
1118 t.old_animVal_items = get_array_of_list_items(t.animVal);
1120 t.element.setAttribute(t.attr_name, t.attr_val_3a);
1122 ok(t.baseVal.numberOfItems ==
3 &&
1123 t.baseVal.getItem(
2) === t.old_baseVal_items[
2],
1124 "Setting the underlying attribute should change the items in the " +
1125 t.list_type +
" for " + t.bv_path +
", including when an animation is " +
1128 ok(t.animVal.numberOfItems ==
5 &&
1129 t.animVal.getItem(
4) === t.old_animVal_items[
4],
1130 "Setting the underlying attribute should not change the " + t.list_type +
1131 " for " + t.bv_path +
" when an animation that does not depend on the " +
1132 "base val is in progress.");
1134 t.element.setAttribute(t.attr_name, t.attr_val_4); // reset
1136 t.old_baseVal_items = get_array_of_list_items(t.baseVal);
1137 t.old_animVal_items = get_array_of_list_items(t.animVal);
1140 /** ****************** t =
2s ********************/
1142 svg.setCurrentTime(
2); // begin override animation
1144 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
1145 t.baseVal.getItem(
3) === t.old_baseVal_items[
3],
1146 "The start of an override animation should never affect the " +
1147 t.list_type +
" for " + t.bv_path +
", or its list items.");
1149 is(t.animVal.numberOfItems,
3,
1150 "The start of the override animation should have changed the number " +
1151 "of items in the " + t.list_type +
" for " + t.bv_path +
" to 3.");
1153 ok(t.animVal.getItem(
2) === t.old_animVal_items[
2],
1154 "When affected by an override SMIL animation, list items in the " +
1155 t.list_type +
" for " + t.bv_path +
" that are at indexes that existed " +
1156 "prior to the start of the animation should be the exact same " +
1157 "objects as the objects that were at those indexes prior to the " +
1158 "start of that animation.");
1160 t.old_animVal_items = get_array_of_list_items(t.animVal);
1163 /** ****************** t =
3s ********************/
1165 svg.setCurrentTime(
3); // end of override animation
1167 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
1168 t.baseVal.getItem(
3) === t.old_baseVal_items[
3],
1169 "The end of an override animation should never affect the " +
1170 t.list_type +
" for " + t.bv_path +
", or its list items.");
1172 is(t.animVal.numberOfItems,
5,
1173 "At the end of the override animation, the number of items in the " +
1174 t.list_type +
" for " + t.bv_path +
" should have reverted to 5.");
1176 ok(t.animVal.getItem(
2) === t.old_animVal_items[
2],
1177 "At the end of the override animation, list items in the " +
1178 t.list_type +
" for " + t.bv_path +
" that are at indexes that existed " +
1179 "prior to the end of the animation should be the exact same " +
1180 "objects as the objects that were at those indexes prior to the " +
1181 "end of that animation.");
1183 t.old_animVal_items = get_array_of_list_items(t.animVal);
1186 /** ****************** t =
5s ********************/
1188 svg.setCurrentTime(
5); // animation repeat point
1190 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
1191 t.baseVal.getItem(
3) === t.old_baseVal_items[
3],
1192 "When a SMIL animation repeats, it should never affect the " +
1193 t.list_type +
" for " + t.bv_path +
", or its list items.");
1195 ok(t.animVal.numberOfItems == t.old_animVal_items.length &&
1196 t.animVal.getItem(
4) === t.old_animVal_items[
4],
1197 "When an animation repeats, the list items that are at a given " +
1198 "index in the " + t.list_type +
" for " + t.av_path +
" should be the exact " +
1199 "same objects as were at that index before the repeat occurred.");
1202 /** ****************** t =
6s ********************/
1204 svg.setCurrentTime(
6); // inside animation repeat
1206 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
1207 t.baseVal.getItem(
3) === t.old_baseVal_items[
3],
1208 "When a SMIL animation repeats, it should never affect the " +
1209 t.list_type +
" for " + t.bv_path +
", or its list items.");
1211 ok(t.animVal.numberOfItems == t.old_animVal_items.length &&
1212 t.animVal.getItem(
4) === t.old_animVal_items[
4],
1213 "When an animation repeats, the list items that are at a given " +
1214 "index in the " + t.list_type +
" for " + t.av_path +
" should be the exact " +
1215 "same objects as were at that index before the repeat occurred.");
1218 /** ****************** t =
7s ********************/
1220 svg.setCurrentTime(
7); // start of
additive=
"sum" animation
1222 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
1223 t.baseVal.getItem(
3) === t.old_baseVal_items[
3],
1224 "When a new SMIL animation starts and should blend with an " +
1225 "underlying animation, it should never affect the " +
1226 t.list_type +
" for " + t.bv_path +
", or its list items.");
1228 if (t.list_type ==
"SVGLengthList") {
1229 // Length lists are a special case where it makes sense to allow shorter
1230 // lists to be composed on top of longer lists (but not necessarily vice
1231 // versa - see comment below).
1233 ok(t.animVal.numberOfItems == t.old_animVal_items.length &&
1234 t.animVal.getItem(
3) === t.old_animVal_items[
3],
1235 'When an animation with
additive=
"sum" is added on top of an ' +
1236 "existing animation that has more list items, the length of the " +
1237 t.list_type +
" for " + t.av_path +
" should not change.");
1242 'Decide what to do here - see ' +
1243 'https://bugzilla.mozilla.org/show_bug.cgi?id=
573716 - we ' +
1244 'probably should be discarding any animation sandwich layers from ' +
1245 'a layer that fails to add, on up.');
1248 // In other words, we wouldn't need the if-else check here.
1252 // XXX what if the higher priority sandwich layer has *more* list items
1253 // than the underlying animation? In that case we would need to
1254 // distinguish between different SVGLengthList attributes, since although
1255 // all SVGLengthList attributes allow a short list to be added to longer
1256 // list, they do not all allow a longer list to be added to shorter list.
1257 // Specifically that would not be good for 'x' and 'y' on
<text> since
1258 // lengths there are not naturally zero. See the comment in
1259 // SVGLengthListSMILAttr::Add().
1262 /** ****************** t =
13s ********************/
1264 svg.setCurrentTime(
13); // all animations have finished, but one is frozen
1266 ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
1267 t.baseVal.getItem(
3) === t.old_baseVal_items[
3],
1268 "When a SMIL animation ends, it should never affect the " +
1269 t.list_type +
" for " + t.bv_path +
", or its list items.");
1271 is(t.animVal.numberOfItems,
5,
1272 "Even though all SMIL animation have finished, the number " +
1273 "of items in the " + t.list_type +
" for " + t.av_path +
1274 " should still be more than the same as the number of items in " +
1275 t.bv_path +
" since one of the animations is still frozen.");
1277 var expected = t.attr_val_5b_firstItem_x3_constructor(t.item_constructor);
1278 t.item_is(t.animVal.getItem(
0), expected,
1279 'animation with
accumulate=
"sum" and
repeatCount=
"3" for attribute
"' +
1280 t.attr_name + '" should end up at
3x the
"to" value.');
1282 // Unfreeze frozen animation (removing its effects)
1283 var frozen_animate_element =
1284 t.element.querySelector('animate[fill][
attributeName=
"' + t.attr_name + '"]');
1285 frozen_animate_element.removeAttribute(
"fill");
1287 ok(t.animVal.numberOfItems == t.baseVal.numberOfItems,
1288 "Once all SMIL animation have finished and been un-frozen, the number " +
1289 "of items in the " + t.list_type +
" for " + t.av_path +
1290 " should be the same as the number of items in " + t.bv_path +
".");
1292 ok(t.animVal.getItem(
2) === t.old_animVal_items[
2],
1293 "Even after an animation finishes and is un-frozen, the list items " +
1294 "that are at a given index in the " + t.list_type +
" for " + t.av_path +
1295 " should be the exact same objects as were at that index before the " +
1296 "end and unfreezing of the animation occurred.");
1301 function run_tests() {
1302 // Initialize each test object with some useful properties, and create their
1303 // 'animate' elements. Note that 'prop' and 'animVal' may be null.
1304 for (let t of tests) {
1305 t.element = document.getElementById(t.target_element_id);
1306 t.prop = t.prop_name ? t.element[t.prop_name] : null;
1307 t.baseVal = ( t.prop || t.element )[t.bv_name];
1308 t.animVal = t.av_name ? ( t.prop || t.element )[t.av_name] : null;
1309 t.bv_path = t.el_type +
"." +
1310 (t.prop ? t.prop_name +
"." :
"") +
1311 t.bv_name; // e.g. 'SVGTextElement.x.baseVal'
1313 t.av_path = t.el_type +
"." +
1314 (t.prop ? t.prop_name +
"." :
"") +
1317 t.prop_type = t.prop_type || null;
1319 // use fallback 'is' function, if none was provided.
1321 t.item_is = function(itemA, itemB, message) {
1322 ok(typeof(itemA.value) !=
"undefined" &&
1323 typeof(itemB.value) !=
"undefined",
1324 "expecting value property");
1325 is(itemA.value, itemB.value, message);
1330 t.element.appendChild(create_animate_elements(t));
1334 // Run the major test groups:
1336 run_baseVal_API_tests();
1337 run_animVal_API_tests();
1338 run_basic_setAttribute_tests();
1339 run_list_mutation_tests();
1340 run_animation_timeline_tests();
1342 // After all the other test manipulations, we check that the following
1343 // objects have still not changed, since they never should:
1345 for (let t of tests) {
1347 ok(t.prop === t.element[t.prop_name],
1348 "The same " + t.prop_type +
" object should ALWAYS be returned for " +
1349 t.el_type +
"." + t.prop_name +
" each time it is accessed.");
1352 ok(t.baseVal === ( t.prop || t.element )[t.bv_name],
1353 "The same " + t.list_type +
" object should ALWAYS be returned for " +
1354 t.el_type +
"." + t.prop_name +
"." + t.bv_name +
" each time it is accessed.");
1357 ok(t.animVal === ( t.prop || t.element )[t.av_name],
1358 "The same " + t.list_type +
" object should ALWAYS be returned for " +
1359 t.el_type +
"." + t.prop_name +
"." + t.av_name +
" each time it is accessed.");
1363 SimpleTest.finish();
1366 window.addEventListener(
"load",
1367 () =
> SpecialPowers.pushPrefEnv({
"set": [[
"dom.mutation_events.enabled", true]]}, run_tests));